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:
		@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -108,7 +108,7 @@ describe('Testing addSketchTo', () => {
 | 
			
		||||
        body: [],
 | 
			
		||||
        start: 0,
 | 
			
		||||
        end: 0,
 | 
			
		||||
        nonCodeMeta: {},
 | 
			
		||||
        nonCodeMeta: { noneCodeNodes: {} },
 | 
			
		||||
      },
 | 
			
		||||
      'yz'
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
@ -452,7 +452,7 @@ export function createPipeExpression(
 | 
			
		||||
    start: 0,
 | 
			
		||||
    end: 0,
 | 
			
		||||
    body,
 | 
			
		||||
    nonCodeMeta: {},
 | 
			
		||||
    nonCodeMeta: { noneCodeNodes: {} },
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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))
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										73
									
								
								src/wasm-lib/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										73
									
								
								src/wasm-lib/Cargo.lock
									
									
									
										generated
									
									
									
								
							@ -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",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
@ -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"
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										241
									
								
								src/wasm-lib/src/abstract_syntax_tree.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										241
									
								
								src/wasm-lib/src/abstract_syntax_tree.rs
									
									
									
									
									
										Normal 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,
 | 
			
		||||
}
 | 
			
		||||
@ -1 +1,3 @@
 | 
			
		||||
mod abstract_syntax_tree;
 | 
			
		||||
mod recast;
 | 
			
		||||
mod tokeniser;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										432
									
								
								src/wasm-lib/src/recast.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										432
									
								
								src/wasm-lib/src/recast.rs
									
									
									
									
									
										Normal 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)
 | 
			
		||||
}
 | 
			
		||||
@ -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,
 | 
			
		||||
                },
 | 
			
		||||
            ]
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user