Implementing parseBinaryOperator
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
d7704110dd
commit
80d0dad63a
3 changed files with 59 additions and 57 deletions
|
@ -614,6 +614,9 @@ class IntegralElement extends FunctionElement {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
class UnaryOperation extends AbstractSyntaxElement {}
|
||||
|
||||
class BinaryOperation extends AbstractSyntaxElement {
|
||||
type = ASEType.BINARY_OPERATION
|
||||
|
||||
|
@ -884,49 +887,5 @@ class Negation extends AbstractSyntaxElement {
|
|||
}
|
||||
}
|
||||
|
||||
class Negation extends AbstractSyntaxElement {
|
||||
type = ASEType.NEGATION
|
||||
|
||||
constructor(expression) {
|
||||
this.expression = expression
|
||||
}
|
||||
|
||||
execute(variables) {
|
||||
if(variables.includes(this.arrayName)) {
|
||||
let index = this.astIndex.execute(variables)
|
||||
if(index % 1 != 0 || index < 0) { // Float index.
|
||||
throw new EvalError("Non-integer array index " + index + " used as array index for " + this.variableName + ".")
|
||||
} else if(variables[this.arrayName].length <= index) {
|
||||
throw new EvalError("Out-of-range index " + index + " used as array index for " + this.variableName + ".")
|
||||
} else {
|
||||
return variables[this.arrayName][index]
|
||||
}
|
||||
} else {
|
||||
throw new EvalError("Unknown variable " + this.variableName + ".")
|
||||
}
|
||||
|
||||
toLatex() {
|
||||
return this.variableName
|
||||
}
|
||||
}
|
||||
|
||||
simplify() {
|
||||
return new Negation(this.expression.simplify())
|
||||
}
|
||||
|
||||
derivation(variable) {
|
||||
return new Negation(this.expression.derivation(variable))
|
||||
}
|
||||
|
||||
integral(variable) {
|
||||
return new Negation(this.expression.integral(variable))
|
||||
}
|
||||
|
||||
toLatex() {
|
||||
return '-' + this.expression.toLatex()
|
||||
}
|
||||
|
||||
isConstant() {
|
||||
return this.expression.isConstant()
|
||||
}
|
||||
}
|
||||
|
||||
class TertiaryOperation extends AbstractSyntaxElement {}
|
||||
|
|
|
@ -94,9 +94,13 @@ class ExpressionBuilder {
|
|||
*/
|
||||
handleSingle(token) {
|
||||
switch(token.type) {
|
||||
case TK.TokenType.IDENTIFIER:
|
||||
this.parseIdentifier()
|
||||
case TK.TokenType.NUMBER:
|
||||
this.stack.push(new AST.NumberElement(this.tokenizer.next().value))
|
||||
break
|
||||
case TK.TokenType.STRING:
|
||||
this.stack.push(new AST.StringElement(this.tokenizer.next().value))
|
||||
break
|
||||
case TK.TokenType.IDENTIFIER:
|
||||
case TK.TokenType.OPERATOR:
|
||||
if(this.stack.length == 0 && Reference.UNARY_OPERATORS.includes(token.value))
|
||||
this.parseSingleOperation()
|
||||
|
@ -104,12 +108,11 @@ class ExpressionBuilder {
|
|||
this.parseBinaryOperations()
|
||||
else if(this.stack.length > 0 && Reference.TERTIARY_OPERATORS.includes(token.value))
|
||||
this.parseTertiaryOperation()
|
||||
break
|
||||
case TK.TokenType.NUMBER:
|
||||
this.stack.push(new AST.NumberElement(this.tokenizer.next().value))
|
||||
break
|
||||
case TK.TokenType.STRING:
|
||||
this.stack.push(new AST.StringElement(this.tokenizer.next().value))
|
||||
else if(token.type == TK.TokenType.IDENTIFIER)
|
||||
// If it isn't a reserved keyword for operators (e.g and, or...), then it *is* and identifier.
|
||||
this.parseIdentifier()
|
||||
else
|
||||
this.tokenizer.raise(`Unknown operator: ${token.value}.`)
|
||||
break
|
||||
case TK.TokenType.PUNCT:
|
||||
if(token.value == '(') {
|
||||
|
@ -202,7 +205,7 @@ class ExpressionBuilder {
|
|||
|
||||
/**
|
||||
* Checks for followup tokens following a value getting.
|
||||
* E.g: getting the property of an object, an array member, or calling a function.
|
||||
* E.g: getting the property of an object, an array member, or calling a function.^
|
||||
* NOTE: Expects to have at least one stack element for previous calling object.
|
||||
*/
|
||||
checkIdentifierFollowupTokens() {
|
||||
|
@ -231,9 +234,47 @@ class ExpressionBuilder {
|
|||
throw new Error(`The operator ${this.tokenizer.peek().value} can only be used after a value.`)
|
||||
// Parse a sequence of operations, and orders them based on OPERATION_PRIORITY.
|
||||
let elements = [this.stack.pop()]
|
||||
let operators = [this.tokenizer.next()]
|
||||
let operators = [this.tokenizer.next().value]
|
||||
let nextIsOperator = false
|
||||
let token
|
||||
while((token = this.tokenizer.peek()) != null) {
|
||||
if(nextIsOperator)
|
||||
if(token.type == TK.TokenType.PUNCT)
|
||||
if(token.value == ')')
|
||||
// Don't skip that token, but stop the parsing,
|
||||
// as it may be an unopened expression.
|
||||
break
|
||||
else
|
||||
this.tokenizer.raise(`Unexpected ${token.value}. Expected an operator, or ')'.`)
|
||||
else if(token.type == TK.TokenType.IDENTIFIER)
|
||||
if(Reference.BINARY_OPERATORS.includes(token.value))
|
||||
this.operartors.push(this.tokenizer.next().value)
|
||||
else if(Reference.TERTIARY_OPERATORS.includes(token.value))
|
||||
// Break to let the hand back to the parser.
|
||||
break
|
||||
else if(Reference.UNARY_OPERATORS.includes(token.value))
|
||||
this.tokenizer.raise(`Invalid use of operator ${token.value} after ${elements.pop().value}.`)
|
||||
else
|
||||
this.tokenizer.raise(`Unknown operator: ${token.value}.`)
|
||||
else {
|
||||
handleSingle(token)
|
||||
let value = this.stack.pop()
|
||||
if(token.value != '(' && (value instanceof AST.BinaryOperation || value instanceof AST.TertiaryOperation))
|
||||
// In case you chain something like 'or' and '*'
|
||||
// Unary operations are exempted from this as they are used for a single value.
|
||||
this.tokenizer.raise(`Cannot chain operations ${operators.pop().value} and ${value.ope}.`)
|
||||
elements.push(value)
|
||||
}
|
||||
}
|
||||
// Now we have our full chain, we need to match by operation priority
|
||||
// TODO: Implement FlatBinaryOperations for better simplification and smarter trees.
|
||||
for(let ope of AST.BINARY_OPERATORS)
|
||||
while(operators.includes(ope)) { // Skip if not in priority.
|
||||
let index = operators.indexOf(ope)
|
||||
operators.splice(index, 1) // Remove operator from array.
|
||||
elements.splice(index, 2, new BinaryOperation(elements[index], ope, elements[index+1]))
|
||||
}
|
||||
// At the end, there should be no more operators and only one element.
|
||||
this.stack.push(elements.pop())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,9 @@ const BINARY_OPERATION_PRIORITY = {
|
|||
'*': 40, '/': 40,
|
||||
'^': 50
|
||||
}
|
||||
const BINARY_OPERATORS = Object.keys(BINARY_OPERATION_PRIORITY)
|
||||
|
||||
// Sorted by priority (most to least)
|
||||
const BINARY_OPERATORS = Object.keys(BINARY_OPERATION_PRIORITY).sort((ope1, ope2) => BINARY_OPERATION_PRIORITY[ope2]-BINARY_OPERATION_PRIORITY[ope1])
|
||||
|
||||
const TERTIARY_OPERATORS = ['?']
|
||||
|
||||
|
|
Loading…
Reference in a new issue