diff --git a/environment.js b/environment.js index 22ff1cd..833aaf4 100644 --- a/environment.js +++ b/environment.js @@ -22,6 +22,10 @@ class Environment { return this.map.set(token.lexeme, value) } + setBuiltin(name, func) { + this.map.set(name, typeof func === 'function' ? { call: func } : func) + } + assign(token, value) { if (!this.map.has(token.lexeme)) { if (this.enclosing) return this.enclosing.assign(token, value) diff --git a/index.js b/index.js index fdbd453..95786a5 100755 --- a/index.js +++ b/index.js @@ -44,6 +44,7 @@ const run = (code, environment) => { // Error String const errorSection = code.substr(e.startCoordinates.index, e.endCoordinates.index) + // @TODO: Fix this // Post Error String const backIndex = code.indexOf('\n', e.endCoordinates.index) const postErrorStart = backIndex < 0 ? code.length : backIndex @@ -69,6 +70,7 @@ const runPrompt = () => { historySize: +options.history }) const env = new Environment() + env.setBuiltin('readFile', (_vars, args) => fs.readFileSync(args[0], 'utf8')) lineReader.on('line', line => { let code = line @@ -109,6 +111,7 @@ const processOptions = args => .filter(Boolean) const main = argv => { + process.title = 'YALI' const args = processOptions(argv.slice(2)) if (options.debug) console.log(options) if (args.length > 1) { diff --git a/interpreter.js b/interpreter.js index 347f313..c074486 100644 --- a/interpreter.js +++ b/interpreter.js @@ -2,6 +2,7 @@ const { runtimeError } = require('./errors') const { Binary, Unary, + Call, Literal, Logical, Var, @@ -31,6 +32,12 @@ const checkNumber = (token, ...operands) => { class Interpreter { constructor(environment) { this.environment = environment || new Environment() + this.environment.setBuiltin('PI', Math.PI) + this.environment.setBuiltin('cos', (_vars, args) => Math.cos(args[0])) + this.environment.setBuiltin('mod', (_vars, args) => args[0] % args[1]) + this.environment.setBuiltin('strlen', (_vars, args) => args[0].length) + this.environment.setBuiltin('charAt', (_vars, args) => args[0][args[1]]) + this.environment.setBuiltin('clock', () => new Date().getTime()) } interpret(expr) { @@ -41,6 +48,7 @@ class Interpreter { if (expr instanceof Block) return this.visitBlock(expr) else if (expr instanceof Assignment) return this.visitAssignment(expr) else if (expr instanceof Logical) return this.visitLogical(expr) + else if (expr instanceof Call) return this.visitCall(expr) else if (expr instanceof While) return this.visitWhile(expr) else if (expr instanceof Condition) return this.visitCondition(expr) else if (expr instanceof VarStatement) return this.visitVarStatement(expr) @@ -130,6 +138,18 @@ class Interpreter { return value } + visitCall(expr) { + const callee = this.evaluate(expr.callee) + + let args = expr.arguments.map(arg => this.evaluate(arg)) + + if (!callee.call) { + throw runtimeError('Can only call functions and classes', expr.paren) + } + + return callee.call(this, args) + } + visitUnary(expr) { const right = this.evaluate(expr.right) switch (expr.operator.type) { diff --git a/parser.js b/parser.js index 7b7f32d..5ee1fb2 100644 --- a/parser.js +++ b/parser.js @@ -3,6 +3,7 @@ const { Binary, Unary, Var, + Call, Literal, While, Grouping, @@ -75,7 +76,6 @@ class Parser { return this.matchBinary('equality', Logical, token.AND) } - varDeclaration() { const name = this.consume(token.IDENTIFIER, 'Expected variable name') let initializer = null @@ -87,8 +87,7 @@ class Parser { return new VarStatement(name, initializer) } - - forStatement () { + forStatement() { this.consume(token.LEFT_PAREN, 'Expected "(" after "for"') let init @@ -204,7 +203,31 @@ class Parser { const right = this.unary() return new Unary(operator, right) } - return this.primary() + return this.call() + } + + call() { + let expr = this.primary() + while (true) { + if (this.match(token.LEFT_PAREN)) { + expr = this.finishCall(expr) + } else { + break + } + } + + return expr + } + + finishCall(callee) { + let args = [] + if (!this.check(token.RIGHT_PAREN)) { + do { + args.push(this.expression()) + } while (this.match(token.COMMA)) + } + const paren = this.consume(token.RIGHT_PAREN, 'Unfinished argument list') + return new Call(callee, paren, args) } primary() { diff --git a/types.js b/types.js index de24d0c..7b7a559 100644 --- a/types.js +++ b/types.js @@ -27,7 +27,6 @@ class Var { } } -// @TODO: Can we express this in a less type-dependant way? class Grouping { constructor(expression) { this.expression = expression @@ -72,11 +71,31 @@ class While { } } +class Call { + constructor(callee, paren, args) { + ;(this.callee = callee), (this.paren = paren) + this.arguments = args + } +} + +class Callable { + constructor(name, func) { + this.lexeme = name + this.call = func + } + + toString() { + return '' + } +} + module.exports = { Var, Binary, Unary, Block, + Call, + Callable, While, Literal, Logical,