| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444 |
- const fs = require('fs')
- const sourcedFiles = []
- var preprocess = function (fileNameIn, input) {
- let inputArr = input.split('\n')
- sourcedFiles.push(fileNameIn)
- for (i = 0; i < inputArr.length; i++) {
- line = inputArr[i]
- if (line.startsWith('`source')) {
- let fileName = line.split(' ')[1]
- if (!~sourcedFiles.indexOf(fileName)) {
- line = fs.readFileSync(fileName, { encoding: 'utf-8' })
- inputArr[i] = preprocess(fileName, line)
- } else {
- inputArr[i] = '\n'
- console.log('Already sourced file: ' + fileName + '; Skipping!')
- }
- }
- }
- return inputArr.join('\n')
- }
- var tokenizer = function (input) {
- let pos = 0
- let tokens = []
- tokens.push(input)
- while (pos < input.length) {
- let char = input[pos]
- let parens = /[()]/
- if (parens.test(char)) {
- tokens.push({
- type: 'paren',
- value: char
- })
- pos++
- continue
- }
- if (char === '|') {
- tokens.push({
- type: 'bar',
- value: char
- })
- pos++
- continue
- }
- let whitespace = /[;\s]/ // Include comments as a whitespace character as they are ignored
- if (whitespace.test(char)) {
- if (char === ';') { // Rest of line is ignored
- comment = ''
- while (char !== '\n') {
- comment += char
- char = input[++pos]
- }
- } else {
- pos++
- }
- continue
- }
- let stringChars = /['"]/
- if (stringChars.test(char)) {
- let myDelim = char
- let stringString = ''
- char = input[++pos]
- while (char !== myDelim) {
- if (char !== '\n') {
- stringString += char
- }
- char = input[++pos]
- }
- pos++
- tokens.push({
- type: 'string',
- value: stringString
- })
- continue
- }
- let numbers = /[0-9]/
- if (numbers.test(char)) {
- let numberString = ''
- while (numbers.test(char)) {
- numberString += char
- char = input[++pos]
- }
- tokens.push({
- type: 'number',
- value: numberString
- })
- continue
- }
- let characters = /[a-zA-Z_:]/
- if (characters.test(char)) {
- let name = ''
- while (characters.test(char)) {
- name += char
- char = input[++pos]
- }
- tokens.push({
- type: 'name',
- value: name
- })
- continue
- }
- let dollar = /[$]/
- if (dollar.test(char)) {
- let name = ''
- char = input[++pos]
- if (numbers.test(char)) {
- while (numbers.test(char)) {
- name += char
- char = input[++pos]
- }
- } else {
- console.error("Compiler Error: $ must be followed by a digit [0-9]")
- process.exit(1);
- }
- tokens.push({
- type: 'dollar',
- value: name
- })
- continue
- }
- let argv = /[{}]/
- if (argv.test(char)) {
- tokens.push({
- type: 'argv',
- value: char
- })
- pos++
- continue
- }
- throw new TypeError("I'm not sure what you are telling me :( Ask my creator to teach me what a: " + char + " is.")
- }
- return tokens
- }
- var parser = function (input) {
- let pos = 1
- function walk() {
- let token = input[pos]
- if (token.type === 'number') {
- pos++
- return {
- type: 'NumberLiteral',
- value: token.value
- }
- }
- if (token.type === 'name') {
- pos++
- return {
- type: 'VariableReference',
- value: token.value
- }
- }
- if (token.type === 'dollar') {
- pos++
- return {
- type: 'DollarVar',
- value: token.value
- }
- }
- if (token.type === 'bar') {
- pos++
- return {
- type: 'BarLiteral',
- value: token.value
- }
- }
- if (token.type === 'string') {
- pos++
- return {
- type: 'StringLiteral',
- value: token.value
- }
- }
- if (token.type === 'paren' && token.value == '(') {
- token = input[++pos]
- if (token.type !== 'name') {
- throw {
- name: 'Compiler Error',
- message: 'FunctionCall may only be type "name" not "' + token.type + '".'
- }
- }
- let node = {
- type: 'FunctionCall',
- value: token.value,
- params: []
- }
- token = input[++pos]
- while ((token.type !== 'paren') || (token.type === 'paren' && token.value !== ')')) {
- node.params.push(walk())
- token = input[pos]
- }
- pos++
- return node
- }
- if (token.type === 'argv' && token.value == '{') {
- token = input[++pos]
- if (token.type !== 'number') {
- throw {
- name: 'Compiler Error',
- message: 'argv may only take integer values.'
- }
- }
- let node = {
- type: 'ArgvLiteral',
- value: token.value
- }
- token = input[++pos]
- if (token.type !== 'argv' || token.value !== '}') {
- throw {
- name: 'Compiler Error',
- message: 'argv literals take one integer value and nothing else.'
- }
- }
- pos++
- return node
- }
- throw new TypeError(token.type)
- }
- let ast = {
- type: 'Prog',
- body: []
- }
- while (pos < input.length) {
- ast.body.push(walk())
- }
- return ast
- }
- var traverser = function (ast, visitor) {
- function traverseArray(array, parent) {
- array.forEach(function (child) {
- traverseNode(child, parent)
- })
- }
- function traverseNode(node, parent) {
- const method = visitor[node.type]
-
- if (method) {
- method(node, parent)
- }
- switch (node.type) {
- case 'Prog':
- traverseArray(node.body, node)
- break
- case 'FunctionCall':
- traverseArray(node.params, node)
- break
- case 'VariableReference':
- break
- case 'NumberLiteral':
- break
- case 'StringLiteral':
- break
- case 'DollarVar':
- break
- case 'BarLiteral':
- break
- case 'ArgvLiteral':
- break
- default:
- throw {
- name: 'Compiler Error',
- message: 'Unknown leaf in AST: ' + node.type
- }
- }
- }
- traverseNode(ast, null)
- }
- var transformer = function (ast) {
- let newAst = {
- type: 'Prog',
- body: []
- }
- ast._context = newAst.body
- traverser(ast, {
- NumberLiteral: function (node, parent) {
- parent._context.push({
- type: 'NumberLiteral',
- value: node.value
- })
- },
- StringLiteral: function (node, parent) {
- parent._context.push({
- type: 'StringLiteral',
- value: node.value
- })
- },
- BarLiteral: function (node, parent) {
- parent._context.push({
- type: 'BarLiteral',
- value: node.value
- })
- },
- VariableReference: function (node, parent) {
- parent._context.push({
- type: 'VariableReference',
- value: node.value
- })
- },
- DollarVar: function (node, parent) {
- parent._context.push({
- type: 'DollarVar',
- value: node.value
- })
- },
- ArgvLiteral: function (node, parent) {
- parent._context.push({
- type: 'ArgvLiteral',
- value: node.value
- })
- },
- FunctionCall: function (node, parent) {
- let expression = {
- type: 'FunctionCall',
- callee: {
- type: 'FunctionName',
- name: node.value
- },
- args: []
- }
- node._context = expression.args
- if (parent.type !== 'FunctionCall') {
- expression = {
- type: 'Statement',
- expr: expression
- }
- }
- parent._context.push(expression)
- }
- })
- return newAst
- }
- var escapeDepth = 0
- var generator = function (node) {
- switch (node.type) {
- case 'Prog':
- let program = node.body.map(generator)
- program.unshift('var _ = require("./libjs/stdlib.js")(this)')
- return program.join('\n')
- break
- case 'Statement':
- return (generator(node.expr) + ';')
- break
- case 'FunctionCall':
- if (!node.callee.name.match('(def|if|repeat)')) {
- if (node.callee.name.match('include')) {
- // Include is a special function and we will write the generation ourselves
- return node.args.map((arg) => {
- let lib = './libjs/' + arg.value + '.js'
- return ('var _' + arg.value + ' = require("' + lib + '")(this)')
- }).join("\n")
- } else {
- return (generator(node.callee) + '(' + node.args.map(generator).join(', ') + ')')
- }
- } else {
- return (generator(node.callee) + '(' + node.args.map((v, i) => {
- if (i === 0) {
- return generator(v) + ', '
- } else {
- if (i === 1) {
- return 'function() { \n' + generator(v) + ';\n'
- } else {
- return generator(v) + ';\n'
- }
- }
- }).join('') + '})')
- }
- break;
- case 'DollarVar':
- return 'arguments[' + (+node.value-1) + ']'
- break
- case 'BarLiteral':
- return '}, function() {'
- break
- case 'FunctionName':
- if (node.name.match("::")) {
- let [namespace, func] = node.name.split("::")
- return "_" + namespace + "." + func
- } else {
- return '_.' + node.name
- }
- break
- case 'VariableReference':
- return '_.ref(\'' + node.value + '\')'
- break
- case 'NumberLiteral':
- return '{value: ' + node.value + '}'
- break
- case 'StringLiteral':
- return '{ value: \'' + node.value + '\' }'
- break
- case 'ArgvLiteral':
- if (node.value === '0') {
- return '{ value: process.argv.slice(2).join(\' \') }'
- } else {
- return '_.__get_arg(' + (+node.value+1) + ')'
- }
- break
- default:
- throw {
- name: 'Compiler Error',
- message: 'Unexpected leaf in transformed AST: ' + node.type
- }
- break
- }
- }
- const fileNameIn = process.argv[2]
- const fileNameOut = fileNameIn + '.js'
- const myInput = fs.readFileSync(process.argv[2], { encoding: 'utf-8' })
- const preProcessedInput = preprocess(fileNameIn, myInput) // Run the preprocessor to evaluate any `source's
- const myTokens = tokenizer(preProcessedInput) // Convert our input into individual tokens
- const parsedTree = parser(myTokens) // Convert these tokens into a syntax tree
- const transformedTree = transformer(parsedTree) // Now put the tree into an easily traversable format for our generator
- const output = generator(transformedTree) // Generate the final JS code
- fs.writeFileSync(fileNameOut, output)
|