+143
-15
@@ -51,3 +51,3 @@ 'use strict' | ||
| } | ||
| return isIdentifier(true) || /^[0-9-]$/.test(char) | ||
| return isIdentifier(true) || /^[0-9-]$/.test(char) || char === '.' | ||
| } | ||
@@ -62,2 +62,5 @@ | ||
| .find(s => s.startsWith(start + peek())) | ||
| if (start[0] === '/' && isIdentifier()) { | ||
| throw new Error('Unexpected internal call') | ||
| } | ||
| return Boolean(starters) | ||
@@ -69,3 +72,3 @@ } | ||
| while (peek() !== '') { | ||
| while (whitespace.includes(peek())) next() | ||
| while (peek() && whitespace.includes(peek())) next() | ||
@@ -149,2 +152,10 @@ if (peek() === '') break | ||
| return sExpr | ||
| } else if (peek() === '[') { | ||
| next() | ||
| const list = ['/list'] | ||
| while (peek() !== '' && peek() !== ']') { | ||
| list.push(parseExpression()) | ||
| } | ||
| next() | ||
| return list | ||
| } else if (/^[a-zA-Z$_-]+$/.test(peek())) { | ||
@@ -184,3 +195,5 @@ return next() | ||
| const unaryOps = [] | ||
| const unaryOps = [ | ||
| ['/str', x => x] | ||
| ] | ||
@@ -193,8 +206,105 @@ const nativeGlobals = Object.fromEntries([ | ||
| const evaluate = exports.evaluate = (expression) => { | ||
| class Scope { | ||
| constructor (parent) { | ||
| this.parent = parent | ||
| this.variables = new Map() | ||
| } | ||
| lookup (name) { | ||
| if (this.variables.has(name)) return this.variables.get(name) | ||
| if (this.parent) return this.parent.lookup(name) | ||
| throw new Error('Unknown variable ' + name) | ||
| } | ||
| } | ||
| class Fn { | ||
| constructor (name, argList, body, parentScope) { | ||
| this.name = name | ||
| this.argList = argList | ||
| this.body = body | ||
| this.parentScope = parentScope | ||
| Object.freeze(this) | ||
| } | ||
| } | ||
| const toArgList = argList => { | ||
| if (argList[0] !== '/list') { | ||
| throw new Error('function was not given a list as the argument list') | ||
| } | ||
| argList.shift() | ||
| if (!argList.every(x => typeof x === 'string')) { | ||
| throw new Error('non-name argument in argument list') | ||
| } | ||
| return argList | ||
| } | ||
| const compile = exports.compile = (expression) => { | ||
| if (typeof expression === 'string') return expression | ||
| if (typeof expression === 'number') return expression | ||
| if (typeof expression === 'boolean') return expression | ||
| let [head, ...args] = expression | ||
| if (Array.isArray(head)) { | ||
| return `(${compile(head)})(${args.map(compile).join(', ')})` | ||
| } | ||
| if (head === 'do') { | ||
| if (!args.length) return 'null' | ||
| args = args.map(compile) | ||
| if (args.length === 1) return args[0] | ||
| const last = args.pop() | ||
| return '(()=>{' + args.join(';') + ';return ' + last + '})()' | ||
| } else if (head === 'if') { | ||
| args = args.map(compile) | ||
| if (args.length < 3) args.push('null') | ||
| return `(${args[0]}?${args[1]}:${args[2]})` | ||
| } else if (head === 'def') { | ||
| return `let ${args[0]}=${compile(args[1])}` | ||
| } else if (head === 'set') { | ||
| return `(${args[0]}=${compile(args[1])})` | ||
| } else if (head === 'fn') { | ||
| const name = args[0][0] === '/list' ? '' : args.shift() | ||
| const argList = args.shift().slice(1).join(',') | ||
| const body = compile(['do', ...args]) | ||
| if (name) { | ||
| return `(function ${name}(${argList}){return ${body}})` | ||
| } | ||
| return `((${argList}) => ${body})` | ||
| } else if (head === '/str') { | ||
| return JSON.stringify(args[0]) | ||
| } else if (head === '/list') { | ||
| return JSON.stringify(args) | ||
| } else if (comparisonOperators.includes(head)) { | ||
| return `(${args.map(compile).reduce((a, b) => `(${a})${head}${b}`)})` | ||
| } else if (binaryOperators.includes(head)) { | ||
| return '(' + args.map(compile).join(head) + ')' | ||
| } else if (typeof head === 'string') { | ||
| return `${head}(${args.map(compile).join(',')})` | ||
| } | ||
| throw new Error('unknown expression ' + head) | ||
| } | ||
| const evaluate = exports.evaluate = (expression, stack = [new Scope(null)]) => { | ||
| const scope = stack[stack.length - 1] | ||
| if (typeof expression === 'string') { | ||
| return scope.lookup(expression) | ||
| } | ||
| const isAtom = !Array.isArray(expression) | ||
| const push = (s = new Scope(scope)) => stack.concat(s) | ||
| if (isAtom) { | ||
| return expression | ||
| } | ||
| const [head, ...args] = expression | ||
| let [head, ...args] = expression | ||
| if (Array.isArray(head)) { | ||
| head = evaluate(head, stack) | ||
| } | ||
| if (head instanceof Fn) { | ||
| const fnScope = new Scope(head.parentScope) | ||
| head.argList.forEach((arg, i) => { | ||
| fnScope.variables.set(arg, evaluate(args[i], stack)) | ||
| }) | ||
| return evaluate(head.body, push(fnScope)) | ||
| } | ||
| if (head in nativeGlobals) { | ||
@@ -205,15 +315,34 @@ const func = nativeGlobals[head] | ||
| if (head === 'do') { | ||
| for (var i = 0; ; i++) { | ||
| const isLast = i === args.length - 1 | ||
| const val = evaluate(args[i]) | ||
| if (isLast) return val | ||
| let val | ||
| const doStack = push() | ||
| for (let i = 0; i < args.length; i++) { | ||
| val = evaluate(args[i], doStack) | ||
| } | ||
| return val | ||
| } else if (head === 'if') { | ||
| const [cond, then, alternate] = args | ||
| if (evaluate(cond)) { | ||
| return evaluate(then) | ||
| if (evaluate(cond, stack)) { | ||
| return evaluate(then, stack) | ||
| } else if (alternate != null) { | ||
| return evaluate(alternate) | ||
| return evaluate(alternate, stack) | ||
| } | ||
| return null | ||
| } else if (head === 'fn') { | ||
| let name | ||
| if (!Array.isArray(args[0])) { | ||
| name = args.shift() | ||
| } | ||
| if (!Array.isArray(args[0])) { | ||
| throw new Error('function without arguments list') | ||
| } | ||
| const argList = toArgList(args.shift()) | ||
| return new Fn(name, argList, ['do', ...args], scope) | ||
| } else if (head === 'set') { | ||
| const value = evaluate(args[1], scope) | ||
| scope.variables.set(args[0], value) | ||
| return value | ||
| } else if (head === 'def') { | ||
| const value = evaluate(args[1], scope) | ||
| scope.variables.set(args[0], value) | ||
| return value | ||
| } else { | ||
@@ -224,4 +353,3 @@ throw new Error('Unknown function or macro ' + head) | ||
| exports.parseEvaluate = (source) => { | ||
| return evaluate(exports.parse(source)) | ||
| } | ||
| exports.parseEvaluate = source => evaluate(exports.parse(source)) | ||
| exports.parseCompile = source => compile(exports.parse(source)) |
+6
-2
| { | ||
| "name": "flang", | ||
| "version": "0.0.1", | ||
| "version": "0.0.2", | ||
| "description": "", | ||
@@ -17,3 +17,3 @@ "main": "lib/index.js", | ||
| "scripts": { | ||
| "test": "mocha", | ||
| "test": "mocha -r ./register.js test/*.js test/*.fl", | ||
| "lint": "eslint --fix 'lib/**/*.js'" | ||
@@ -31,2 +31,3 @@ }, | ||
| "eslint-plugin-standard": "^4.0.0", | ||
| "flang": "0.0.1", | ||
| "husky": "^3.0.3", | ||
@@ -49,3 +50,6 @@ "mocha": "^6.2.0" | ||
| } | ||
| }, | ||
| "dependencies": { | ||
| "pirates": "^4.0.1" | ||
| } | ||
| } |
11313
64.62%326
68.04%1
Infinity%10
11.11%+ Added
+ Added