UNPKG

sellquiz

Version:

An open source domain-specific language for online assessment

950 lines (921 loc) 41.6 kB
/****************************************************************************** * SELL - SIMPLE E-LEARNING LANGUAGE * * * * Copyright (c) 2019-2021 TH Köln * * Author: Andreas Schwenk, contact@compiler-construction.com * * * * Partly funded by: Digitale Hochschule NRW * * https://www.dh.nrw/kooperationen/hm4mint.nrw-31 * * * * GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 * * * * This library is licensed as described in LICENSE, which you should have * * received as part of this distribution. * * * * This software is distributed on "AS IS" basis, WITHOUT WARRENTY OF ANY * * KIND, either impressed or implied. * ******************************************************************************/ import * as math from 'mathjs'; import { sellassert } from './sellassert.js'; import { symtype, SellSymbol } from './symbol.js'; import { Lexer } from './lex.js'; import * as LinAlg from './linalg.js'; import { SellSymTerm, SellSymTerm_Matrix } from './symbolic.js'; import { SellQuiz } from './quiz.js'; export class ParseCode { p : SellQuiz; constructor(parent : SellQuiz) { this.p = parent; } getFunctionList() { return ['abs', 'binomial', 'integrate', 'conj', 'sqrt', 'xgcd', 'det', 'rank', 'inv', 'eye', 'eigenvalues_sym', 'triu', 'sin', 'cos', 'asin', 'acos', 'tan', 'atan', 'norm2', 'dot', 'cross', 'linsolve', 'is_zero', 'min', 'max']; } // code = // "§CODE_START" { (code_prop | code_hint | assign | prog) "\n" } "§CODE_END"; parseCode() { this.p.terminal('§CODE_START'); while(!this.p.is('§CODE_END') && !this.p.is('§END')) { if(this.p.is("input")) { this.parseCodeProp(); this.p.terminal('§EOL'); } else if(this.p.is('?')) { this.parseCodeHint(); this.p.terminal('§EOL'); } else if(this.p.is('JavaBlock') || this.p.is('JavaMethod') || this.p.is('Python')) { this.p.progParser.parseProg(); } else { this.parseAssign(); this.p.terminal('§EOL'); } } if(!this.p.is("§END")) this.p.terminal('§CODE_END'); } // code_prop = // "input" ("rows"|"cols") ":=" ("resizeable"|"static"); parseCodeProp() { if(this.p.is("input")) this.p.next(); else this.p.err("expected input"); let isRows = false; if(this.p.is("rows")) { this.p.next(); isRows = true; } else if(this.p.is("cols")) { this.p.next(); isRows = false; } else this.p.err("expected 'rows' or 'cols'"); this.p.terminal(":="); let isResizable = true; if(this.p.is("resizable")) { this.p.next(); isResizable = true; } else if(this.p.is("static")) { this.p.next(); isResizable = false; } else this.p.err("expected 'resizable' or 'static'"); if(isRows) this.p.resizableRows = isResizable; else this.p.resizableCols = isResizable; } // code_hint = // "?" text; parseCodeHint() { if(this.p.is("?")) this.p.next(); else this.p.err("expected ?"); let hintSym = this.p.q.lastParsedInputSymbol; if(hintSym == null) this.p.err("Hint/explanation is forbidden, since there is no preceding input field"); let htmlLen = this.p.q.html.length; this.p.textParser.parseText(true/*parsingHint=true*/); hintSym.hint_html = this.p.q.html.substr(htmlLen); this.p.q.html = this.p.q.html.substr(0, htmlLen); } chooseFromSet(set) { let v; if (set.length == 4 && set[2].type === symtype.T_DOTS) { let lowerBound = set[0].value; let upperBound = set[3].value; let step = parseFloat(set[1].value) - lowerBound; v = Math.floor(Math.random() * (upperBound - lowerBound + step) / step) * step + lowerBound; } else { let idx = Math.floor(Math.random() * set.length); v = set[idx].value; } return v; } // assign = // ID {"," ID} (":="|"=") expr // | ID {"," ID} "in" (matrix_def | set | expr) // | ID "[" expr "," expr "]" (":="|"=") expr // | ID "[" expr "," expr "]" "in" (matrix_def | set | expr) // | ID "(" ID {"," ID} ")" (":="|"=") symbolic_term; parseAssign() { this.p.q.stack = []; // set of left-hand side (lhs) variables let isFunction = false; let lhsIDs = []; let lhsSymbolIDs = []; // e.g. for "f(x,y)", we call "x" and "y" symbols let lhsMatrixIndexed = false; let lhsMatrixRow=0, lhsMatrixCol=0; this.p.ident(); lhsIDs.push(this.p.id); if(this.p.id === 'i' || this.p.id === 'e') { // TODO: also test for functions names, ... this.p.err("id '" + this.p.id +"' is a reserved symbol"); } if(this.p.is("(")) { // function / symbolic term isFunction = true; this.p.next(); this.p.ident(); lhsSymbolIDs.push(this.p.id); while(this.p.is(",")) { this.p.next(); this.p.ident(); lhsSymbolIDs.push(this.p.id); } this.p.terminal(")"); } else if(this.p.is("[")) { // matrix indexing this.p.next(); lhsMatrixIndexed = true; // row this.parseExpr(); let tos = this.p.q.stack.pop(); // tos := top of stack if(tos.type != symtype.T_REAL || !Lexer.isInteger(tos.value)) this.p.err("row index is not an integer value"); lhsMatrixRow = tos.value; // separator this.p.terminal(","); // columns this.parseExpr(); tos = this.p.q.stack.pop(); // tos := top of stack if(tos.type != symtype.T_REAL || !Lexer.isInteger(tos.value)) this.p.err("column index is not an integer value"); lhsMatrixCol = tos.value; // end this.p.terminal("]"); } else { // list of lhs-variables only allowed for non-functions while(this.p.is(",")) { this.p.next(); this.p.ident(); lhsIDs.push(this.p.id); } } // assignment if(this.p.is(':=') || this.p.is('=')) { this.p.next(); if(isFunction) this.p.codeSymParser.parseSymbolicTerm(lhsSymbolIDs); else this.parseExpr(); let rhs = this.p.q.stack.pop(); if(lhsMatrixIndexed) { if(rhs.type != symtype.T_REAL) this.p.err("right-hand side must be of type real"); if(!(lhsIDs[0] in this.p.q.symbols)) this.p.err("unkown symbol '" + lhsIDs[0] + "'"); let lhs = this.p.q.symbols[lhsIDs[0]]; if(lhs.type != symtype.T_MATRIX) this.p.err("symbol '" + lhsIDs[0] + "' is not a matrix"); lhs.value = LinAlg.SellLinAlg.mat_set_element( lhs.value, lhsMatrixRow-1, lhsMatrixCol-1, rhs.value); if(lhs.value == null) this.p.err("invalid indices"); } else { for(let i=0; i<lhsIDs.length; i++) this.p.q.symbols[lhsIDs[i]] = rhs; } } // choose element of right-hand side (rhs) else if(this.p.is('in')) { if(isFunction) this.p.err("cannot apply 'in' to a function") this.p.next(); if(this.p.is('MM')) { // matrix this.parseMatrixDef(); let rhs = this.p.q.stack.pop(); // rhs is a matrix definition let m = rhs.value[0]; let n = rhs.value[1]; let set = rhs.value[2]; let invertible = rhs.value[3]; let symmetric = rhs.value[4]; for(let k=0; k<lhsIDs.length; k++) { let value = math.zeros(m, n); let iterations=0; do { // run, until all properties are fulfilled for(let i=0; i<m; i++) { for(let j=0; j<n; j++) { let element = this.chooseFromSet(set.value); (value as math.Matrix).subset(math.index(i, j), element); } } if(symmetric) { for (let i=1; i<m; i++) { for (let j=0; j<i; j++) { let element = (value as math.Matrix).subset(math.index(i, j)); value = (value as math.Matrix).subset(math.index(j, i), element); } } } if(!invertible) break; if(iterations > 256) this.p.err("matrix generation failed: too many iterations"); iterations ++; } while(math.abs(math.det(value)) < 1e-16); // TODO:epsilon this.p.q.symbols[lhsIDs[k]] = new SellSymbol(symtype.T_MATRIX, value); } } else if(this.p.is('{') || this.p.isIdent()) { // set if(this.p.is('{')) // TODO: move this to parseUnary! this.parseSet(); else this.parseExpr(); let rhs = this.p.q.stack.pop(); if(rhs.type != symtype.T_SET) this.p.err("expected a set"); // run until (optional) constrains are met let lex_backup = this.p.backupLexer(); let ctr = 0; while(true) { if(ctr > 1000) this.p.err("constraints for random variables cannot be fulfilled"); ctr ++; for(let i=0; i<lhsIDs.length; i++) { let value = this.chooseFromSet(rhs.value); this.p.q.symbols[lhsIDs[i]] = new SellSymbol(symtype.T_REAL, value); } // optional constraint(s) this.p.replayLexer(lex_backup); if(this.p.is('with')) { this.p.next(); this.parseExpr(); let tos = this.p.q.stack.pop(); // tos := top of stack if(tos.type != symtype.T_BOOL) this.p.err("constraint must be boolean"); if(tos.value) break; } else break; // if no constraints are set: stop } } else this.p.err("unexpected '" + this.p.tk + "'"); } else this.p.err("expected ':=' or '=' or 'in'"); } // expr = // or; parseExpr() { this.parseOr(); } // or = // and [ "or" and ]; parseOr() { this.parseAnd(); if(this.p.is('or')) { let op = this.p.tk; this.p.next(); this.parseAnd(); let o2 = this.p.q.stack.pop(); let o1 = this.p.q.stack.pop(); if(o1.type == symtype.T_BOOL && o2.type == symtype.T_BOOL) { this.p.pushSym(symtype.T_BOOL, o1.value || o2.value); } else this.p.err("types not compatible for '" + op + "' (must be boolean)"); } } // and = // equal [ "and" equal ]; parseAnd() { this.parseEqual(); if(this.p.is('and')) { let op = this.p.tk; this.p.next(); this.parseEqual(); let o2 = this.p.q.stack.pop(); let o1 = this.p.q.stack.pop(); if(o1.type == symtype.T_BOOL && o2.type == symtype.T_BOOL) { this.p.pushSym(symtype.T_BOOL, o1.value && o2.value); } else this.p.err("types not compatible for '" + op + "' (must be boolean)"); } } // equal = // compare [ ("=="|"!=") compare ]; parseEqual() { this.parseCompare(); if(this.p.is('==') || this.p.is('!=')) { let op = this.p.tk; this.p.next(); this.parseCompare(); let o2 = this.p.q.stack.pop(); let o1 = this.p.q.stack.pop(); if(o1.type == symtype.T_REAL && o2.type == symtype.T_REAL) { let isEqual = math.abs(o1.value-o2.value) < 1e-14; switch(op) { case '==': this.p.pushSym(symtype.T_BOOL, isEqual); break; case '!=': this.p.pushSym(symtype.T_BOOL, !isEqual); break; } } else if(o1.type == symtype.T_MATRIX && o2.type == symtype.T_MATRIX) { let isEqual = LinAlg.SellLinAlg.mat_compare_numerically(o1.value, o2.value); switch(op) { case '==': this.p.pushSym(symtype.T_BOOL, isEqual); break; case '!=': this.p.pushSym(symtype.T_BOOL, !isEqual); break; } } else this.p.err("types not compatible for '" + op + "'"); } } // compare = // add [ ("<="|"<"|">="|">") add ]; parseCompare() { this.parseAdd(); if(this.p.is('<=') || this.p.is('<') || this.p.is('>=') || this.p.is('>')) { let op = this.p.tk; this.p.next(); this.parseAdd(); let o2 = this.p.q.stack.pop(); let o1 = this.p.q.stack.pop(); if(o1.type == symtype.T_REAL && o2.type == symtype.T_REAL) { switch(op) { case '<=': this.p.pushSym(symtype.T_BOOL, o1.value <= o2.value); break; case '<': this.p.pushSym(symtype.T_BOOL, o1.value < o2.value); break; case '>=': this.p.pushSym(symtype.T_BOOL, o1.value >= o2.value); break; case '>': this.p.pushSym(symtype.T_BOOL, o1.value > o2.value); break; } } else this.p.err("types not compatible for '" + op + "'"); } } // add = // mul { ("+"|"-") mul }; parseAdd() { this.parseMul(); while(this.p.is('+') || this.p.is('-')) { let op = this.p.tk; this.p.next(); this.parseMul(); let o2 = this.p.q.stack.pop(); let o1 = this.p.q.stack.pop(); if(o1.type == symtype.T_REAL && o2.type == symtype.T_REAL) { switch(op) { case '+': this.p.pushSym(symtype.T_REAL, o1.value + o2.value); break; case '-': this.p.pushSym(symtype.T_REAL, o1.value - o2.value); break; } } else if( (o1.type == symtype.T_REAL || o1.type == symtype.T_COMPLEX) && (o2.type == symtype.T_REAL || o2.type == symtype.T_COMPLEX)) { switch(op) { case '+': this.p.pushSym(symtype.T_COMPLEX, math.add(o1.value, o2.value)); break; case '-': this.p.pushSym(symtype.T_COMPLEX, math.subtract(o1.value, o2.value)); break; } } else if(o1.type == symtype.T_MATRIX && o2.type == symtype.T_MATRIX) { let o1_m = o1.value.size()[0]; let o1_n = o1.value.size()[1]; let o2_m = o2.value.size()[0]; let o2_n = o2.value.size()[1]; if(o1_m != o2_m || o1_n != o2_n) this.p.err("cannot apply '" + op + "' on (" + o1_m + "x" + o1_n + ") and (" + o2_m + "x" + o2_n + ") matrices"); this.p.pushSym(symtype.T_MATRIX, op=="+" ? math.add(o1.value, o2.value) : math.subtract(o1.value, o2.value)); } else this.p.err("types not compatible for '" + op + "'"); } } // mul = // pow { ("*"|"/"|"mod") pow }; parseMul() { this.parsePow(); while(this.p.is('*') || this.p.is('/') || this.p.is('mod')) { let op = this.p.tk; this.p.next(); this.parsePow(); let o2 = this.p.q.stack.pop(); let o1 = this.p.q.stack.pop(); if(o1.type == symtype.T_REAL && o2.type == symtype.T_REAL) { switch(op) { case '*': this.p.pushSym(symtype.T_REAL, o1.value * o2.value); break; case '/': this.p.pushSym(symtype.T_REAL, o1.value / o2.value); break; case 'mod': if(!this.p.isNumberInt(o1.value) || !this.p.isNumberInt(o2.value)) this.p.err("operator 'mod' expectes integral operands"); this.p.pushSym(symtype.T_REAL, math.mod(math.round(o1.value), math.round(o2.value))); break; } } else if( (o1.type == symtype.T_REAL || o1.type == symtype.T_COMPLEX) && (o2.type == symtype.T_REAL || o2.type == symtype.T_COMPLEX)) { switch(op) { case '*': this.p.pushSym(symtype.T_COMPLEX, math.multiply(o1.value, o2.value)); break; case '/': this.p.pushSym(symtype.T_COMPLEX, math.divide(o1.value, o2.value)); break; case 'mod': this.p.err("types not compatible for '" + op + "'"); break; } } else if(o1.type == symtype.T_MATRIX && o2.type == symtype.T_MATRIX) { switch(op) { case '*': let o1_m = o1.value.size()[0]; let o1_n = o1.value.size()[1]; let o2_m = o2.value.size()[0]; let o2_n = o2.value.size()[1]; if(o1_n != o2_m) this.p.err("cannot multiply (" + o1_m + "x" + o1_n + ") and (" + o2_m + "x" + o2_n + ") matrices"); this.p.pushSym(symtype.T_MATRIX, math.multiply(o1.value, o2.value)); break; case '/': case 'mod': this.p.err("types not compatible for '" + op + "'"); break; } } else if(o1.type == symtype.T_MATRIX && o2.type == symtype.T_REAL) { switch(op) { case '*': this.p.pushSym(symtype.T_MATRIX, math.multiply(o1.value, o2.value)); break; case 'mod': // TODO: must test, if all elements of matrix are integral! if(!this.p.isNumberInt(o2.value)) this.p.err("operator 'mod' expectes integral operands"); this.p.pushSym(symtype.T_MATRIX, LinAlg.SellLinAlg.mat_mod(o1.value, o2.value)); break; default: console.log(o1.value.toString()) this.p.err("types not compatible for '" + op + "'"); break; } } else if(o1.type == symtype.T_REAL && o2.type == symtype.T_MATRIX) { switch(op) { case '*': this.p.pushSym(symtype.T_MATRIX, math.multiply(o1.value, o2.value)); break; default: this.p.err("types not compatible for '" + op + "'"); break; } } else this.p.err("types not compatible for '" + op + "'"); } } // pow = // unary { ("^") unary }; parsePow() { this.parseUnary(); while(this.p.is('^')) { let op = this.p.tk; this.p.next(); this.parseUnary(); let o2 = this.p.q.stack.pop(); let o1 = this.p.q.stack.pop(); if(o1.type == symtype.T_REAL && o2.type == symtype.T_REAL) { switch(op) { case '^': this.p.pushSym(symtype.T_REAL, math.pow(o1.value, o2.value)); break; } } else if( (o1.type == symtype.T_REAL || o1.type == symtype.T_COMPLEX) && (o2.type == symtype.T_REAL || o2.type == symtype.T_COMPLEX)) { switch(op) { case '^': this.p.pushSym(symtype.T_COMPLEX, math.pow(o1.value, o2.value)); break; } } else if(o1.type == symtype.T_MATRIX && o2.type == symtype.T_MATRIX_TRANSPOSE) { switch(op) { case '^': this.p.pushSym(symtype.T_MATRIX, math.transpose(o1.value)); break; } } else this.p.err("types not compatible for '" + op + "'"); } } // unary = ( // | "-" unary; // | INT [ "." INT ] // | "true" | "false" // | "not" unary // | "i" | "j" | "T" // | function_call // | ID [ "(" add { "," add } ")" ] // | "..." // | matrix // | "(" expr ")" // ) [ factorial ]; parseUnary() { if(this.p.is('-')) { this.p.next(); this.parseUnary(); let o = this.p.q.stack.pop(); if(o.type == symtype.T_REAL) this.p.pushSym(symtype.T_REAL, - o.value); else this.p.err("unary '-' must be followed by type real"); } else if(this.p.isInt()) { let value = parseInt(this.p.tk); this.p.next(); if(this.p.is('.')) { this.p.next(); if(!Lexer.isInteger(this.p.tk)) this.p.err("expected decimal places after '.'"); value = parseFloat(""+value+"."+this.p.tk); this.p.next(); } this.p.q.stack.push(new SellSymbol(symtype.T_REAL, value)); } else if(this.p.is("true")) { this.p.next(); this.p.q.stack.push(new SellSymbol(symtype.T_BOOL, true)); } else if(this.p.is("false")) { this.p.next(); this.p.q.stack.push(new SellSymbol(symtype.T_BOOL, false)); } else if(this.p.is("not")) { this.p.next(); this.parseUnary(); let u = this.p.q.stack.pop(); if(u.type != symtype.T_BOOL) this.p.err("expected boolean datatype as argument for 'not'"); this.p.q.stack.push(new SellSymbol(symtype.T_BOOL, !u.value)); } else if(this.p.is("i") || this.p.is("j")) { this.p.q.stack.push(new SellSymbol(symtype.T_COMPLEX, math.complex(0,1))); this.p.next(); } else if(this.p.is("T")) { this.p.q.stack.push(new SellSymbol(symtype.T_MATRIX_TRANSPOSE, "T")); this.p.next(); } else if(this.getFunctionList().includes(this.p.tk)) { this.parseFunctionCall(); } else if(this.p.isIdent()) { let id = this.p.tk; this.p.next(); if((id in this.p.q.symbols) == false) this.p.err("unknown identifier '" + id + "'"); let sym = this.p.q.symbols[id]; if(sym.type == symtype.T_FUNCTION && this.p.is("(")) { this.p.next(); this.parseAdd(); let args = [ this.p.q.stack.pop() ]; while(this.p.is(",")) { this.p.next(); this.parseAdd(); args.push(this.p.q.stack.pop()); } let term : SellSymTerm = sym.value; let termSymIDs = term.symbolIDs; if(termSymIDs.length != args.length) this.p.err("number of arguments does not correspond to function definition") let args_dict = {}; for(let i=0; i<termSymIDs.length; i++) { if(args[i].type != symtype.T_REAL) this.p.err("all arguements must be scalar and real valued"); args_dict[termSymIDs[i]] = args[i].value; } let v = term.eval(args_dict); let v_sym = new SellSymbol(symtype.T_REAL, v); this.p.q.stack.push(v_sym); this.p.terminal(')'); } else this.p.q.stack.push(sym); } else if(this.p.is("...")) { this.p.next(); this.p.q.stack.push(new SellSymbol(symtype.T_DOTS)); } else if(this.p.is("{")) { this.parseSet(); } else if(this.p.is("[")) { this.parseMatrix(); } else if(this.p.is("(")) { this.p.next(); this.parseExpr(); this.p.terminal(")"); } else this.p.err("expected unary, got '" + this.p.tk + "'"); // postfix if(this.p.is('!')) this.parseFactorial(); } // matrix = // "[" "[" expr {"," expr} "]" { "," "[" expr {"," expr} "]" } "]"; parseMatrix() { this.p.terminal('['); let cols = -1; let rows = 0; let elements = []; let elementsType = null; while(this.p.is('[') || this.p.is(',')) { if(rows > 0) this.p.terminal(','); this.p.terminal('['); let col = 0; while(!this.p.is(']') && !this.p.is('§EOF')) { if(col > 0) this.p.terminal(','); this.parseExpr(); let element = this.p.q.stack.pop(); if(element.type != symtype.T_REAL && element.type != symtype.T_FUNCTION) this.p.err("matrix element must be of type real or function"); if(elementsType == null) elementsType = element.type; else if(element.type != elementsType) this.p.err("all matrix elements must have same type"); elements.push(element.value); col ++; } if(cols == -1) cols = col; else if(col != cols) this.p.err('matrix has different number of cols per row'); this.p.terminal(']'); rows ++; } if(rows < 1 || cols < 1) this.p.err('matrix must have at least one row and one column'); this.p.terminal(']'); // create matrix: let matrix = null; let matrixType = elementsType == symtype.T_REAL ? symtype.T_MATRIX : symtype.T_MATRIX_OF_FUNCTIONS; if(matrixType == symtype.T_MATRIX) { matrix = math.zeros(rows, cols); sellassert(elements.length == rows*cols); for(let i=0; i<rows; i++) for(let j=0; j<cols; j++) matrix = LinAlg.SellLinAlg.mat_set_element(matrix, i, j, elements[i*cols+j]); } else { // matrixType == symtype.T_MATRIX_OF_FUNCTIONS matrix = new SellSymTerm_Matrix(rows, cols, elements); } this.p.q.stack.push( new SellSymbol(matrixType, matrix)); } // function_call = // ("abs"|"binomial"|"integrate"|"conj"|"sqrt"|"xgcd"|"det"|"rank"|"inv" // |"eye"|"eigenvalues_sym"|"triu"|"sin"|"cos"|"asin"|"acos"|"tan" // |"atan"|"norm2"|"dot"|"cross"|"linsolve"| "is_zero") // ID "(" [ expr {"," expr} ] ")"; parseFunctionCall() { this.p.ident(); let functionName = this.p.id; this.p.terminal('('); // get parameters =: p let p = []; while(!this.p.is(')') && !this.p.is('§EOL') && !this.p.is('§EOF')) { if(p.length > 0) this.p.terminal(','); if(functionName == "integrate" && p.length==1) { // second parametr of "integrate" must be a string this.p.ident(); p.push(this.p.id); } else { this.parseExpr(); p.push(this.p.q.stack.pop()); } } this.p.terminal(')'); // calculate if(functionName === 'abs') { if(p.length != 1 || (p[0].type != symtype.T_REAL && p[0].type != symtype.T_COMPLEX)) this.p.err("signature must be 'abs(real|complex)'"); this.p.pushSym(symtype.T_REAL, math.abs(p[0].value)); } else if(functionName === 'sqrt') { if(p.length != 1 || (p[0].type != symtype.T_REAL && p[0].type != symtype.T_COMPLEX)) this.p.err("signature must be 'sqrt(real|complex)'"); let v = math.sqrt(p[0].value); let isComplex = math.typeOf(v) == 'Complex'; this.p.pushSym(isComplex ? symtype.T_COMPLEX : symtype.T_REAL, v); } else if(['sin','asin','cos','acos','tan','atan'].includes(functionName)) { if(p.length != 1 || p[0].type != symtype.T_REAL) this.p.err("signature must be '" + functionName + "(real)'"); let v=0.0; switch(functionName) { case 'sin': v = math.sin(p[0].value); break; case 'asin': v = math.asin(p[0].value); break; case 'cos': v = math.cos(p[0].value); break; case 'acos': v = math.acos(p[0].value); break; case 'tan': v = math.tan(p[0].value); break; case 'atan': v = math.atan(p[0].value); break; default: this.p.err("UNIMPLEMENTED: " + functionName) } this.p.pushSym(symtype.T_REAL, v); } else if(functionName === 'conj') { if(p.length != 1 || p[0].type != symtype.T_COMPLEX) this.p.err("signature must be 'conj(complex)'"); this.p.pushSym(symtype.T_COMPLEX, math.conj(p[0].value)); } else if(functionName === 'binomial') { if(p.length != 2 || p[0].type != symtype.T_REAL || p[1].type != symtype.T_REAL || !this.p.isNumberInt(p[0].value) || !this.p.isNumberInt(p[1].value)) this.p.err("signature must be 'binomial(int,int)'"); this.p.pushSym(symtype.T_REAL, math.combinations(math.round(p[0].value), math.round(p[1].value))); } else if(functionName === 'xgcd') { if(p.length != 3 || p[0].type != symtype.T_REAL || p[1].type != symtype.T_REAL || p[2].type != symtype.T_REAL || !this.p.isNumberInt(p[0].value) || !this.p.isNumberInt(p[1].value) || !this.p.isNumberInt(p[2].value)) this.p.err("signature must be 'xgcd(int,int,int)'"); this.p.pushSym(symtype.T_REAL, math.subset(math.xgcd(p[0].value, p[1].value), math.index(p[2].value - 1))); } else if(functionName === 'integrate') { if(p.length != 4 || p[0].type != symtype.T_FUNCTION || typeof(p[1]) !== 'string' || p[2].type != symtype.T_REAL || p[3].type != symtype.T_REAL ) this.p.err("signature must be 'integrate(function, string, real, real)'"); let v = p[0].value.integrateNumerically(p[1], p[2].value, p[3].value); let precision = 0.001; this.p.pushSym(symtype.T_REAL, v, precision); } else if(['det','rank','inv','eigenvalues_sym','triu','norm2'].includes(functionName)) { if(p.length != 1 || p[0].type != symtype.T_MATRIX) this.p.err("signature must be '" + functionName + "(matrix)'"); if(functionName === 'det') this.p.pushSym(symtype.T_REAL, math.det(p[0].value)); else if(functionName === 'rank') this.p.pushSym(symtype.T_REAL, LinAlg.SellLinAlg.mat_rank(p[0].value)); else if(functionName === 'inv') this.p.pushSym(symtype.T_MATRIX, math.inv(p[0].value)); else if(functionName === 'eigenvalues_sym') { if(!LinAlg.SellLinAlg.mat_is_symmetric(p[0].value)) this.p.err("matrix is not symmetric"); let eigs = (math.eigs(p[0].value).values as any)._data; let set = []; for(let i=0; i<eigs.length; i++) set.push(new SellSymbol(symtype.T_REAL, eigs[i])); this.p.pushSym(symtype.T_SET, set); } else if(functionName === 'triu') { this.p.pushSym(symtype.T_MATRIX, LinAlg.SellLinAlg.mat_triu(p[0].value)); } else if(functionName === 'norm2') { this.p.pushSym(symtype.T_REAL, LinAlg.SellLinAlg.mat_norm2(p[0].value)); } else sellassert(false); } else if(functionName === 'eye') { if(p.length != 1 || p[0].type != symtype.T_REAL || !this.p.isNumberInt(p[0].value)) this.p.err("signature must be 'eye(integer)'"); this.p.pushSym(symtype.T_MATRIX, math.identity(math.round(p[0].value))); } else if(functionName === 'dot') { if(p.length != 2 || p[0].type != symtype.T_MATRIX || p[1].type != symtype.T_MATRIX) this.p.err("signature must be 'dot(columnVector,columnVector)'"); if(LinAlg.SellLinAlg.mat_get_col_count(p[0].value) != 1) this.p.err("signature must be 'dot(columnVector,columnVector)'"); if(LinAlg.SellLinAlg.mat_get_col_count(p[1].value) != 1) this.p.err("signature must be 'dot(columnVector,columnVector)'"); if(LinAlg.SellLinAlg.mat_get_row_count(p[0].value) != LinAlg.SellLinAlg.mat_get_row_count(p[1].value)) this.p.err("vectors in 'dot(..)' must have equal length"); this.p.pushSym(symtype.T_REAL, LinAlg.SellLinAlg.mat_vecdot(p[0].value, p[1].value)); } else if(functionName === 'cross') { if(p.length != 2 || p[0].type != symtype.T_MATRIX || p[1].type != symtype.T_MATRIX) this.p.err("signature must be 'cross(columnVector,columnVector)'"); if(LinAlg.SellLinAlg.mat_get_col_count(p[0].value) != 1) this.p.err("signature must be 'cross(columnVector,columnVector)'"); if(LinAlg.SellLinAlg.mat_get_col_count(p[1].value) != 1) this.p.err("signature must be 'cross(columnVector,columnVector)'"); if(LinAlg.SellLinAlg.mat_get_row_count(p[0].value) != 3) this.p.err("vectors in 'cross(..)' must have length 3"); if(LinAlg.SellLinAlg.mat_get_row_count(p[1].value) != 3) this.p.err("vectors in 'cross(..)' must have length 3"); this.p.pushSym(symtype.T_MATRIX, LinAlg.SellLinAlg.mat_veccross(p[0].value, p[1].value)); } else if(functionName === 'linsolve') { if(p.length != 2 || p[0].type != symtype.T_MATRIX || p[1].type != symtype.T_MATRIX) this.p.err("signature must be 'linsolve(matrix,columnVector)'"); if(LinAlg.SellLinAlg.mat_get_col_count(p[0].value) != LinAlg.SellLinAlg.mat_get_row_count(p[0].value)) this.p.err("matrix must be square, i.e. m=n"); if(LinAlg.SellLinAlg.mat_get_col_count(p[1].value) != 1) this.p.err("second parameter must be a colum vector"); if(LinAlg.SellLinAlg.mat_get_row_count(p[0].value) != LinAlg.SellLinAlg.mat_get_row_count(p[1].value)) this.p.err("number of rows of matrix and does not match vector"); this.p.pushSym(symtype.T_MATRIX, LinAlg.SellLinAlg.linsolve(p[0].value, p[1].value)); } else if(functionName === 'is_zero') { if(p.length != 1 || p[0].type != symtype.T_MATRIX) this.p.err("signature must be 'is_zero(matrix)'"); this.p.pushSym(symtype.T_BOOL, LinAlg.SellLinAlg.mat_is_zero(p[0].value)); } else if(functionName === 'min') { if(p.length != 1 || p[0].type != symtype.T_SET) this.p.err("signature must be 'min(set)'"); let v = 1e13; for(let i=0; i<p[0].value.length; i++) { if(p[0].value[i].value < v) v = p[0].value[i].value; } this.p.pushSym(symtype.T_REAL, v); } else if(functionName === 'max') { if(p.length != 1 || p[0].type != symtype.T_SET) this.p.err("signature must be 'max(set)'"); let v = -1e13; for(let i=0; i<p[0].value.length; i++) { if(p[0].value[i].value > v) v = p[0].value[i].value; } this.p.pushSym(symtype.T_REAL, v); } else this.p.err("unimplemented function call '" + functionName + "'"); } // factorial = // "!"; parseFactorial() { this.p.terminal('!'); let op = '!'; let o = this.p.q.stack.pop(); if(o.type == symtype.T_REAL) this.p.pushSym(symtype.T_REAL, math.factorial(o.value)); else this.p.err("types not compatible for '" + op + "'"); } // matrix_def = // "MM" "(" expr "x" expr "|" expr [ {"," ("invertible"|"symmetric")} ] ")"; parseMatrixDef() { this.p.terminal("MM"); this.p.terminal("("); // number of rows this.parseExpr(); let m = this.p.q.stack.pop(); if(m.type !== symtype.T_REAL || !this.p.isNumberInt(m.value)) this.p.err("expected integer for the number of rows"); let rows = math.round(m.value); if(rows <= 0) this.p.err("number of matrix cols must be > 0 (actually is '" + rows + "')") // times this.p.terminal("x"); // number of columns this.parseExpr(); let n = this.p.q.stack.pop(); if(n.type !== symtype.T_REAL || !this.p.isNumberInt(n.value)) this.p.err("expected integer for the number of columns"); let cols = math.round(n.value); if(cols <= 0) this.p.err("number of matrix rows must be > 0 (actually is '" + cols + "')") // set this.p.terminal("|"); this.parseExpr(); let set = this.p.q.stack.pop(); if(set.type !== symtype.T_SET) this.p.err("expected set from which matrix elements are drawn"); // properties let invertible = false; let symmetric = false; while(this.p.is(",")) { this.p.next(); let prop = this.p.tk; switch(prop) { case 'invertible': invertible = true; break; case 'symmetric': symmetric = true; break; default: this.p.err("unknown property '" + prop +"'"); } this.p.next(); } // end this.p.terminal(")"); // create symbol this.p.pushSym(symtype.T_MATRIX_DEF, [rows, cols, set, invertible, symmetric]); } // set = // "{" [ expr { "," expr } ] "}"; parseSet() { this.p.terminal("{"); let idx = 0; let sym = new SellSymbol(symtype.T_SET); sym.value = []; let hasDot = false; while(!this.p.is('}') && !this.p.is('§EOF')) { if(idx > 0) this.p.terminal(","); this.parseExpr(); let symi = this.p.q.stack.pop(); sym.value.push(symi); if(symi.type !== symtype.T_REAL) { if(idx==2 && symi.type === symtype.T_DOTS) hasDot = true; else if(symi.type === symtype.T_COMPLEX) sym.type = symtype.T_COMPLEX_SET; else this.p.err("set must consist of real values only"); } idx ++; } if(hasDot && idx != 4 || hasDot && sym.type == symtype.T_COMPLEX_SET) this.p.err("if set contains '...', then it must have 4 real-valued elements") if(sym.type == symtype.T_COMPLEX_SET) { for(let i=0; i<sym.value.length; i++) { if(sym.value[i].type == symtype.T_REAL) sym.value[i].value = math.complex(sym.value[i].value, 0); } } this.p.q.stack.push(sym); this.p.terminal("}"); } }