236 lines
6.9 KiB
Rexx
236 lines
6.9 KiB
Rexx
expressions = .array~of("2+3", "2+3/4", "2*3-4", "2*(3+4)+5/6", "2 * (3 + (4 * 5 + (6 * 7) * 8) - 9) * 10", "2*-3--4+-.25")
|
|
loop input over expressions
|
|
expression = createExpression(input)
|
|
if expression \= .nil then
|
|
say 'Expression "'input'" parses to "'expression~string'" and evaluates to "'expression~evaluate'"'
|
|
end
|
|
|
|
|
|
-- create an executable expression from the input, printing out any
|
|
-- errors if they are raised.
|
|
::routine createExpression
|
|
use arg inputString
|
|
-- signal on syntax
|
|
return .ExpressionParser~parseExpression(inputString)
|
|
|
|
syntax:
|
|
condition = condition('o')
|
|
say condition~errorText
|
|
say condition~message
|
|
return .nil
|
|
|
|
|
|
-- a base class for tree nodes in the tree
|
|
-- all nodes return some sort of value. This can be constant,
|
|
-- or the result of additional evaluations
|
|
::class evaluatornode
|
|
-- all evaluation is done here
|
|
::method evaluate abstract
|
|
|
|
-- node for numeric values in the tree
|
|
::class constant
|
|
::method init
|
|
expose value
|
|
use arg value
|
|
|
|
::method evaluate
|
|
expose value
|
|
return value
|
|
|
|
::method string
|
|
expose value
|
|
return value
|
|
|
|
-- node for a parenthetical group on the tree
|
|
::class parens
|
|
::method init
|
|
expose subexpression
|
|
use arg subexpression
|
|
|
|
::method evaluate
|
|
expose subexpression
|
|
return subexpression~evaluate
|
|
|
|
::method string
|
|
expose subexpression
|
|
return "("subexpression~string")"
|
|
|
|
-- base class for binary operators
|
|
::class binaryoperator
|
|
::method init
|
|
expose left right
|
|
-- the left and right sides are set after the left and right sides have
|
|
-- been resolved.
|
|
left = .nil
|
|
right = .nil
|
|
|
|
-- base operation
|
|
::method evaluate
|
|
expose left right
|
|
return self~operation(left~evaluate, right~evaluate)
|
|
|
|
-- the actual operation of the node
|
|
::method operation abstract
|
|
::method symbol abstract
|
|
::method precedence abstract
|
|
|
|
-- display an operator as a string value
|
|
::method string
|
|
expose left right
|
|
return '('left~string self~symbol right~string')'
|
|
|
|
::attribute left
|
|
::attribute right
|
|
|
|
::class addoperator subclass binaryoperator
|
|
::method operation
|
|
use arg left, right
|
|
return left + right
|
|
|
|
::method symbol
|
|
return "+"
|
|
|
|
::method precedence
|
|
return 1
|
|
|
|
::class subtractoperator subclass binaryoperator
|
|
::method operation
|
|
use arg left, right
|
|
return left - right
|
|
|
|
::method symbol
|
|
return "-"
|
|
|
|
::method precedence
|
|
return 1
|
|
|
|
::class multiplyoperator subclass binaryoperator
|
|
::method operation
|
|
use arg left, right
|
|
return left * right
|
|
|
|
::method symbol
|
|
return "*"
|
|
|
|
::method precedence
|
|
return 2
|
|
|
|
::class divideoperator subclass binaryoperator
|
|
::method operation
|
|
use arg left, right
|
|
return left / right
|
|
|
|
::method symbol
|
|
return "/"
|
|
|
|
::method precedence
|
|
return 2
|
|
|
|
-- a class to parse the expression and build an evaluation tree
|
|
::class expressionParser
|
|
-- create a resolved operand from an operator instance and the top
|
|
-- two entries on the operand stack.
|
|
::method createNewOperand class
|
|
use strict arg operator, operands
|
|
-- the operands are a stack, so they are in inverse order current
|
|
operator~right = operands~pull
|
|
operator~left = operands~pull
|
|
-- this goes on the top of the stack now
|
|
operands~push(operator)
|
|
|
|
::method parseExpression class
|
|
use strict arg inputString
|
|
-- stacks for managing the operands and pending operators
|
|
operands = .queue~new
|
|
operators = .queue~new
|
|
-- this flags what sort of item we expect to find at the current
|
|
-- location
|
|
afterOperand = .false
|
|
|
|
loop currentIndex = 1 to inputString~length
|
|
char = inputString~subChar(currentIndex)
|
|
-- skip over whitespace
|
|
if char == ' ' then iterate currentIndex
|
|
-- If the last thing we parsed was an operand, then
|
|
-- we expect to see either a closing paren or an
|
|
-- operator to appear here
|
|
if afterOperand then do
|
|
if char == ')' then do
|
|
loop while \operators~isempty
|
|
operator = operators~pull
|
|
-- if we find the opening paren, replace the
|
|
-- top operand with a paren group wrapper
|
|
-- and stop popping items
|
|
if operator == '(' then do
|
|
operands~push(.parens~new(operands~pull))
|
|
leave
|
|
end
|
|
-- collapse the operator stack a bit
|
|
self~createNewOperand(operator, operands)
|
|
end
|
|
-- done with this character
|
|
iterate currentIndex
|
|
end
|
|
afterOperand = .false
|
|
operator = .nil
|
|
if char == "+" then operator = .addoperator~new
|
|
else if char == "-" then operator = .subtractoperator~new
|
|
else if char == "*" then operator = .multiplyoperator~new
|
|
else if char == "/" then operator = .divideoperator~new
|
|
if operator \= .nil then do
|
|
loop while \operators~isEmpty
|
|
top = operators~peek
|
|
-- start of a paren group stops the popping
|
|
if top == '(' then leave
|
|
-- or the top operator has a lower precedence
|
|
if top~precedence < operator~precedence then leave
|
|
-- process this pending one
|
|
self~createNewOperand(operators~pull, operands)
|
|
end
|
|
-- this new operator is now top of the stack
|
|
operators~push(operator)
|
|
-- and back to the top
|
|
iterate currentIndex
|
|
end
|
|
raise syntax 98.900 array("Invalid expression character" char)
|
|
end
|
|
-- if we've hit an open paren, add this to the operator stack
|
|
-- as a phony operator
|
|
if char == '(' then do
|
|
operators~push('(')
|
|
iterate currentIndex
|
|
end
|
|
-- not an operator, so we have an operand of some type
|
|
afterOperand = .true
|
|
startindex = currentIndex
|
|
-- allow a leading minus sign on this
|
|
if inputString~subchar(currentIndex) == '-' then
|
|
currentIndex += 1
|
|
-- now scan for the end of numbers
|
|
loop while currentIndex <= inputString~length
|
|
-- exit for any non-numeric value
|
|
if \inputString~matchChar(currentIndex, "0123456789.") then leave
|
|
currentIndex += 1
|
|
end
|
|
-- extract the string value
|
|
operand = inputString~substr(startIndex, currentIndex - startIndex)
|
|
if \operand~datatype('Number') then
|
|
raise syntax 98.900 array("Invalid numeric operand '"operand"'")
|
|
-- back this up to the last valid character
|
|
currentIndex -= 1
|
|
-- add this to the operand stack as a tree element that returns a constant
|
|
operands~push(.constant~new(operand))
|
|
end
|
|
|
|
loop while \operators~isEmpty
|
|
operator = operators~pull
|
|
if operator == '(' then
|
|
raise syntax 98.900 array("Missing closing ')' in expression")
|
|
self~createNewOperand(operator, operands)
|
|
end
|
|
-- our entire expression should be the top of the expression tree
|
|
expression = operands~pull
|
|
if \operands~isEmpty then
|
|
raise syntax 98.900 array("Invalid expression")
|
|
return expression
|