2021-07-20 13:23:13 +00:00
|
|
|
|
/**
|
2022-03-05 16:49:35 +00:00
|
|
|
|
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
2022-01-12 17:47:17 +00:00
|
|
|
|
* Copyright (C) 2022 Ad5001
|
2021-07-20 13:23:13 +00:00
|
|
|
|
*
|
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
* (at your option) any later version.
|
|
|
|
|
*
|
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
.pragma library
|
|
|
|
|
|
2022-01-20 17:19:36 +00:00
|
|
|
|
.import "reference.js" as Reference
|
2021-07-20 13:23:13 +00:00
|
|
|
|
|
|
|
|
|
const WHITESPACES = " \t\n\r"
|
|
|
|
|
const STRING_LIMITORS = '"\'`';
|
2022-10-18 23:16:54 +00:00
|
|
|
|
const OPERATORS = "+-*/^%?:=!><";
|
2022-10-23 13:43:47 +00:00
|
|
|
|
const PUNCTUTATION = "()[],.";
|
2022-10-20 14:23:12 +00:00
|
|
|
|
const NUMBER_CHARS = "0123456789"
|
2021-07-20 13:23:13 +00:00
|
|
|
|
const IDENTIFIER_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789_₀₁₂₃₄₅₆₇₈₉αβγδεζηθκλμξρςστφχψωₐₑₒₓₔₕₖₗₘₙₚₛₜ"
|
|
|
|
|
|
2022-10-18 23:16:54 +00:00
|
|
|
|
var TokenType = {
|
2021-07-20 13:23:13 +00:00
|
|
|
|
// Expression type
|
2022-10-18 23:16:54 +00:00
|
|
|
|
"WHITESPACE": "WHITESPACE",
|
2022-10-23 13:43:47 +00:00
|
|
|
|
"IDENTIFIER": "IDENTIFIER",
|
2022-10-18 23:16:54 +00:00
|
|
|
|
"VARIABLE": "VARIABLE",
|
|
|
|
|
"CONSTANT": "CONSTANT",
|
|
|
|
|
"FUNCTION": "FUNCTION",
|
|
|
|
|
"OPERATOR": "OPERATOR",
|
|
|
|
|
"PUNCT": "PUNCT",
|
|
|
|
|
"NUMBER": "NUMBER",
|
|
|
|
|
"STRING": "STRING",
|
|
|
|
|
"UNKNOWN": "UNKNOWN"
|
2021-07-20 13:23:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class Token {
|
2022-10-19 21:44:04 +00:00
|
|
|
|
constructor(type, value, startPosition) {
|
2021-07-20 13:23:13 +00:00
|
|
|
|
this.type = type;
|
|
|
|
|
this.value = value;
|
2022-10-19 21:44:04 +00:00
|
|
|
|
this.startPosition = startPosition
|
2021-07-20 13:23:13 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class ExpressionTokenizer {
|
2022-10-23 13:43:47 +00:00
|
|
|
|
constructor(input, tokenizeWhitespaces = false, differentiateIdentifiers = false, errorOnUnknown = true) {
|
|
|
|
|
this.input = input
|
|
|
|
|
this.currentToken = null
|
2022-10-18 23:16:54 +00:00
|
|
|
|
this.tokenizeWhitespaces = tokenizeWhitespaces
|
2022-10-23 13:43:47 +00:00
|
|
|
|
this.differentiateIdentifiers = differentiateIdentifiers
|
2022-10-18 23:16:54 +00:00
|
|
|
|
this.errorOnUnknown = errorOnUnknown
|
2021-07-20 13:23:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
skipWhitespaces() {
|
|
|
|
|
while(!this.input.atEnd() && WHITESPACES.includes(this.input.peek()))
|
|
|
|
|
this.input.next();
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-18 23:16:54 +00:00
|
|
|
|
readWhitespaces() {
|
|
|
|
|
let included = "";
|
|
|
|
|
while(!this.input.atEnd() && WHITESPACES.includes(this.input.peek())) {
|
|
|
|
|
included += this.input.next();
|
|
|
|
|
}
|
2022-10-19 21:44:04 +00:00
|
|
|
|
return new Token(TokenType.WHITESPACE, included, this.input.position-included.length)
|
2022-10-18 23:16:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-07-20 13:23:13 +00:00
|
|
|
|
readString() {
|
|
|
|
|
let delimitation = this.input.peek();
|
|
|
|
|
if(STRING_LIMITORS.includes(delimitation)) {
|
|
|
|
|
this.input.skip(delimitation)
|
|
|
|
|
let included = "";
|
|
|
|
|
let justEscaped = false;
|
|
|
|
|
while(!this.input.atEnd() && (!STRING_LIMITORS.includes(this.input.peek()) || justEscaped)) {
|
|
|
|
|
justEscaped = this.input.peek() == "\\"
|
|
|
|
|
if(!justEscaped)
|
|
|
|
|
included += this.input.next();
|
|
|
|
|
}
|
|
|
|
|
this.input.skip(delimitation)
|
2022-10-19 21:44:04 +00:00
|
|
|
|
let token = new Token(TokenType.STRING, included, this.input.position-included.length)
|
2022-10-18 23:16:54 +00:00
|
|
|
|
token.limitator = delimitation
|
|
|
|
|
return token
|
2021-07-20 13:23:13 +00:00
|
|
|
|
} else {
|
|
|
|
|
this.input.raise("Unexpected " + delimitation + ". Expected string delimitator")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
readNumber() {
|
|
|
|
|
let included = "";
|
|
|
|
|
let hasDot = false;
|
2022-10-20 14:23:12 +00:00
|
|
|
|
while(!this.input.atEnd() && (NUMBER_CHARS.includes(this.input.peek()) || this.input.peek() == '.')) {
|
2021-07-20 13:23:13 +00:00
|
|
|
|
if(this.input.peek() == ".") {
|
|
|
|
|
if(hasDot) this.input.raise("Unexpected '.'. Expected digit")
|
|
|
|
|
hasDot = true;
|
|
|
|
|
}
|
|
|
|
|
included += this.input.next();
|
|
|
|
|
}
|
2022-10-19 21:44:04 +00:00
|
|
|
|
return new Token(TokenType.NUMBER, included, this.input.position-included.length)
|
2022-10-18 23:16:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
readOperator() {
|
|
|
|
|
let included = "";
|
|
|
|
|
while(!this.input.atEnd() && OPERATORS.includes(this.input.peek())) {
|
|
|
|
|
included += this.input.next();
|
|
|
|
|
}
|
2022-10-19 21:44:04 +00:00
|
|
|
|
return new Token(TokenType.OPERATOR, included, this.input.position-included.length)
|
2021-07-20 13:23:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
readIdentifier() {
|
|
|
|
|
let identifier = "";
|
2022-10-18 23:16:54 +00:00
|
|
|
|
while(!this.input.atEnd() && IDENTIFIER_CHARS.includes(this.input.peek().toLowerCase())) {
|
2021-07-20 13:23:13 +00:00
|
|
|
|
identifier += this.input.next();
|
|
|
|
|
}
|
2022-10-23 13:43:47 +00:00
|
|
|
|
let identifierLC = identifier.toLowerCase()
|
|
|
|
|
if(Reference.CONSTANTS_LIST.includes(identifierLC)) {
|
|
|
|
|
return new Token(TokenType.CONSTANT, identifierLC, this.input.position-identifier.length)
|
|
|
|
|
} else if(Reference.FUNCTIONS_LIST.includes(identifierLC)) {
|
|
|
|
|
return new Token(TokenType.FUNCTION, identifierLC, this.input.position-identifier.length)
|
|
|
|
|
} else if(Reference.UNARY_OPERATORS.includes(identifierLC) ||
|
|
|
|
|
Reference.BINARY_OPERATORS.includes(identifierLC) ||
|
|
|
|
|
Reference.TERTIARY_OPERATORS.includes(identifierLC)
|
|
|
|
|
) {
|
|
|
|
|
return new Token(TokenType.OPERATOR, identifierLC, this.input.position-identifier.length)
|
2021-07-20 13:23:13 +00:00
|
|
|
|
} else {
|
2022-10-19 21:44:04 +00:00
|
|
|
|
return new Token(TokenType.VARIABLE, identifier, this.input.position-identifier.length)
|
2021-07-20 13:23:13 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
readNextToken() {
|
2022-10-18 23:16:54 +00:00
|
|
|
|
if(!this.tokenizeWhitespaces)
|
|
|
|
|
this.skipWhitespaces()
|
|
|
|
|
if(this.input.atEnd()) return null;
|
|
|
|
|
let c = this.input.peek();
|
|
|
|
|
if(this.tokenizeWhitespaces && WHITESPACES.includes(c)) return this.readWhitespaces();
|
2021-07-20 13:23:13 +00:00
|
|
|
|
if(STRING_LIMITORS.includes(c)) return this.readString();
|
|
|
|
|
if(NUMBER_CHARS.includes(c)) return this.readNumber();
|
2022-10-18 23:16:54 +00:00
|
|
|
|
if(IDENTIFIER_CHARS.includes(c.toLowerCase())) return this.readIdentifier();
|
|
|
|
|
if(OPERATORS.includes(c)) return this.readOperator();
|
2022-10-19 21:44:04 +00:00
|
|
|
|
if(Reference.CONSTANTS_LIST.includes(c)) return new Token(TokenType.CONSTANT, this.input.next(), this.input.position-1);
|
|
|
|
|
if(PUNCTUTATION.includes(c)) return new Token(TokenType.PUNCT, this.input.next(), this.input.position-1);
|
2022-10-18 23:16:54 +00:00
|
|
|
|
if(this.errorOnUnknown)
|
2022-10-23 13:43:47 +00:00
|
|
|
|
this.raise("Unknown token character " + c)
|
2022-10-18 23:16:54 +00:00
|
|
|
|
else
|
2022-10-19 21:44:04 +00:00
|
|
|
|
return new Token(TokenType.UNKNOWN, this.input.next(), this.input.position-1);
|
2021-07-20 13:23:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
peek() {
|
|
|
|
|
if(this.currentToken == null) this.currentToken = this.readNextToken();
|
|
|
|
|
return this.currentToken;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
next() {
|
|
|
|
|
let tmp;
|
|
|
|
|
if(this.currentToken == null)
|
|
|
|
|
tmp = this.readNextToken();
|
|
|
|
|
else
|
|
|
|
|
tmp = this.currentToken;
|
|
|
|
|
this.currentToken = null;
|
|
|
|
|
return tmp;
|
|
|
|
|
}
|
2022-10-23 13:43:47 +00:00
|
|
|
|
|
|
|
|
|
read(type, value) {
|
|
|
|
|
let next = this.next()
|
|
|
|
|
if(type != null && next.type != type)
|
|
|
|
|
this.raise(`Unexpected ${next.type.toLowerCase()} ${next.value}. Expected type was ${type.toLowerCase()}.`);
|
|
|
|
|
if(value != null && next.value == value)
|
|
|
|
|
this.raise(`Unexpected ${next.type.toLowerCase()} ${next.value}. Expected value was ${value}.`);
|
|
|
|
|
return next
|
|
|
|
|
}
|
2021-07-20 13:23:13 +00:00
|
|
|
|
|
|
|
|
|
atEnd() {
|
|
|
|
|
return this.peek() == null;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-23 13:43:47 +00:00
|
|
|
|
skip(type, value) {
|
|
|
|
|
this.read(type, value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
raise(message) {
|
|
|
|
|
this.input.raise(message)
|
2021-07-20 13:23:13 +00:00
|
|
|
|
}
|
|
|
|
|
}
|