UNPKG

tcl-js

Version:

tcl-js is a tcl intepreter written completely in Typescript. It is meant to replicate the tcl-sh interpreter as closely as possible.

272 lines (239 loc) 7.04 kB
import { TOP, TNUMBER, TSTRING, TPAREN, TCOMMA, TNAME } from './token'; import { Instruction, INUMBER, IVAR, IFUNCALL, IEXPR, IMEMBER, ternaryInstruction, binaryInstruction, unaryInstruction, } from './instruction'; import contains from './contains'; export function ParserState(parser, tokenStream, options) { this.parser = parser; this.tokens = tokenStream; this.current = null; this.nextToken = null; this.next(); this.savedCurrent = null; this.savedNextToken = null; this.allowMemberAccess = options.allowMemberAccess !== false; } ParserState.prototype.next = function() { this.current = this.nextToken; this.nextToken = this.tokens.next(); return this.nextToken; }; ParserState.prototype.tokenMatches = function(token, value) { if (typeof value === 'undefined') { return true; } else if (Array.isArray(value)) { return contains(value, token.value); } else if (typeof value === 'function') { return value(token); } else { return token.value === value; } }; ParserState.prototype.save = function() { this.savedCurrent = this.current; this.savedNextToken = this.nextToken; this.tokens.save(); }; ParserState.prototype.restore = function() { this.tokens.restore(); this.current = this.savedCurrent; this.nextToken = this.savedNextToken; }; ParserState.prototype.accept = function(type, value) { if ( this.nextToken.type === type && this.tokenMatches(this.nextToken, value) ) { this.next(); return true; } return false; }; ParserState.prototype.expect = function(type, value) { if (!this.accept(type, value)) { var coords = this.tokens.getCoordinates(); throw new Error( 'parse error [' + coords.line + ':' + coords.column + ']: Expected ' + (value || type), ); } }; ParserState.prototype.parseAtom = function(instr) { if (this.accept(TNAME)) { instr.push(new Instruction(IVAR, this.current.value)); } else if (this.accept(TNUMBER)) { instr.push(new Instruction(INUMBER, this.current.value)); } else if (this.accept(TSTRING)) { instr.push(new Instruction(INUMBER, this.current.value)); } else if (this.accept(TPAREN, '(')) { this.parseExpression(instr); this.expect(TPAREN, ')'); } else { throw new Error('unexpected ' + this.nextToken); } }; ParserState.prototype.parseExpression = function(instr) { this.parseConditionalExpression(instr); }; ParserState.prototype.parseConditionalExpression = function(instr) { this.parseOrExpression(instr); while (this.accept(TOP, '?')) { var trueBranch = []; var falseBranch = []; this.parseConditionalExpression(trueBranch); this.expect(TOP, ':'); this.parseConditionalExpression(falseBranch); instr.push(new Instruction(IEXPR, trueBranch)); instr.push(new Instruction(IEXPR, falseBranch)); instr.push(ternaryInstruction('?')); } }; ParserState.prototype.parseOrExpression = function(instr) { this.parseAndExpression(instr); while (this.accept(TOP, '||')) { var falseBranch = []; this.parseAndExpression(falseBranch); instr.push(new Instruction(IEXPR, falseBranch)); instr.push(binaryInstruction('||')); } }; ParserState.prototype.parseAndExpression = function(instr) { this.parseBitwise(instr); while (this.accept(TOP, '&&')) { var trueBranch = []; this.parseBitwise(trueBranch); instr.push(new Instruction(IEXPR, trueBranch)); instr.push(binaryInstruction('&&')); } }; var BITWISE_OPERATORS = ['&', '|', '^']; ParserState.prototype.parseBitwise = function(instr) { this.parseComparison(instr); while (this.accept(TOP, BITWISE_OPERATORS)) { var op = this.current; this.parseComparison(instr); instr.push(binaryInstruction(op.value)); } }; var COMPARISON_OPERATORS = ['==', '!=', '<', '<=', '>=', '>', 'eq', 'ne']; ParserState.prototype.parseComparison = function(instr) { this.parseShift(instr); while (this.accept(TOP, COMPARISON_OPERATORS)) { var op = this.current; this.parseShift(instr); instr.push(binaryInstruction(op.value)); } }; var SHIFT_OPERATORS = ['<<', '>>']; ParserState.prototype.parseShift = function(instr) { this.parseAddSub(instr); while (this.accept(TOP, SHIFT_OPERATORS)) { var op = this.current; this.parseAddSub(instr); instr.push(binaryInstruction(op.value)); } }; var ADD_SUB_OPERATORS = ['+', '-']; ParserState.prototype.parseAddSub = function(instr) { this.parseTerm(instr); while (this.accept(TOP, ADD_SUB_OPERATORS)) { var op = this.current; this.parseTerm(instr); instr.push(binaryInstruction(op.value)); } }; var TERM_OPERATORS = ['*', '/', '%']; ParserState.prototype.parseTerm = function(instr) { this.parseExponential(instr); while (this.accept(TOP, TERM_OPERATORS)) { var op = this.current; this.parseExponential(instr); instr.push(binaryInstruction(op.value)); } }; ParserState.prototype.parseExponential = function(instr) { this.parseFactor(instr); while (this.accept(TOP, '**')) { this.parseFactor(instr); instr.push(binaryInstruction('**')); } }; ParserState.prototype.parseFactor = function(instr) { var unaryOps = this.tokens.unaryOps; function isPrefixOperator(token) { return token.value in unaryOps; } this.save(); if (this.accept(TOP, isPrefixOperator)) { if ( this.current.value !== '-' && this.current.value !== '+' && this.nextToken.type === TPAREN && this.nextToken.value === '(' ) { this.restore(); this.parseFunctionCall(instr); } else { var op = this.current; this.parseFactor(instr); instr.push(unaryInstruction(op.value)); } } else { this.parseFunctionCall(instr); } }; ParserState.prototype.parseFunctionCall = function(instr) { var unaryOps = this.tokens.unaryOps; function isPrefixOperator(token) { return token.value in unaryOps; } if (this.accept(TOP, isPrefixOperator)) { var op = this.current; this.parseAtom(instr); instr.push(unaryInstruction(op.value)); } else { this.parseMemberExpression(instr); while (this.accept(TPAREN, '(')) { if (this.accept(TPAREN, ')')) { instr.push(new Instruction(IFUNCALL, 0)); } else { var argCount = this.parseArgumentList(instr); instr.push(new Instruction(IFUNCALL, argCount)); } } } }; ParserState.prototype.parseArgumentList = function(instr) { var argCount = 0; while (!this.accept(TPAREN, ')')) { this.parseExpression(instr); ++argCount; while (this.accept(TCOMMA)) { this.parseExpression(instr); ++argCount; } } return argCount; }; ParserState.prototype.parseMemberExpression = function(instr) { this.parseAtom(instr); while (this.accept(TOP, '.')) { if (!this.allowMemberAccess) { throw new Error('unexpected ".", member access is not permitted'); } this.expect(TNAME); instr.push(new Instruction(IMEMBER, this.current.value)); } };