├── .babelrc ├── .gitignore ├── .prettierrc ├── README.md ├── bin └── dropbear ├── examples └── example.dbr ├── package-lock.json ├── package.json ├── src ├── evaluate.js ├── identify.js ├── index.js ├── parse-and-evaluate.js ├── parse-program.js ├── parse.js ├── repl.js ├── special-forms.js ├── standard-library.js ├── to-javascript.js ├── tokenize.js ├── transform.js ├── traverse.js └── utilities.js └── tests ├── evaluate.test.js ├── parse-program.test.js ├── parse.test.js ├── special-forms.test.js ├── to-javascript.test.js ├── tokenize.test.js ├── transform.test.js └── traverse.test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["@babel/plugin-transform-modules-commonjs", "@babel/plugin-proposal-export-default-from"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | *.log 4 | *.un~ 5 | *.swp 6 | report.* 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "tabWidth": 2, 4 | "singleQuote": true 5 | } 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Welcome to the [Building Your Own Programming Language](https://frontendmasters.com/courses/programming-language/) course! 2 | 3 | ## Getting started 4 | 5 | Before starting the course, please have [VSCode](https://code.visualstudio.com/) or another code editor installed. Make sure to install the testing framework [jest](https://jestjs.io/docs/en/getting-started). 6 | 7 | `yarn add --dev jest` 8 | 9 | OR 10 | 11 | `npm install --save-dev jest` 12 | 13 | Each branch covers different concepts discussed during the course. 14 | Branches [4](https://github.com/stevekinney/dropbear/tree/4-parsing-solutions) and [6](https://github.com/stevekinney/dropbear/tree/6-max-method-in-environment-solution) have solutions to exercises covered in previous branches. 15 | the final version of the project is on branch [10-finalversion](https://github.com/stevekinney/dropbear/tree/10-finalversion). 16 | 17 | This course was written and recorded by Frontend Masters. The code here is licensed under the Apache 2.0 license and the course notes are licensed under the Creative Commons Attribution-NonCommercial 4.0 International license. 18 | 19 | ## See a Bug or Typo? 20 | 21 | Pull requests are welcome! 22 | -------------------------------------------------------------------------------- /bin/dropbear: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { parseAndEvaluate } = require('../src'); 4 | 5 | const fs = require('fs'); 6 | 7 | const [command, ...args] = process.argv.slice(2); 8 | 9 | if (!command) { 10 | const repl = require('../src/repl'); 11 | return repl(); 12 | } 13 | 14 | if (command.toLowerCase() === 'run') { 15 | fs.readFile(args[0], 'utf-8', (error, file) => { 16 | if (error) { 17 | console.error(error); 18 | } 19 | const result = parseAndEvaluate(file); 20 | console.log(result); 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /examples/example.dbr: -------------------------------------------------------------------------------- 1 | (multiply (add 2 2 (subtract 4 2))) 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dropbear", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "index.js", 6 | "bin": { 7 | "dropbear": "bin/dropbear" 8 | }, 9 | "scripts": { 10 | "start": "node src/repl.js", 11 | "test": "jest" 12 | }, 13 | "keywords": [], 14 | "author": "Steve Kinney (https://stevekinney.net)", 15 | "license": "MIT", 16 | "dependencies": { 17 | "@babel/core": "^7.4.5", 18 | "@babel/generator": "^7.4.4", 19 | "@babel/node": "^7.4.5", 20 | "@babel/plugin-proposal-export-default-from": "^7.2.0", 21 | "@babel/plugin-transform-modules-commonjs": "^7.4.4", 22 | "chalk": "^2.4.2", 23 | "inquirer": "^6.3.1", 24 | "lodash": "^4.17.11", 25 | "minimist": "^1.2.0" 26 | }, 27 | "devDependencies": { 28 | "babel-jest": "^24.8.0", 29 | "jest": "^24.8.0", 30 | "mock-fs": "^4.10.0" 31 | }, 32 | "jest": { 33 | "transform": { 34 | "^.+\\.jsx?$": "babel-jest" 35 | }, 36 | "testRegex": "^.+\\.test.jsx?$", 37 | "moduleFileExtensions": [ 38 | "js", 39 | "jsx", 40 | "json", 41 | "node" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/evaluate.js: -------------------------------------------------------------------------------- 1 | const { environment } = require('./standard-library'); 2 | const last = collection => collection[collection.length - 1]; 3 | 4 | const evaluate = () => {}; 5 | 6 | module.exports = { evaluate }; 7 | -------------------------------------------------------------------------------- /src/identify.js: -------------------------------------------------------------------------------- 1 | const LETTER = /[a-zA-Z]/; 2 | const WHITESPACE = /\s+/; 3 | const NUMBER = /^[0-9]+$/; 4 | const OPERATORS = ['+', '-', '*', '/', '%']; 5 | 6 | const isLetter = character => LETTER.test(character); 7 | 8 | const isWhitespace = character => WHITESPACE.test(character); 9 | 10 | const isNumber = character => NUMBER.test(character); 11 | 12 | const isOpeningParenthesis = character => character === '('; 13 | 14 | const isClosingParenthesis = character => character === ')'; 15 | 16 | const isParenthesis = character => 17 | isOpeningParenthesis(character) || isClosingParenthesis(character); 18 | 19 | const isQuote = character => character === '"'; 20 | 21 | const isOperator = character => OPERATORS.includes(character); 22 | 23 | module.exports = { 24 | isLetter, 25 | isWhitespace, 26 | isNumber, 27 | isOpeningParenthesis, 28 | isClosingParenthesis, 29 | isParenthesis, 30 | isQuote, 31 | isOperator, 32 | }; 33 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const { tokenize } = require('./tokenize'); 2 | const { parse } = require('./parse'); 3 | const { evaluate } = require('./evaluate'); 4 | const { parseAndEvaluate } = require('./parse-and-evaluate'); 5 | 6 | module.exports = { 7 | tokenize, 8 | parse, 9 | evaluate, 10 | parseAndEvaluate, 11 | }; 12 | -------------------------------------------------------------------------------- /src/parse-and-evaluate.js: -------------------------------------------------------------------------------- 1 | const { tokenize } = require('./tokenize'); 2 | const { parse } = require('./parse'); 3 | const { evaluate } = require('./evaluate'); 4 | const { log, pipe } = require('./utilities'); 5 | const { parseProgram } = require('./parse-program'); 6 | const { transform } = require('./transform'); 7 | 8 | const parseAndEvaluate = pipe( 9 | tokenize, 10 | parse, 11 | transform, 12 | evaluate, 13 | ); 14 | 15 | const tokenizeAndParse = pipe( 16 | tokenize, 17 | parse, 18 | ); 19 | 20 | const parseAndEvaluateProgram = pipe( 21 | tokenize, 22 | parseProgram, 23 | evaluate, 24 | ); 25 | 26 | module.exports = { 27 | parseAndEvaluate, 28 | tokenizeAndParse, 29 | parseAndEvaluateProgram, 30 | }; 31 | -------------------------------------------------------------------------------- /src/parse-program.js: -------------------------------------------------------------------------------- 1 | const { parse } = require('./parse'); 2 | 3 | const parseProgram = () => {}; 4 | 5 | module.exports = { parseProgram }; 6 | -------------------------------------------------------------------------------- /src/parse.js: -------------------------------------------------------------------------------- 1 | const { isOpeningParenthesis, isClosingParenthesis } = require('./identify'); 2 | const { specialForms } = require('./special-forms'); 3 | const { peek, pop } = require('./utilities'); 4 | 5 | const parenthesize = () => {}; 6 | 7 | const parse = () => {}; 8 | 9 | module.exports = { parse: tokens => parse(parenthesize(tokens)) }; 10 | -------------------------------------------------------------------------------- /src/repl.js: -------------------------------------------------------------------------------- 1 | const { prompt } = require('inquirer'); 2 | const chalk = require('chalk'); 3 | 4 | const { parseAndEvaluate } = require('./parse-and-evaluate'); 5 | 6 | const repl = async () => {}; 7 | 8 | if (require.main === module) { 9 | console.log( 10 | chalk.red( 11 | `Welcome to the ${chalk.bgYellow('Dropbear')} Programming Language`, 12 | ), 13 | ); 14 | repl(); 15 | } 16 | 17 | module.exports = repl; 18 | -------------------------------------------------------------------------------- /src/special-forms.js: -------------------------------------------------------------------------------- 1 | const specialForms = {}; 2 | 3 | module.exports = { specialForms }; 4 | -------------------------------------------------------------------------------- /src/standard-library.js: -------------------------------------------------------------------------------- 1 | const all = fn => (...list) => list.reduce(fn); 2 | 3 | const add = all((a, b) => a + b); 4 | const subtract = all((a, b) => a - b); 5 | const multiply = all((a, b) => a * b); 6 | const divide = all((a, b) => a / b); 7 | const modulo = all((a, b) => a % b); 8 | const log = console.log; 9 | 10 | const environment = { 11 | add, 12 | subtract, 13 | multiply, 14 | divide, 15 | modulo, 16 | log, 17 | pi: Math.PI, 18 | }; 19 | 20 | module.exports = { environment }; 21 | -------------------------------------------------------------------------------- /src/to-javascript.js: -------------------------------------------------------------------------------- 1 | const generate = require('@babel/generator').default; 2 | const { traverse } = require('./traverse'); 3 | 4 | const babelVisitor = {}; 5 | 6 | const toJavaScript = ast => {}; 7 | 8 | module.exports = { 9 | toJavaScript, 10 | }; 11 | -------------------------------------------------------------------------------- /src/tokenize.js: -------------------------------------------------------------------------------- 1 | const { 2 | isLetter, 3 | isWhitespace, 4 | isNumber, 5 | isParenthesis, 6 | isQuote, 7 | } = require('./identify'); 8 | 9 | const tokenize = () => {}; 10 | 11 | module.exports = { tokenize }; 12 | -------------------------------------------------------------------------------- /src/transform.js: -------------------------------------------------------------------------------- 1 | const { traverse } = require('./traverse'); 2 | 3 | const transform = node => {}; 4 | 5 | const specialForms = {}; 6 | 7 | module.exports = { specialForms, transform }; 8 | -------------------------------------------------------------------------------- /src/traverse.js: -------------------------------------------------------------------------------- 1 | const traverse = (node, visitor) => {}; 2 | 3 | module.exports = { traverse }; 4 | -------------------------------------------------------------------------------- /src/utilities.js: -------------------------------------------------------------------------------- 1 | const tap = require('lodash/tap'); 2 | 3 | const pipe = (...funcs) => value => 4 | funcs.reduce((value, func) => func(value), value); 5 | 6 | const log = value => tap(value, console.log); 7 | 8 | const peek = array => array[0]; 9 | const pop = array => array.shift(); 10 | 11 | module.exports = { 12 | pipe, 13 | log, 14 | peek, 15 | pop, 16 | tap, 17 | }; 18 | -------------------------------------------------------------------------------- /tests/evaluate.test.js: -------------------------------------------------------------------------------- 1 | import { evaluate } from '../src/evaluate'; 2 | 3 | describe(evaluate, () => { 4 | it.skip('should fall back to returning a primitive numeric value', () => { 5 | const ast = { type: 'NumericLiteral', value: 2 }; 6 | 7 | expect(evaluate(ast)).toBe(2); 8 | }); 9 | 10 | it.skip('should fall back to returning a primitive string value', () => { 11 | const ast = { type: 'StringValue', value: 'Hello' }; 12 | 13 | expect(evaluate(ast)).toBe('Hello'); 14 | }); 15 | 16 | it.skip('should be able to evaluate a single expression', () => { 17 | const ast = { 18 | type: 'CallExpression', 19 | name: 'add', 20 | arguments: [ 21 | { type: 'NumericLiteral', value: 2 }, 22 | { type: 'NumericLiteral', value: 3 }, 23 | ], 24 | }; 25 | 26 | const result = evaluate(ast); 27 | 28 | expect(result).toBe(5); 29 | }); 30 | 31 | it.skip('should be able to evaluate a nested expression', () => { 32 | const ast = { 33 | type: 'CallExpression', 34 | name: 'add', 35 | arguments: [ 36 | { type: 'NumericLiteral', value: 2 }, 37 | { type: 'NumericLiteral', value: 3 }, 38 | { 39 | type: 'CallExpression', 40 | name: 'subtract', 41 | arguments: [ 42 | { type: 'NumericLiteral', value: 5 }, 43 | { type: 'NumericLiteral', value: 4 }, 44 | ], 45 | }, 46 | ], 47 | }; 48 | 49 | const result = evaluate(ast); 50 | 51 | expect(result).toBe(6); 52 | }); 53 | 54 | it.skip('should be able to lookup identifiers in the environment', () => { 55 | const ast = { type: 'Identifier', name: 'pi' }; 56 | expect(evaluate(ast)).toBe(Math.PI); 57 | }); 58 | 59 | it.skip('should be able to determine the highest number in a range', () => { 60 | const ast = { 61 | type: 'CallExpression', 62 | name: 'max', 63 | arguments: [ 64 | { type: 'NumericLiteral', value: 2 }, 65 | { type: 'NumericLiteral', value: 3 }, 66 | { type: 'NumericLiteral', value: 10 }, 67 | ], 68 | }; 69 | 70 | expect(evaluate(ast)).toBe(10); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /tests/parse-program.test.js: -------------------------------------------------------------------------------- 1 | const { parseProgram } = require('../src/parse-program'); 2 | 3 | describe(parseProgram, () => { 4 | it.skip('should return a program node', () => { 5 | const tokens = [ 6 | { type: 'Parenthesis', value: '(' }, 7 | { type: 'Name', value: 'define' }, 8 | { type: 'Name', value: 'x' }, 9 | { type: 'Number', value: 7 }, 10 | { type: 'Parenthesis', value: ')' }, 11 | { type: 'Parenthesis', value: '(' }, 12 | { type: 'Name', value: 'add' }, 13 | { type: 'Name', value: 'x' }, 14 | { type: 'Name', value: 'x' }, 15 | { type: 'Parenthesis', value: ')' }, 16 | ]; 17 | 18 | expect(parseProgram(tokens).type).toBe('Program'); 19 | }); 20 | 21 | it.skip('should have an array of expressions', () => { 22 | const tokens = [ 23 | { type: 'Parenthesis', value: '(' }, 24 | { type: 'Name', value: 'define' }, 25 | { type: 'Name', value: 'x' }, 26 | { type: 'Number', value: 7 }, 27 | { type: 'Parenthesis', value: ')' }, 28 | { type: 'Parenthesis', value: '(' }, 29 | { type: 'Name', value: 'add' }, 30 | { type: 'Name', value: 'x' }, 31 | { type: 'Name', value: 'x' }, 32 | { type: 'Parenthesis', value: ')' }, 33 | ]; 34 | 35 | const program = parseProgram(tokens); 36 | 37 | expect(Array.isArray(program.body)).toBe(true); 38 | expect(program.body.length).toBe(2); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /tests/parse.test.js: -------------------------------------------------------------------------------- 1 | import { parse } from '../src/parse'; 2 | 3 | describe(parse, () => { 4 | it.skip('should return a token with the type of NumericLiteral for number tokens', () => { 5 | const tokens = [{ type: 'Number', value: 2 }]; 6 | 7 | const ast = { type: 'NumericLiteral', value: 2 }; 8 | 9 | expect(parse(tokens)).toEqual(ast); 10 | }); 11 | 12 | // Exercise 3 Begin 13 | it.skip('should return a token with the type of StringLiteral for string tokens', () => { 14 | const tokens = [{ type: 'String', value: 'hello' }]; 15 | 16 | const ast = { type: 'StringLiteral', value: 'hello' }; 17 | 18 | expect(parse(tokens)).toEqual(ast); 19 | }); 20 | 21 | it.skip('should return a token with the type of Identifier for name tokens', () => { 22 | const tokens = [{ type: 'Name', value: 'x' }]; 23 | 24 | const ast = { type: 'Identifier', name: 'x' }; 25 | 26 | expect(parse(tokens)).toEqual(ast); 27 | }); 28 | // Exercise 3 End 29 | 30 | it.skip('should return an AST for a basic data structure', () => { 31 | const tokens = [ 32 | { type: 'Parenthesis', value: '(' }, 33 | { type: 'Name', value: 'add' }, 34 | { type: 'Number', value: 2 }, 35 | { type: 'Number', value: 3 }, 36 | { type: 'Parenthesis', value: ')' }, 37 | ]; 38 | 39 | const ast = { 40 | type: 'CallExpression', 41 | name: 'add', 42 | arguments: [ 43 | { type: 'NumericLiteral', value: 2 }, 44 | { type: 'NumericLiteral', value: 3 }, 45 | ], 46 | }; 47 | 48 | const result = parse(tokens); 49 | 50 | expect(result).toEqual(ast); 51 | }); 52 | 53 | it.skip('should return an AST for a nested data structure', () => { 54 | const tokens = [ 55 | { type: 'Parenthesis', value: '(' }, 56 | { type: 'Name', value: 'add' }, 57 | { type: 'Number', value: 2 }, 58 | { type: 'Number', value: 3 }, 59 | { type: 'Parenthesis', value: '(' }, 60 | { type: 'Name', value: 'subtract' }, 61 | { type: 'Number', value: 4 }, 62 | { type: 'Number', value: 2 }, 63 | { type: 'Parenthesis', value: ')' }, 64 | { type: 'Parenthesis', value: ')' }, 65 | ]; 66 | 67 | const ast = { 68 | type: 'CallExpression', 69 | name: 'add', 70 | arguments: [ 71 | { type: 'NumericLiteral', value: 2 }, 72 | { type: 'NumericLiteral', value: 3 }, 73 | { 74 | type: 'CallExpression', 75 | name: 'subtract', 76 | arguments: [ 77 | { type: 'NumericLiteral', value: 4 }, 78 | { type: 'NumericLiteral', value: 2 }, 79 | ], 80 | }, 81 | ], 82 | }; 83 | 84 | const result = parse(tokens); 85 | 86 | expect(result).toEqual(ast); 87 | }); 88 | }); 89 | -------------------------------------------------------------------------------- /tests/special-forms.test.js: -------------------------------------------------------------------------------- 1 | const { specialForms } = require('../src/special-forms'); 2 | 3 | describe('specialForms', () => { 4 | describe('define', () => { 5 | it.skip('should transform a call expression into a variable declaration', () => { 6 | const callExpression = { 7 | type: 'CallExpression', 8 | name: 'define', 9 | arguments: [ 10 | { type: 'Identifier', name: 'x' }, 11 | { type: 'NumericLiteral', value: 3 }, 12 | ], 13 | }; 14 | 15 | const variableDeclaration = { 16 | type: 'VariableDeclaration', 17 | identifier: { type: 'Identifier', name: 'x' }, 18 | assignment: { type: 'NumericLiteral', value: 3 }, 19 | }; 20 | 21 | expect(specialForms.define(callExpression)).toEqual(variableDeclaration); 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /tests/to-javascript.test.js: -------------------------------------------------------------------------------- 1 | const { toJavaScript } = require('../src/to-javascript'); 2 | 3 | describe(toJavaScript, () => { 4 | it.skip('should reformate Dropbear to valid JavaScript', () => { 5 | const ast = { 6 | type: 'CallExpression', 7 | name: 'add', 8 | arguments: [ 9 | { type: 'NumericLiteral', value: 2 }, 10 | { type: 'NumericLiteral', value: 3 }, 11 | { 12 | type: 'CallExpression', 13 | name: 'subtract', 14 | arguments: [ 15 | { type: 'NumericLiteral', value: 5 }, 16 | { type: 'NumericLiteral', value: 4 }, 17 | ], 18 | }, 19 | ], 20 | }; 21 | 22 | expect(toJavaScript(ast)).toBe('add(2, 3, subtract(5, 4))'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /tests/tokenize.test.js: -------------------------------------------------------------------------------- 1 | import { tokenize } from '../src/tokenize'; 2 | 3 | describe(tokenize, () => { 4 | it.skip('should return an array', () => { 5 | expect(Array.isArray(tokenize(''))).toBe(true); 6 | }); 7 | 8 | it.skip('should be able to tokenize a pair of parentheses', () => { 9 | const input = '()'; 10 | const result = [ 11 | { type: 'Parenthesis', value: '(' }, 12 | { type: 'Parenthesis', value: ')' }, 13 | ]; 14 | 15 | expect(tokenize(input)).toEqual(result); 16 | }); 17 | 18 | it.skip('should ignore whitespace completely', () => { 19 | const input = ' '; 20 | const result = []; 21 | 22 | expect(tokenize(input)).toEqual(result); 23 | }); 24 | 25 | // Exercise 1 - Begin 26 | it.skip('should correctly tokenize a single digit', () => { 27 | const input = '2'; 28 | const result = [{ type: 'Number', value: 2 }]; 29 | 30 | expect(tokenize(input)).toEqual(result); 31 | }); 32 | 33 | it.skip('should be able to handle single numbers in expressions', () => { 34 | const input = '(1 2)'; 35 | 36 | const result = [ 37 | { type: 'Parenthesis', value: '(' }, 38 | { type: 'Number', value: 1 }, 39 | { type: 'Number', value: 2 }, 40 | { type: 'Parenthesis', value: ')' }, 41 | ]; 42 | 43 | expect(tokenize(input)).toEqual(result); 44 | }); 45 | 46 | it.skip('should be able to handle single letters in expressions', () => { 47 | const input = '(a b)'; 48 | 49 | const result = [ 50 | { type: 'Parenthesis', value: '(' }, 51 | { type: 'Name', value: 'a' }, 52 | { type: 'Name', value: 'b' }, 53 | { type: 'Parenthesis', value: ')' }, 54 | ]; 55 | 56 | expect(tokenize(input)).toEqual(result); 57 | }); 58 | // Exercise 1: End 59 | 60 | it.skip('should be able to handle multiple-digit numbers', () => { 61 | const input = '(11 22)'; 62 | 63 | const result = [ 64 | { type: 'Parenthesis', value: '(' }, 65 | { type: 'Number', value: 11 }, 66 | { type: 'Number', value: 22 }, 67 | { type: 'Parenthesis', value: ')' }, 68 | ]; 69 | 70 | expect(tokenize(input)).toEqual(result); 71 | }); 72 | 73 | // Exercise 2 Begin 74 | it.skip('should correctly tokenize a simple expression', () => { 75 | const input = '(add 2 3)'; 76 | const result = [ 77 | { type: 'Parenthesis', value: '(' }, 78 | { type: 'Name', value: 'add' }, 79 | { type: 'Number', value: 2 }, 80 | { type: 'Number', value: 3 }, 81 | { type: 'Parenthesis', value: ')' }, 82 | ]; 83 | 84 | expect(tokenize(input)).toEqual(result); 85 | }); 86 | 87 | it.skip('should ignore whitespace', () => { 88 | const input = ' (add 2 3)'; 89 | const result = [ 90 | { type: 'Parenthesis', value: '(' }, 91 | { type: 'Name', value: 'add' }, 92 | { type: 'Number', value: 2 }, 93 | { type: 'Number', value: 3 }, 94 | { type: 'Parenthesis', value: ')' }, 95 | ]; 96 | 97 | expect(tokenize(input)).toEqual(result); 98 | }); 99 | // Exercise 2 End 100 | 101 | it.skip('should know about strings', () => { 102 | const input = '(log "hello" "world")'; 103 | const result = [ 104 | { type: 'Parenthesis', value: '(' }, 105 | { type: 'Name', value: 'log' }, 106 | { type: 'String', value: 'hello' }, 107 | { type: 'String', value: 'world' }, 108 | { type: 'Parenthesis', value: ')' }, 109 | ]; 110 | 111 | expect(tokenize(input)).toEqual(result); 112 | }); 113 | }); 114 | -------------------------------------------------------------------------------- /tests/transform.test.js: -------------------------------------------------------------------------------- 1 | const { transform } = require('../src/transform.js'); 2 | 3 | describe(transform, () => { 4 | it.skip('should transform a "define" function to a variable declaration', () => { 5 | const callExpression = { 6 | type: 'CallExpression', 7 | name: 'define', 8 | arguments: [ 9 | { type: 'Identifier', name: 'x' }, 10 | { type: 'NumericLiteral', value: 3 }, 11 | ], 12 | }; 13 | 14 | const variableDeclaration = { 15 | type: 'VariableDeclaration', 16 | identifier: { type: 'Identifier', name: 'x' }, 17 | assignment: { type: 'NumericLiteral', value: 3 }, 18 | }; 19 | 20 | expect(transform(callExpression)).toEqual(variableDeclaration); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /tests/traverse.test.js: -------------------------------------------------------------------------------- 1 | const { traverse } = require('../src/traverse'); 2 | 3 | describe(traverse, () => { 4 | it.skip('should travel to all the nodes in the tree and reverse the math', () => { 5 | const ast = { 6 | type: 'CallExpression', 7 | name: 'add', 8 | arguments: [ 9 | { type: 'NumericLiteral', value: 12 }, 10 | { type: 'NumericLiteral', value: 6 }, 11 | ], 12 | }; 13 | 14 | const visitor = { 15 | CallExpression: { 16 | enter({ node }) { 17 | if (node.name === 'add') { 18 | node.name = 'subtract'; 19 | } 20 | }, 21 | }, 22 | NumericLiteral: { 23 | exit({ node }) { 24 | node.value = node.value * 2; 25 | }, 26 | }, 27 | }; 28 | 29 | traverse(ast, visitor); 30 | 31 | expect(ast.name).toBe('subtract'); 32 | }); 33 | 34 | it.skip('should travel to all the nodes in the tree and double all of the numbers', () => { 35 | const ast = { 36 | type: 'CallExpression', 37 | name: 'add', 38 | arguments: [ 39 | { type: 'NumericLiteral', value: 12 }, 40 | { type: 'NumericLiteral', value: 6 }, 41 | ], 42 | }; 43 | 44 | const visitor = { 45 | NumericLiteral: { 46 | exit({ node }) { 47 | node.value = node.value * 2; 48 | }, 49 | }, 50 | }; 51 | 52 | traverse(ast, visitor); 53 | 54 | expect(ast.arguments[0].value).toBe(24); 55 | expect(ast.arguments[1].value).toBe(12); 56 | }); 57 | }); 58 | --------------------------------------------------------------------------------