port recast to rust 🦀 (#178)

* port recast to rust with massaged serialisation

* remove logs

* fix the last of white space test failures

* remove ts recastor

* clean up imports

* use serde serialise features

* unneeded async

* final clean up recast.ts

* more clean up tweaks

* improve Rust BinaryPart types

* Comments, fix warnings

* Run clippy --fix

* Remove unused variable

* serialise none_code_nodes manual to force strings to numbers

---------

Co-authored-by: Adam Chalmers <adam.s.chalmers@gmail.com>
This commit is contained in:
Kurt Hutten
2023-07-20 12:38:05 +10:00
committed by GitHub
parent 11cc85a9e8
commit 5d90c0488f
13 changed files with 1022 additions and 547 deletions

View File

@ -11,7 +11,7 @@ export const LoginButton = () => {
const token: string = await invoke('login')
setToken(token)
} catch (error) {
console.error("login button", error)
console.error('login button', error)
}
}
return <button onClick={() => handleClick()}>Login</button>

View File

@ -1685,15 +1685,17 @@ const key = 'c'`
value: '\n// this is a comment\n',
}
const { nonCodeMeta } = abstractSyntaxTree(lexer(code))
expect(nonCodeMeta[0]).toEqual(nonCodeMetaInstance)
expect(nonCodeMeta.noneCodeNodes[0]).toEqual(nonCodeMetaInstance)
// extra whitespace won't change it's position (0) or value (NB the start end would have changed though)
const codeWithExtraStartWhitespace = '\n\n\n' + code
const { nonCodeMeta: nonCodeMeta2 } = abstractSyntaxTree(
lexer(codeWithExtraStartWhitespace)
)
expect(nonCodeMeta2[0].value).toBe(nonCodeMetaInstance.value)
expect(nonCodeMeta2[0].start).not.toBe(nonCodeMetaInstance.start)
expect(nonCodeMeta2.noneCodeNodes[0].value).toBe(nonCodeMetaInstance.value)
expect(nonCodeMeta2.noneCodeNodes[0].start).not.toBe(
nonCodeMetaInstance.start
)
})
it('comments nested within a block statement', () => {
const code = `const mySketch = startSketchAt([0,0])
@ -1708,6 +1710,7 @@ const key = 'c'`
const { body } = abstractSyntaxTree(lexer(code))
const indexOfSecondLineToExpression = 2
const sketchNonCodeMeta = (body as any)[0].declarations[0].init.nonCodeMeta
.noneCodeNodes
expect(sketchNonCodeMeta[indexOfSecondLineToExpression]).toEqual({
type: 'NoneCodeNode',
start: 106,
@ -1728,6 +1731,7 @@ const key = 'c'`
const { body } = abstractSyntaxTree(lexer(code))
const sketchNonCodeMeta = (body[0] as any).declarations[0].init.nonCodeMeta
.noneCodeNodes
expect(sketchNonCodeMeta[3]).toEqual({
type: 'NoneCodeNode',
start: 125,

View File

@ -795,7 +795,7 @@ function makePipeBody(
tokens: Token[],
index: number,
previousValues: Value[] = [],
previousNonCodeMeta: NoneCodeMeta = {}
previousNonCodeMeta: NoneCodeMeta = { noneCodeNodes: {} }
): { body: Value[]; lastIndex: number; nonCodeMeta: NoneCodeMeta } {
const nonCodeMeta = { ...previousNonCodeMeta }
const currentToken = tokens[index]
@ -819,7 +819,8 @@ function makePipeBody(
}
}
if (nextPipeToken.bonusNonCodeNode) {
nonCodeMeta[previousValues.length] = nextPipeToken.bonusNonCodeNode
nonCodeMeta.noneCodeNodes[previousValues.length] =
nextPipeToken.bonusNonCodeNode
}
return makePipeBody(
tokens,
@ -885,7 +886,11 @@ function makeBlockStatement(
const nextToken = { token: tokens[index + 1], index: index + 1 }
const { body, lastIndex, nonCodeMeta } =
nextToken.token.value === '}'
? { body: [], lastIndex: nextToken.index, nonCodeMeta: {} }
? {
body: [],
lastIndex: nextToken.index,
nonCodeMeta: { noneCodeNodes: {} },
}
: makeBody({ tokens, tokenIndex: nextToken.index })
return {
block: {
@ -964,7 +969,7 @@ function makeBody(
tokenIndex?: number
},
previousBody: BodyItem[] = [],
previousNonCodeMeta: NoneCodeMeta = {}
previousNonCodeMeta: NoneCodeMeta = { noneCodeNodes: {} }
): { body: BodyItem[]; lastIndex: number; nonCodeMeta: NoneCodeMeta } {
const nonCodeMeta = { ...previousNonCodeMeta }
if (tokenIndex >= tokens.length) {
@ -981,7 +986,8 @@ function makeBody(
if (previousBody.length === 0) {
nonCodeMeta.start = nextToken.bonusNonCodeNode
} else {
nonCodeMeta[previousBody.length] = nextToken.bonusNonCodeNode
nonCodeMeta.noneCodeNodes[previousBody.length] =
nextToken.bonusNonCodeNode
}
}
return makeBody(
@ -992,7 +998,8 @@ function makeBody(
}
const nextToken = nextMeaningfulToken(tokens, tokenIndex)
nextToken.bonusNonCodeNode &&
(nonCodeMeta[previousBody.length] = nextToken.bonusNonCodeNode)
(nonCodeMeta.noneCodeNodes[previousBody.length] =
nextToken.bonusNonCodeNode)
if (
token.type === 'word' &&
@ -1004,7 +1011,8 @@ function makeBody(
)
const nextThing = nextMeaningfulToken(tokens, lastIndex)
nextThing.bonusNonCodeNode &&
(nonCodeMeta[previousBody.length] = nextThing.bonusNonCodeNode)
(nonCodeMeta.noneCodeNodes[previousBody.length] =
nextThing.bonusNonCodeNode)
return makeBody(
{ tokens, tokenIndex: nextThing.index },
@ -1016,7 +1024,8 @@ function makeBody(
const { statement, lastIndex } = makeReturnStatement(tokens, tokenIndex)
const nextThing = nextMeaningfulToken(tokens, lastIndex)
nextThing.bonusNonCodeNode &&
(nonCodeMeta[previousBody.length] = nextThing.bonusNonCodeNode)
(nonCodeMeta.noneCodeNodes[previousBody.length] =
nextThing.bonusNonCodeNode)
return makeBody(
{ tokens, tokenIndex: nextThing.index },
@ -1035,7 +1044,8 @@ function makeBody(
)
const nextThing = nextMeaningfulToken(tokens, lastIndex)
if (nextThing.bonusNonCodeNode) {
nonCodeMeta[previousBody.length] = nextThing.bonusNonCodeNode
nonCodeMeta.noneCodeNodes[previousBody.length] =
nextThing.bonusNonCodeNode
}
return makeBody(
@ -1050,7 +1060,8 @@ function makeBody(
nextThing.token.type === 'operator'
) {
if (nextThing.bonusNonCodeNode) {
nonCodeMeta[previousBody.length] = nextThing.bonusNonCodeNode
nonCodeMeta.noneCodeNodes[previousBody.length] =
nextThing.bonusNonCodeNode
}
const { expression, lastIndex } = makeExpressionStatement(
tokens,

View File

@ -64,7 +64,7 @@ export interface NoneCodeNode extends GeneralStatement {
export interface NoneCodeMeta {
// Stores the whitespace/comments that go after the statement who's index we're using here
[statementIndex: number]: NoneCodeNode
noneCodeNodes: { [statementIndex: number]: NoneCodeNode }
// Which is why we also need `start` for and whitespace at the start of the file/block
start?: NoneCodeNode
}

View File

@ -108,7 +108,7 @@ describe('Testing addSketchTo', () => {
body: [],
start: 0,
end: 0,
nonCodeMeta: {},
nonCodeMeta: { noneCodeNodes: {} },
},
'yz'
)

View File

@ -452,7 +452,7 @@ export function createPipeExpression(
start: 0,
end: 0,
body,
nonCodeMeta: {},
nonCodeMeta: { noneCodeNodes: {} },
}
}

View File

@ -1,273 +1,4 @@
import {
Program,
BinaryExpression,
BinaryPart,
Literal,
CallExpression,
Value,
FunctionExpression,
ArrayExpression,
ObjectExpression,
MemberExpression,
PipeExpression,
UnaryExpression,
} from './abstractSyntaxTreeTypes'
import { precedence } from './astMathExpressions'
import { Program } from './abstractSyntaxTreeTypes'
import { recast_js } from '../wasm-lib/pkg/wasm_lib'
export function recast(
ast: Program,
previousWrittenCode = '',
indentation = '',
isWithBlock = false
): string {
return ast.body
.map((statement) => {
if (statement.type === 'ExpressionStatement') {
if (statement.expression.type === 'BinaryExpression') {
return recastBinaryExpression(statement.expression)
} else if (statement.expression.type === 'ArrayExpression') {
return recastArrayExpression(statement.expression)
} else if (statement.expression.type === 'ObjectExpression') {
return recastObjectExpression(statement.expression)
} else if (statement.expression.type === 'CallExpression') {
return recastCallExpression(statement.expression)
}
} else if (statement.type === 'VariableDeclaration') {
return statement.declarations
.map((declaration) => {
return `${statement.kind} ${declaration.id.name} = ${recastValue(
declaration.init
)}`
})
.join('')
} else if (statement.type === 'ReturnStatement') {
return `return ${recastArgument(statement.argument)}`
}
return statement.type
})
.map((recastStr, index, arr) => {
const isLegitCustomWhitespaceOrComment = (str: string) =>
str !== ' ' && str !== '\n' && str !== ' '
// determine the value of startString
const lastWhiteSpaceOrComment =
index > 0 ? ast?.nonCodeMeta?.[index - 1]?.value : ' '
// indentation of this line will be covered by the previous if we're using a custom whitespace or comment
let startString = isLegitCustomWhitespaceOrComment(
lastWhiteSpaceOrComment
)
? ''
: indentation
if (index === 0) {
startString = ast?.nonCodeMeta?.start?.value || indentation
}
if (startString.endsWith('\n')) {
startString += indentation
}
// determine the value of endString
const maybeLineBreak: string =
index === arr.length - 1 && !isWithBlock ? '' : '\n'
let customWhiteSpaceOrComment = ast?.nonCodeMeta?.[index]?.value
if (!isLegitCustomWhitespaceOrComment(customWhiteSpaceOrComment))
customWhiteSpaceOrComment = ''
let endString = customWhiteSpaceOrComment || maybeLineBreak
return startString + recastStr + endString
})
.join('')
}
function recastBinaryExpression(expression: BinaryExpression): string {
const maybeWrapIt = (a: string, doit: boolean) => (doit ? `(${a})` : a)
const shouldWrapRight =
expression.right.type === 'BinaryExpression' &&
(precedence(expression.operator) > precedence(expression.right.operator) ||
expression.right.operator === '-')
const shouldWrapLeft =
expression.left.type === 'BinaryExpression' &&
precedence(expression.operator) > precedence(expression.left.operator)
return `${maybeWrapIt(recastBinaryPart(expression.left), shouldWrapLeft)} ${
expression.operator
} ${maybeWrapIt(recastBinaryPart(expression.right), shouldWrapRight)}`
}
function recastUnaryExpression(expression: UnaryExpression): string {
return `${expression.operator}${recastValue(expression.argument)}`
}
function recastArrayExpression(
expression: ArrayExpression,
indentation = ''
): string {
const flatRecast = `[${expression.elements
.map((el) => recastValue(el))
.join(', ')}]`
const maxArrayLength = 40
if (flatRecast.length > maxArrayLength) {
const _indentation = indentation + ' '
return `[
${_indentation}${expression.elements
.map((el) => recastValue(el, _indentation))
.join(`,\n${_indentation}`)}
${indentation}]`
}
return flatRecast
}
function recastObjectExpression(
expression: ObjectExpression,
indentation = '',
isInPipeExpression = false
): string {
const flatRecast = `{ ${expression.properties
.map((prop) => `${prop.key.name}: ${recastValue(prop.value)}`)
.join(', ')} }`
const maxArrayLength = 40
if (flatRecast.length > maxArrayLength) {
const _indentation = indentation + ' '
return `{
${_indentation}${expression.properties
.map((prop) => `${prop.key.name}: ${recastValue(prop.value)}`)
.join(`,\n${_indentation}`)}
${isInPipeExpression ? ' ' : ''}}`
}
return flatRecast
}
function recastBinaryPart(part: BinaryPart): string {
if (part.type === 'Literal') {
return recastLiteral(part)
} else if (part.type === 'Identifier') {
return part.name
} else if (part.type === 'BinaryExpression') {
return recastBinaryExpression(part)
} else if (part.type === 'CallExpression') {
return recastCallExpression(part)
}
return ''
// throw new Error(`Cannot recast BinaryPart ${part}`)
}
function recastLiteral(literal: Literal): string {
if (typeof literal.value === 'string') {
const quote = literal.raw.trim().startsWith('"') ? '"' : "'"
return `${quote}${literal.value}${quote}`
}
return String(literal?.value)
}
function recastCallExpression(
expression: CallExpression,
indentation = '',
isInPipeExpression = false
): string {
return `${expression.callee.name}(${expression.arguments
.map((arg) => recastArgument(arg, indentation, isInPipeExpression))
.join(', ')})`
}
function recastArgument(
argument: Value,
indentation = '',
isInPipeExpression = false
): string {
if (argument.type === 'Literal') {
return recastLiteral(argument)
} else if (argument.type === 'Identifier') {
return argument.name
} else if (argument.type === 'BinaryExpression') {
return recastBinaryExpression(argument)
} else if (argument.type === 'ArrayExpression') {
return recastArrayExpression(argument, indentation)
} else if (argument.type === 'ObjectExpression') {
return recastObjectExpression(argument, indentation, isInPipeExpression)
} else if (argument.type === 'CallExpression') {
return recastCallExpression(argument)
} else if (argument.type === 'FunctionExpression') {
return recastFunction(argument)
} else if (argument.type === 'PipeSubstitution') {
return '%'
} else if (argument.type === 'UnaryExpression') {
return recastUnaryExpression(argument)
}
throw new Error(`Cannot recast argument ${argument}`)
}
function recastFunction(expression: FunctionExpression): string {
return `(${expression.params
.map((param) => param.name)
.join(', ')}) => {${recast(expression.body, '', '', true)}}`
}
function recastMemberExpression(
expression: MemberExpression,
indentation: string
): string {
// TODO handle breaking into multiple lines if too long
let keyString =
expression.computed && expression.property.type === 'Identifier'
? `[${expression.property.name}]`
: expression.property.type !== 'Identifier'
? `[${expression.property.raw}]`
: `.${expression.property.name}`
if (expression.object.type === 'MemberExpression') {
return recastMemberExpression(expression.object, indentation) + keyString
}
return expression.object.name + keyString
}
function recastValue(
node: Value,
_indentation = '',
isInPipeExpression = false
): string {
const indentation = _indentation + (isInPipeExpression ? ' ' : '')
if (node.type === 'BinaryExpression') {
return recastBinaryExpression(node)
} else if (node.type === 'ArrayExpression') {
return recastArrayExpression(node, indentation)
} else if (node.type === 'ObjectExpression') {
return recastObjectExpression(node, indentation, isInPipeExpression)
} else if (node.type === 'MemberExpression') {
return recastMemberExpression(node, indentation)
} else if (node.type === 'Literal') {
return recastLiteral(node)
} else if (node.type === 'FunctionExpression') {
return recastFunction(node)
} else if (node.type === 'CallExpression') {
return recastCallExpression(node, indentation, isInPipeExpression)
} else if (node.type === 'Identifier') {
return node.name
} else if (node.type === 'PipeExpression') {
return recastPipeExpression(node)
} else if (node.type === 'UnaryExpression') {
return recastUnaryExpression(node)
}
return ''
}
function recastPipeExpression(expression: PipeExpression): string {
return expression.body
.map((statement, index, arr): string => {
let str = ''
let indentation = ' '
let maybeLineBreak = '\n'
str = recastValue(statement, indentation, true)
if (
expression.nonCodeMeta?.[index]?.value &&
expression.nonCodeMeta?.[index].value !== ' '
) {
str += expression.nonCodeMeta[index]?.value
indentation = ''
maybeLineBreak = ''
}
if (index !== arr.length - 1) {
str += maybeLineBreak + indentation + '|> '
}
return str
})
.join('')
}
export const recast = (ast: Program): string => recast_js(JSON.stringify(ast))

View File

@ -23,12 +23,27 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "itoa"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
[[package]]
name = "js-sys"
version = "0.3.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -58,18 +73,18 @@ checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "proc-macro2"
version = "1.0.51"
version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.23"
version = "1.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105"
dependencies = [
"proc-macro2",
]
@ -106,6 +121,18 @@ dependencies = [
"serde_derive",
]
[[package]]
name = "serde-wasm-bindgen"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "618365e8e586c22123d692b72a7d791d5ee697817b65a218cdf12a98870af0f7"
dependencies = [
"fnv",
"js-sys",
"serde",
"wasm-bindgen",
]
[[package]]
name = "serde_derive"
version = "1.0.152"
@ -114,7 +141,7 @@ checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.107",
]
[[package]]
@ -139,6 +166,17 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.6"
@ -147,9 +185,9 @@ checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]]
name = "wasm-bindgen"
version = "0.2.84"
version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
@ -157,24 +195,24 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.84"
version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"syn 2.0.26",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.84"
version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@ -182,22 +220,22 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.84"
version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.26",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.84"
version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
[[package]]
name = "wasm-lib"
@ -206,6 +244,7 @@ dependencies = [
"lazy_static",
"regex",
"serde",
"serde-wasm-bindgen",
"serde_json",
"wasm-bindgen",
]

View File

@ -13,3 +13,4 @@ regex = "1.7.1"
serde = {version = "1.0.152", features = ["derive"] }
serde_json = "1.0.93"
wasm-bindgen = "0.2.78"
serde-wasm-bindgen = "0.3.1"

View File

@ -0,0 +1,241 @@
//! Data types for the AST.
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Program {
pub start: usize,
pub end: usize,
pub body: Vec<BodyItem>,
pub non_code_meta: NoneCodeMeta,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(tag = "type")]
pub enum BodyItem {
ExpressionStatement(ExpressionStatement),
VariableDeclaration(VariableDeclaration),
ReturnStatement(ReturnStatement),
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(tag = "type")]
pub enum Value {
Literal(Box<Literal>),
Identifier(Box<Identifier>),
BinaryExpression(Box<BinaryExpression>),
FunctionExpression(Box<FunctionExpression>),
CallExpression(Box<CallExpression>),
PipeExpression(Box<PipeExpression>),
PipeSubstitution(Box<PipeSubstitution>),
ArrayExpression(Box<ArrayExpression>),
ObjectExpression(Box<ObjectExpression>),
MemberExpression(Box<MemberExpression>),
UnaryExpression(Box<UnaryExpression>),
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(tag = "type")]
pub enum BinaryPart {
Literal(Box<Literal>),
Identifier(Box<Identifier>),
BinaryExpression(Box<BinaryExpression>),
CallExpression(Box<CallExpression>),
UnaryExpression(Box<UnaryExpression>),
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct NoneCodeNode {
pub start: usize,
pub end: usize,
pub value: String,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NoneCodeMeta {
pub none_code_nodes: std::collections::HashMap<usize, NoneCodeNode>,
pub start: Option<NoneCodeNode>,
}
// implement Deserialize manually because we to force the keys of none_code_nodes to be usize
// and by default the ts type { [statementIndex: number]: NoneCodeNode } serializes to a string i.e. "0", "1", etc.
impl<'de> Deserialize<'de> for NoneCodeMeta {
fn deserialize<D>(deserializer: D) -> Result<NoneCodeMeta, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct NoneCodeMetaHelper {
none_code_nodes: std::collections::HashMap<String, NoneCodeNode>,
start: Option<NoneCodeNode>,
}
let helper = NoneCodeMetaHelper::deserialize(deserializer)?;
let mut none_code_nodes = std::collections::HashMap::new();
for (key, value) in helper.none_code_nodes {
none_code_nodes.insert(key.parse().unwrap(), value);
}
Ok(NoneCodeMeta {
none_code_nodes,
start: helper.start,
})
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ExpressionStatement {
pub start: usize,
pub end: usize,
pub expression: Value,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct CallExpression {
pub start: usize,
pub end: usize,
pub callee: Identifier,
pub arguments: Vec<Value>,
pub optional: bool,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct VariableDeclaration {
pub start: usize,
pub end: usize,
pub declarations: Vec<VariableDeclarator>,
pub kind: String, // Change to enum if there are specific values
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct VariableDeclarator {
pub start: usize,
pub end: usize,
pub id: Identifier,
pub init: Value,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Literal {
pub start: usize,
pub end: usize,
pub value: serde_json::Value,
pub raw: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Identifier {
pub start: usize,
pub end: usize,
pub name: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct PipeSubstitution {
pub start: usize,
pub end: usize,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ArrayExpression {
pub start: usize,
pub end: usize,
pub elements: Vec<Value>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ObjectExpression {
pub start: usize,
pub end: usize,
pub properties: Vec<ObjectProperty>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ObjectProperty {
pub start: usize,
pub end: usize,
pub key: Identifier,
pub value: Value,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(tag = "type")]
pub enum MemberObject {
MemberExpression(Box<MemberExpression>),
Identifier(Box<Identifier>),
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(tag = "type")]
pub enum MemberProperty {
Identifier(Box<Identifier>),
Literal(Box<Literal>),
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct MemberExpression {
pub start: usize,
pub end: usize,
pub object: MemberObject,
pub property: MemberProperty,
pub computed: bool,
}
#[derive(Debug)]
pub struct ObjectKeyInfo {
pub key: Box<dyn std::any::Any>,
pub index: usize,
pub computed: bool,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct BinaryExpression {
pub start: usize,
pub end: usize,
pub operator: String,
pub left: BinaryPart,
pub right: BinaryPart,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct UnaryExpression {
pub start: usize,
pub end: usize,
pub operator: String,
pub argument: BinaryPart,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PipeExpression {
pub start: usize,
pub end: usize,
pub body: Vec<Value>,
pub non_code_meta: NoneCodeMeta,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct FunctionExpression {
pub start: usize,
pub end: usize,
pub id: Option<Identifier>,
pub params: Vec<Identifier>,
pub body: BlockStatement,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BlockStatement {
pub start: usize,
pub end: usize,
pub body: Vec<BodyItem>,
pub non_code_meta: NoneCodeMeta,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ReturnStatement {
pub start: usize,
pub end: usize,
pub argument: Value,
}

View File

@ -1 +1,3 @@
mod abstract_syntax_tree;
mod recast;
mod tokeniser;

432
src/wasm-lib/src/recast.rs Normal file
View File

@ -0,0 +1,432 @@
//! Generates source code from the AST.
//! The inverse of parsing (which generates an AST from the source code)
use wasm_bindgen::prelude::*;
use crate::abstract_syntax_tree::{
ArrayExpression, BinaryExpression, BinaryPart, BodyItem, CallExpression, FunctionExpression,
Literal, MemberExpression, MemberObject, MemberProperty, ObjectExpression, PipeExpression,
Program, UnaryExpression, Value,
};
fn recast_literal(literal: Literal) -> String {
if let serde_json::Value::String(value) = literal.value {
let quote = if literal.raw.trim().starts_with('"') {
'"'
} else {
'\''
};
format!("{}{}{}", quote, value, quote)
} else {
literal.value.to_string()
}
}
fn precedence(operator: &str) -> u8 {
match operator {
"+" | "-" => 11,
"*" | "/" | "%" => 12,
_ => 0,
}
}
fn recast_binary_expression(expression: BinaryExpression) -> String {
let maybe_wrap_it = |a: String, doit: bool| -> String {
if doit {
format!("({})", a)
} else {
a
}
};
let should_wrap_right = match expression.right.clone() {
BinaryPart::BinaryExpression(bin_exp) => {
precedence(&expression.operator) > precedence(&bin_exp.operator)
|| expression.operator == "-"
}
_ => false,
};
let should_wrap_left = match expression.left.clone() {
BinaryPart::BinaryExpression(bin_exp) => {
precedence(&expression.operator) > precedence(&bin_exp.operator)
}
_ => false,
};
format!(
"{} {} {}",
maybe_wrap_it(recast_binary_part(expression.left), should_wrap_left),
expression.operator,
maybe_wrap_it(recast_binary_part(expression.right), should_wrap_right)
)
}
fn recast_binary_part(part: BinaryPart) -> String {
match part {
BinaryPart::Literal(literal) => recast_literal(*literal),
BinaryPart::Identifier(identifier) => identifier.name,
BinaryPart::BinaryExpression(binary_expression) => {
recast_binary_expression(*binary_expression)
}
BinaryPart::CallExpression(call_expression) => {
recast_call_expression(*call_expression, "".to_string(), false)
}
_ => "".to_string(),
}
}
fn recast_value(node: Value, _indentation: String, is_in_pipe_expression: bool) -> String {
let indentation = _indentation + if is_in_pipe_expression { " " } else { "" };
match node {
Value::BinaryExpression(bin_exp) => recast_binary_expression(*bin_exp),
Value::ArrayExpression(array_exp) => recast_array_expression(*array_exp, indentation),
Value::ObjectExpression(obj_exp) => {
recast_object_expression(*obj_exp, indentation, is_in_pipe_expression)
}
Value::MemberExpression(mem_exp) => recast_member_expression(*mem_exp),
Value::Literal(literal) => recast_literal(*literal),
Value::FunctionExpression(func_exp) => recast_function(*func_exp),
Value::CallExpression(call_exp) => {
recast_call_expression(*call_exp, indentation, is_in_pipe_expression)
}
Value::Identifier(ident) => ident.name,
Value::PipeExpression(pipe_exp) => recast_pipe_expression(*pipe_exp),
Value::UnaryExpression(unary_exp) => recast_unary_expression(*unary_exp),
_ => "".to_string(),
}
}
fn recast_array_expression(expression: ArrayExpression, indentation: String) -> String {
let flat_recast = format!(
"[{}]",
expression
.elements
.iter()
.map(|el| recast_value(el.clone(), "".to_string(), false))
.collect::<Vec<String>>()
.join(", ")
);
let max_array_length = 40;
if flat_recast.len() > max_array_length {
let _indentation = indentation.clone() + " ";
format!(
"[\n{}{}\n{}]",
_indentation,
expression
.elements
.iter()
.map(|el| recast_value(el.clone(), _indentation.clone(), false))
.collect::<Vec<String>>()
.join(format!(",\n{}", _indentation).as_str()),
indentation
)
} else {
flat_recast
}
}
fn recast_object_expression(
expression: ObjectExpression,
indentation: String,
is_in_pipe_expression: bool,
) -> String {
let flat_recast = format!(
"{{ {} }}",
expression
.properties
.iter()
.map(|prop| {
format!(
"{}: {}",
prop.key.name,
recast_value(prop.value.clone(), "".to_string(), false)
)
})
.collect::<Vec<String>>()
.join(", ")
);
let max_array_length = 40;
if flat_recast.len() > max_array_length {
let _indentation = indentation + " ";
format!(
"{{\n{}{}\n{}}}",
_indentation,
expression
.properties
.iter()
.map(|prop| {
format!(
"{}: {}",
prop.key.name,
recast_value(
prop.value.clone(),
_indentation.clone(),
is_in_pipe_expression
)
)
})
.collect::<Vec<String>>()
.join(format!(",\n{}", _indentation).as_str()),
if is_in_pipe_expression { " " } else { "" }
)
} else {
flat_recast
}
}
fn recast_call_expression(
expression: CallExpression,
indentation: String,
is_in_pipe_expression: bool,
) -> String {
format!(
"{}({})",
expression.callee.name,
expression
.arguments
.iter()
.map(|arg| recast_argument(arg.clone(), indentation.clone(), is_in_pipe_expression))
.collect::<Vec<String>>()
.join(", ")
)
}
fn recast_argument(argument: Value, indentation: String, is_in_pipe_expression: bool) -> String {
match argument {
Value::Literal(literal) => recast_literal(*literal),
Value::Identifier(identifier) => identifier.name,
Value::BinaryExpression(binary_exp) => recast_binary_expression(*binary_exp),
Value::ArrayExpression(array_exp) => recast_array_expression(*array_exp, indentation),
Value::ObjectExpression(object_exp) => {
recast_object_expression(*object_exp, indentation, is_in_pipe_expression)
}
Value::CallExpression(call_exp) => {
recast_call_expression(*call_exp, indentation, is_in_pipe_expression)
}
Value::FunctionExpression(function_exp) => recast_function(*function_exp),
Value::PipeSubstitution(_) => "%".to_string(),
Value::UnaryExpression(unary_exp) => recast_unary_expression(*unary_exp),
_ => "".to_string(),
}
}
fn recast_member_expression(expression: MemberExpression) -> String {
let key_str = match expression.property {
MemberProperty::Identifier(identifier) => {
if expression.computed {
format!("[{}]", &(*identifier.name))
} else {
format!(".{}", &(*identifier.name))
}
}
MemberProperty::Literal(lit) => format!("[{}]", &(*lit.raw)),
};
match expression.object {
MemberObject::MemberExpression(member_exp) => {
recast_member_expression(*member_exp) + key_str.as_str()
}
MemberObject::Identifier(identifier) => identifier.name + key_str.as_str(),
}
}
fn recast_pipe_expression(expression: PipeExpression) -> String {
expression
.body
.iter()
.enumerate()
.map(|(index, statement)| {
let mut indentation = " ".to_string();
let mut maybe_line_break = "\n".to_string();
let mut str = recast_value(statement.clone(), indentation.clone(), true);
let non_code_meta = expression.non_code_meta.clone();
if let Some(non_code_meta_value) = non_code_meta.none_code_nodes.get(&index)
{
if non_code_meta_value.value != " " {
str += non_code_meta_value.value.as_str();
indentation = "".to_string();
maybe_line_break = "".to_string();
}
}
if index != expression.body.len() - 1 {
str += maybe_line_break.as_str();
str += indentation.as_str();
str += "|> ".to_string().as_str();
}
str
})
.collect::<Vec<String>>()
.join("")
}
fn recast_unary_expression(expression: UnaryExpression) -> String {
let bin_part_val = match expression.argument {
BinaryPart::Literal(literal) => Value::Literal(literal),
BinaryPart::Identifier(identifier) => Value::Identifier(identifier),
BinaryPart::BinaryExpression(binary_expression) => {
Value::BinaryExpression(binary_expression)
}
BinaryPart::CallExpression(call_expression) => Value::CallExpression(call_expression),
BinaryPart::UnaryExpression(unary_expression) => Value::UnaryExpression(unary_expression),
};
format!(
"{}{}",
expression.operator,
recast_value(bin_part_val, "".to_string(), false)
)
}
pub fn recast(ast: Program, indentation: String, is_with_block: bool) -> String {
ast.body
.iter()
.map(|statement| match statement.clone() {
BodyItem::ExpressionStatement(expression_statement) => {
match expression_statement.expression {
Value::BinaryExpression(binary_expression) => {
recast_binary_expression(*binary_expression)
}
Value::ArrayExpression(array_expression) => {
recast_array_expression(*array_expression, "".to_string())
}
Value::ObjectExpression(object_expression) => {
recast_object_expression(*object_expression, "".to_string(), false)
}
Value::CallExpression(call_expression) => {
recast_call_expression(*call_expression, "".to_string(), false)
}
_ => "Expression".to_string(),
}
}
BodyItem::VariableDeclaration(variable_declaration) => variable_declaration
.declarations
.iter()
.map(|declaration| {
format!(
"{} {} = {}",
variable_declaration.kind,
declaration.id.name,
recast_value(declaration.init.clone(), "".to_string(), false)
)
})
.collect::<Vec<String>>()
.join(""),
BodyItem::ReturnStatement(return_statement) => {
format!(
"return {}",
recast_argument(return_statement.argument, "".to_string(), false)
)
}
})
.enumerate()
.map(|(index, recast_str)| {
let is_legit_custom_whitespace_or_comment =
|str: String| str != " " && str != "\n" && str != " ";
// determine the value of startString
let last_white_space_or_comment = if index > 0 {
let tmp = if let Some(non_code_node) = ast
.non_code_meta
.none_code_nodes
.get(&(index - 1))
{
non_code_node.value.clone()
} else {
" ".to_string()
};
tmp
} else {
" ".to_string()
};
// indentation of this line will be covered by the previous if we're using a custom whitespace or comment
let mut start_string =
if is_legit_custom_whitespace_or_comment(last_white_space_or_comment) {
"".to_string()
} else {
indentation.clone()
};
if index == 0 {
if let Some(start) = ast.non_code_meta.start.clone() {
start_string = start.value;
} else {
start_string = indentation.clone();
}
}
if start_string.ends_with('\n') {
start_string += indentation.as_str();
}
// determine the value of endString
let maybe_line_break: String = if index == ast.body.len() - 1 && !is_with_block {
"".to_string()
} else {
"\n".to_string()
};
let mut custom_white_space_or_comment =
match ast.non_code_meta.none_code_nodes.get(&index) {
Some(custom_white_space_or_comment) => {
custom_white_space_or_comment.value.clone()
}
None => "".to_string(),
};
if !is_legit_custom_whitespace_or_comment(custom_white_space_or_comment.clone()) {
custom_white_space_or_comment = "".to_string();
}
let end_string = if !custom_white_space_or_comment.is_empty() {
custom_white_space_or_comment
} else {
maybe_line_break
};
format!("{}{}{}", start_string, recast_str, end_string)
})
.collect::<Vec<String>>()
.join("")
}
pub fn recast_function(expression: FunctionExpression) -> String {
format!(
"({}) => {{{}}}",
expression
.params
.iter()
.map(|param| param.name.clone())
.collect::<Vec<String>>()
.join(", "),
recast(
Program {
start: expression.body.start,
end: expression.body.start,
body: expression.body.body,
non_code_meta: expression.body.non_code_meta
},
"".to_string(),
true
)
)
}
#[wasm_bindgen]
extern "C" {
// Use `js_namespace` here to bind `console.log(..)` instead of just
// `log(..)`
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
// wasm_bindgen wrapper for recast
// test for this function and by extension the recaster are done in javascript land src/lang/recast.test.ts
#[wasm_bindgen]
pub fn recast_js(json_str: &str) -> String {
// deserialize the ast from a stringified json
let result: Result<Program, _> = serde_json::from_str(json_str);
let ast = match result {
Ok(ast) => ast,
Err(e) => {
log(e.to_string().as_str());
panic!("error: {}", e)
}
};
recast(ast, "".to_string(), false)
}

View File

@ -1,10 +1,7 @@
extern crate lazy_static;
extern crate regex;
use wasm_bindgen::prelude::*;
use lazy_static::lazy_static;
use regex::Regex;
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
#[derive(Debug, PartialEq, Eq, Copy, Clone, Deserialize, Serialize)]
@ -37,7 +34,12 @@ pub struct Token {
impl Token {
#[wasm_bindgen(constructor)]
pub fn new(token_type: TokenType, value: String, start: usize, end: usize) -> Token {
Token { token_type, value, start, end }
Token {
token_type,
value,
start,
end,
}
}
#[wasm_bindgen(getter)]
@ -56,10 +58,11 @@ lazy_static! {
static ref WHITESPACE: Regex = Regex::new(r"\s+").unwrap();
static ref WORD: Regex = Regex::new(r"^[a-zA-Z_][a-zA-Z0-9_]*").unwrap();
static ref STRING: Regex = Regex::new(r#"^"([^"\\]|\\.)*"|'([^'\\]|\\.)*'"#).unwrap();
static ref OPERATOR: Regex = Regex::new(r"^(>=|<=|==|=>|!= |\|>|\*|\+|-|/|%|=|<|>|\||\^)").unwrap();
static ref OPERATOR: Regex =
Regex::new(r"^(>=|<=|==|=>|!= |\|>|\*|\+|-|/|%|=|<|>|\||\^)").unwrap();
static ref BLOCK_START: Regex = Regex::new(r"^\{").unwrap();
static ref BLOCK_END: Regex = Regex::new(r"^\}").unwrap();
static ref PARAN_START: Regex = Regex::new(r"^\(").unwrap();
static ref PARAN_START: Regex = Regex::new(r"^\(").unwrap();
static ref PARAN_END: Regex = Regex::new(r"^\)").unwrap();
static ref ARRAY_START: Regex = Regex::new(r"^\[").unwrap();
static ref ARRAY_END: Regex = Regex::new(r"^\]").unwrap();
@ -137,7 +140,6 @@ fn make_token(token_type: TokenType, value: &str, start: usize) -> Token {
}
}
fn return_token_at_index(str: &str, start_index: usize) -> Option<Token> {
let str_from_index = &str[start_index..];
if is_string(str_from_index) {
@ -261,13 +263,17 @@ fn return_token_at_index(str: &str, start_index: usize) -> Option<Token> {
}
fn lexer(str: &str) -> Vec<Token> {
fn recursively_tokenise(str: &str, current_index: usize, previous_tokens: Vec<Token>) -> Vec<Token> {
fn recursively_tokenise(
str: &str,
current_index: usize,
previous_tokens: Vec<Token>,
) -> Vec<Token> {
if current_index >= str.len() {
return previous_tokens;
}
let token = return_token_at_index(str, current_index);
if token.is_none() {
return recursively_tokenise(str, current_index + 1, previous_tokens)
return recursively_tokenise(str, current_index + 1, previous_tokens);
}
let token = token.unwrap();
let mut new_tokens = previous_tokens;
@ -283,10 +289,7 @@ fn lexer(str: &str) -> Vec<Token> {
#[wasm_bindgen]
pub fn lexer_js(str: &str) -> JsValue {
let tokens = lexer(str);
JsValue::from_str(
&serde_json::to_string(&tokens)
.expect("failed to serialize lexer output"),
)
JsValue::from_str(&serde_json::to_string(&tokens).expect("failed to serialize lexer output"))
}
#[cfg(test)]
@ -295,210 +298,215 @@ mod tests {
#[test]
fn is_number_test() {
assert_eq!(is_number("1"), true);
assert_eq!(is_number("1 abc"), true);
assert_eq!(is_number("1abc"), true);
assert_eq!(is_number("1.1"), true);
assert_eq!(is_number("1.1 abc"), true);
assert_eq!(is_number("a"), false);
assert!(is_number("1"));
assert!(is_number("1 abc"));
assert!(is_number("1abc"));
assert!(is_number("1.1"));
assert!(is_number("1.1 abc"));
assert!(!is_number("a"));
assert_eq!(is_number("1"), true);
assert_eq!(is_number("5?"), true);
assert_eq!(is_number("5 + 6"), true);
assert_eq!(is_number("5 + a"), true);
assert_eq!(is_number("-5"), true);
assert_eq!(is_number("5.5"), true);
assert_eq!(is_number("-5.5"), true);
assert_eq!(is_number("a"), false);
assert_eq!(is_number("?"), false);
assert_eq!(is_number("?5"), false);
assert!(is_number("1"));
assert!(is_number("5?"));
assert!(is_number("5 + 6"));
assert!(is_number("5 + a"));
assert!(is_number("-5"));
assert!(is_number("5.5"));
assert!(is_number("-5.5"));
assert!(!is_number("a"));
assert!(!is_number("?"));
assert!(!is_number("?5"));
}
#[test]
fn is_whitespace_test() {
assert_eq!(is_whitespace(" "), true);
assert_eq!(is_whitespace(" "), true);
assert_eq!(is_whitespace(" a"), true);
assert_eq!(is_whitespace("a "), true);
assert!(is_whitespace(" "));
assert!(is_whitespace(" "));
assert!(is_whitespace(" a"));
assert!(is_whitespace("a "));
assert_eq!(is_whitespace("a"), false);
assert_eq!(is_whitespace("?"), false);
assert!(!is_whitespace("a"));
assert!(!is_whitespace("?"));
}
#[test]
fn is_word_test() {
assert_eq!(is_word("a"), true);
assert_eq!(is_word("a "), true);
assert_eq!(is_word("a5"), true);
assert_eq!(is_word("a5a"), true);
assert!(is_word("a"));
assert!(is_word("a "));
assert!(is_word("a5"));
assert!(is_word("a5a"));
assert_eq!(is_word("5"), false);
assert_eq!(is_word("5a"), false);
assert_eq!(is_word("5a5"), false);
assert!(!is_word("5"));
assert!(!is_word("5a"));
assert!(!is_word("5a5"));
}
#[test]
fn is_string_test() {
assert_eq!(is_string("\"\""), true);
assert_eq!(is_string("\"a\""), true);
assert_eq!(is_string("\"a\" "), true);
assert_eq!(is_string("\"a\"5"), true);
assert_eq!(is_string("'a'5"), true);
assert_eq!(is_string("\"with escaped \\\" backslash\""), true);
assert!(is_string("\"\""));
assert!(is_string("\"a\""));
assert!(is_string("\"a\" "));
assert!(is_string("\"a\"5"));
assert!(is_string("'a'5"));
assert!(is_string("\"with escaped \\\" backslash\""));
assert_eq!(is_string("\""), false);
assert_eq!(is_string("\"a"), false);
assert_eq!(is_string("a\""), false);
assert_eq!(is_string(" \"a\""), false);
assert_eq!(is_string("5\"a\""), false);
assert_eq!(is_string("a + 'str'"), false);
assert!(!is_string("\""));
assert!(!is_string("\"a"));
assert!(!is_string("a\""));
assert!(!is_string(" \"a\""));
assert!(!is_string("5\"a\""));
assert!(!is_string("a + 'str'"));
}
#[test]
fn is_operator_test() {
assert_eq!(is_operator("+"), true);
assert_eq!(is_operator("+ "), true);
assert_eq!(is_operator("-"), true);
assert_eq!(is_operator("<="), true);
assert_eq!(is_operator("<= "), true);
assert_eq!(is_operator(">="), true);
assert_eq!(is_operator(">= "), true);
assert_eq!(is_operator("> "), true);
assert_eq!(is_operator("< "), true);
assert_eq!(is_operator("| "), true);
assert_eq!(is_operator("|> "), true);
assert_eq!(is_operator("^ "), true);
assert_eq!(is_operator("% "), true);
assert_eq!(is_operator("+* "), true);
assert!(is_operator("+"));
assert!(is_operator("+ "));
assert!(is_operator("-"));
assert!(is_operator("<="));
assert!(is_operator("<= "));
assert!(is_operator(">="));
assert!(is_operator(">= "));
assert!(is_operator("> "));
assert!(is_operator("< "));
assert!(is_operator("| "));
assert!(is_operator("|> "));
assert!(is_operator("^ "));
assert!(is_operator("% "));
assert!(is_operator("+* "));
assert_eq!(is_operator("5 + 5"), false);
assert_eq!(is_operator("a"), false);
assert_eq!(is_operator("a+"), false);
assert_eq!(is_operator("a+5"), false);
assert_eq!(is_operator("5a+5"), false);
assert_eq!(is_operator(", newVar"), false);
assert_eq!(is_operator(","), false);
assert!(!is_operator("5 + 5"));
assert!(!is_operator("a"));
assert!(!is_operator("a+"));
assert!(!is_operator("a+5"));
assert!(!is_operator("5a+5"));
assert!(!is_operator(", newVar"));
assert!(!is_operator(","));
}
#[test]
fn is_block_start_test() {
assert_eq!(is_block_start("{"), true);
assert_eq!(is_block_start("{ "), true);
assert_eq!(is_block_start("{5"), true);
assert_eq!(is_block_start("{a"), true);
assert_eq!(is_block_start("{5 "), true);
assert!(is_block_start("{"));
assert!(is_block_start("{ "));
assert!(is_block_start("{5"));
assert!(is_block_start("{a"));
assert!(is_block_start("{5 "));
assert_eq!(is_block_start("5"), false);
assert_eq!(is_block_start("5 + 5"), false);
assert_eq!(is_block_start("5{ + 5"), false);
assert_eq!(is_block_start("a{ + 5"), false);
assert_eq!(is_block_start(" { + 5"), false);
assert!(!is_block_start("5"));
assert!(!is_block_start("5 + 5"));
assert!(!is_block_start("5{ + 5"));
assert!(!is_block_start("a{ + 5"));
assert!(!is_block_start(" { + 5"));
}
#[test]
fn is_block_end_test() {
assert_eq!(is_block_end("}"), true);
assert_eq!(is_block_end("} "), true);
assert_eq!(is_block_end("}5"), true);
assert_eq!(is_block_end("}5 "), true);
assert!(is_block_end("}"));
assert!(is_block_end("} "));
assert!(is_block_end("}5"));
assert!(is_block_end("}5 "));
assert_eq!(is_block_end("5"), false);
assert_eq!(is_block_end("5 + 5"), false);
assert_eq!(is_block_end("5} + 5"), false);
assert_eq!(is_block_end(" } + 5"), false);
assert!(!is_block_end("5"));
assert!(!is_block_end("5 + 5"));
assert!(!is_block_end("5} + 5"));
assert!(!is_block_end(" } + 5"));
}
#[test]
fn is_paran_start_test() {
assert_eq!(is_paran_start("("), true);
assert_eq!(is_paran_start("( "), true);
assert_eq!(is_paran_start("(5"), true);
assert_eq!(is_paran_start("(5 "), true);
assert_eq!(is_paran_start("(5 + 5"), true);
assert_eq!(is_paran_start("(5 + 5)"), true);
assert_eq!(is_paran_start("(5 + 5) "), true);
assert!(is_paran_start("("));
assert!(is_paran_start("( "));
assert!(is_paran_start("(5"));
assert!(is_paran_start("(5 "));
assert!(is_paran_start("(5 + 5"));
assert!(is_paran_start("(5 + 5)"));
assert!(is_paran_start("(5 + 5) "));
assert_eq!(is_paran_start("5"), false);
assert_eq!(is_paran_start("5 + 5"), false);
assert_eq!(is_paran_start("5( + 5)"), false);
assert_eq!(is_paran_start(" ( + 5)"), false);
assert!(!is_paran_start("5"));
assert!(!is_paran_start("5 + 5"));
assert!(!is_paran_start("5( + 5)"));
assert!(!is_paran_start(" ( + 5)"));
}
#[test]
fn is_paran_end_test() {
assert_eq!(is_paran_end(")"), true);
assert_eq!(is_paran_end(") "), true);
assert_eq!(is_paran_end(")5"), true);
assert_eq!(is_paran_end(")5 "), true);
assert!(is_paran_end(")"));
assert!(is_paran_end(") "));
assert!(is_paran_end(")5"));
assert!(is_paran_end(")5 "));
assert_eq!(is_paran_end("5"), false);
assert_eq!(is_paran_end("5 + 5"), false);
assert_eq!(is_paran_end("5) + 5"), false);
assert_eq!(is_paran_end(" ) + 5"), false);
assert!(!is_paran_end("5"));
assert!(!is_paran_end("5 + 5"));
assert!(!is_paran_end("5) + 5"));
assert!(!is_paran_end(" ) + 5"));
}
#[test]
fn is_comma_test() {
assert_eq!(is_comma(","), true);
assert_eq!(is_comma(", "), true);
assert_eq!(is_comma(",5"), true);
assert_eq!(is_comma(",5 "), true);
assert!(is_comma(","));
assert!(is_comma(", "));
assert!(is_comma(",5"));
assert!(is_comma(",5 "));
assert_eq!(is_comma("5"), false);
assert_eq!(is_comma("5 + 5"), false);
assert_eq!(is_comma("5, + 5"), false);
assert_eq!(is_comma(" , + 5"), false);
assert!(!is_comma("5"));
assert!(!is_comma("5 + 5"));
assert!(!is_comma("5, + 5"));
assert!(!is_comma(" , + 5"));
}
#[test]
fn is_line_comment_test() {
assert_eq!(is_line_comment("//"), true);
assert_eq!(is_line_comment("// "), true);
assert_eq!(is_line_comment("//5"), true);
assert_eq!(is_line_comment("//5 "), true);
assert!(is_line_comment("//"));
assert!(is_line_comment("// "));
assert!(is_line_comment("//5"));
assert!(is_line_comment("//5 "));
assert_eq!(is_line_comment("5"), false);
assert_eq!(is_line_comment("5 + 5"), false);
assert_eq!(is_line_comment("5// + 5"), false);
assert_eq!(is_line_comment(" // + 5"), false);
assert!(!is_line_comment("5"));
assert!(!is_line_comment("5 + 5"));
assert!(!is_line_comment("5// + 5"));
assert!(!is_line_comment(" // + 5"));
}
#[test]
fn is_block_comment_test() {
assert_eq!(is_block_comment("/* */"), true);
assert_eq!(is_block_comment("/***/"), true);
assert_eq!(is_block_comment("/*5*/"), true);
assert_eq!(is_block_comment("/*5 */"), true);
assert!(is_block_comment("/* */"));
assert!(is_block_comment("/***/"));
assert!(is_block_comment("/*5*/"));
assert!(is_block_comment("/*5 */"));
assert_eq!(is_block_comment("/*"), false);
assert_eq!(is_block_comment("5"), false);
assert_eq!(is_block_comment("5 + 5"), false);
assert_eq!(is_block_comment("5/* + 5"), false);
assert_eq!(is_block_comment(" /* + 5"), false);
assert!(!is_block_comment("/*"));
assert!(!is_block_comment("5"));
assert!(!is_block_comment("5 + 5"));
assert!(!is_block_comment("5/* + 5"));
assert!(!is_block_comment(" /* + 5"));
}
#[test]
fn make_token_test() {
assert_eq!(make_token(TokenType::Word, &"const".to_string(), 56), Token {
token_type: TokenType::Word,
value: "const".to_string(),
start: 56,
end: 61,
});
assert_eq!(
make_token(TokenType::Word, "const", 56),
Token {
token_type: TokenType::Word,
value: "const".to_string(),
start: 56,
end: 61,
}
);
}
#[test]
fn return_token_at_index_test() {
assert_eq!(return_token_at_index("const", 0), Some(Token {
token_type: TokenType::Word,
value: "const".to_string(),
start: 0,
end: 5,
}));
assert_eq!(return_token_at_index(" 4554", 2),
assert_eq!(
return_token_at_index("const", 0),
Some(Token {
token_type: TokenType::Word,
value: "const".to_string(),
start: 0,
end: 5,
})
);
assert_eq!(
return_token_at_index(" 4554", 2),
Some(Token {
token_type: TokenType::Number,
value: "4554".to_string(),
@ -510,93 +518,99 @@ mod tests {
#[test]
fn lexer_test() {
assert_eq!(lexer("const a=5"), vec![
Token {
token_type: TokenType::Word,
value: "const".to_string(),
start: 0,
end: 5,
},
Token {
token_type: TokenType::Whitespace,
value: " ".to_string(),
start: 5,
end: 6,
},
Token {
token_type: TokenType::Word,
value: "a".to_string(),
start: 6,
end: 7,
},
Token {
token_type: TokenType::Operator,
value: "=".to_string(),
start: 7,
end: 8,
},
Token {
token_type: TokenType::Number,
value: "5".to_string(),
start: 8,
end: 9,
},
]);
assert_eq!(lexer("54 + 22500 + 6"), vec![
Token {
token_type: TokenType::Number,
value: "54".to_string(),
start: 0,
end: 2,
},
Token {
token_type: TokenType::Whitespace,
value: " ".to_string(),
start: 2,
end: 3,
},
Token {
token_type: TokenType::Operator,
value: "+".to_string(),
start: 3,
end: 4,
},
Token {
token_type: TokenType::Whitespace,
value: " ".to_string(),
start: 4,
end: 5,
},
Token {
token_type: TokenType::Number,
value: "22500".to_string(),
start: 5,
end: 10,
},
Token {
token_type: TokenType::Whitespace,
value: " ".to_string(),
start: 10,
end: 11,
},
Token {
token_type: TokenType::Operator,
value: "+".to_string(),
start: 11,
end: 12,
},
Token {
token_type: TokenType::Whitespace,
value: " ".to_string(),
start: 12,
end: 13,
},
Token {
token_type: TokenType::Number,
value: "6".to_string(),
start: 13,
end: 14,
},
]);
assert_eq!(
lexer("const a=5"),
vec![
Token {
token_type: TokenType::Word,
value: "const".to_string(),
start: 0,
end: 5,
},
Token {
token_type: TokenType::Whitespace,
value: " ".to_string(),
start: 5,
end: 6,
},
Token {
token_type: TokenType::Word,
value: "a".to_string(),
start: 6,
end: 7,
},
Token {
token_type: TokenType::Operator,
value: "=".to_string(),
start: 7,
end: 8,
},
Token {
token_type: TokenType::Number,
value: "5".to_string(),
start: 8,
end: 9,
},
]
);
assert_eq!(
lexer("54 + 22500 + 6"),
vec![
Token {
token_type: TokenType::Number,
value: "54".to_string(),
start: 0,
end: 2,
},
Token {
token_type: TokenType::Whitespace,
value: " ".to_string(),
start: 2,
end: 3,
},
Token {
token_type: TokenType::Operator,
value: "+".to_string(),
start: 3,
end: 4,
},
Token {
token_type: TokenType::Whitespace,
value: " ".to_string(),
start: 4,
end: 5,
},
Token {
token_type: TokenType::Number,
value: "22500".to_string(),
start: 5,
end: 10,
},
Token {
token_type: TokenType::Whitespace,
value: " ".to_string(),
start: 10,
end: 11,
},
Token {
token_type: TokenType::Operator,
value: "+".to_string(),
start: 11,
end: 12,
},
Token {
token_type: TokenType::Whitespace,
value: " ".to_string(),
start: 12,
end: 13,
},
Token {
token_type: TokenType::Number,
value: "6".to_string(),
start: 13,
end: 14,
},
]
);
}
}