class Calcula::Parser
The parser for the Calcula
language written in Ruby. Please, if adding a grammar rule, document it.
@author Paul T.
Constants
- ADD_OR_SUB
- EQL_LIKE_OPS
- MUL_LIKE_OPS
- STMT_TERMINATOR
Public Class Methods
Constructs a new parser with the specified Tokens
@param toks [Array<Calcula::Token>] The tokens most likely generated from the lexer
# File lib/Parser.rb, line 27 def initialize(toks) @toks = toks @i = 0 end
Public Instance Methods
Parses the syntax rule of addLikeExpr
addLikeExpr = mulLikeExpr ((OP_ADD | OP_SUB) mulLikeExpr)*
@return [Calcula::Expr]
# File lib/Parser.rb, line 224 def addLikeExpr return consumeWhen(:mulLikeExpr, BinopExpr) do |id, _txt| ADD_OR_SUB.index(id) != nil end end
Parses the syntax rule for basicValExpr
basicValExpr = numExpr | composeExpr | lambdaExpr
@return [Calcula::Expr]
# File lib/Parser.rb, line 325 def basicValExpr tmp = lookahead() parseError(nil, "Expected numExpr or identExpr") if tmp == nil case tmp.id when :NUM then return numExpr() when :ID then return composeExpr() when :LAMBDA then return lambdaExpr() else parseError(tmp, "Expected numExpr or identExpr") end end
Parses the syntax rule for composeExpr
composeExpr = identExpr (COMPOSE identExpr)*
# File lib/Parser.rb, line 342 def composeExpr return consumeWhen(:identExpr, BinopExpr) do |id, _txt| id == :COMPOSE end end
Performs a lookahead and checks to see if the next token is the one specified. If the specified token is not found, then the method `parseError` is called.
(see lookahead
) (see parseError
) @param tokId [Symbol] The type of token being expected @return [Calcula::Token] The actual token found
# File lib/Parser.rb, line 51 def consume(tokId) cons = lookahead() if cons == nil then parseError(nil, "Expected token #{tokId}") elsif cons.id == tokId then @i += 1 return cons else parseError(@toks[@i], "Expected token #{tokId}") end end
Performs lookahead and constructs a parse tree. This could only be used on parse trees with the grammar of
self = $1:child ($2:SOMETHING $3:child)*
The tree being constructed must have the initialization sequence of
SubclassOfExpr.new($2, $1, $3)
@param childrenMethod [Symbol] The name of `child` in the example grammar @param exprClass [Constant (class name)] The `SubclassOfExpr` in the example @yield [id, text] Predicate for whether or not lookahead should continue @yieldparam id [Symbol] The current lookahead token's id @yieldparam text [String] The current lookahead token's text @yieldreturn [true, false] true if consumption should continue, false otherwise @return [Calcula::Expr] The tree being constructed
# File lib/Parser.rb, line 180 def consumeWhen(childrenMethod, exprClass) # => Take a block (predicate) rst = self.send(childrenMethod) tmp = lookahead while tmp != nil && yield(tmp.id, tmp.text) do rst = exprClass.new(consume(tmp.id), rst, self.send(childrenMethod)) tmp = lookahead end return rst end
Parses the syntax rule of defExpr
defExpr = LET identExpr PAREN_O paramsExpr PAREN_C OP_EQ equation | LET identExpr OP_EQ equation
@return [Calcula::Expr]
# File lib/Parser.rb, line 111 def defExpr consume(:LET) name = identExpr() val = nil maybeParenO = lookahead parseError(nil, "Expected a complete function declaration or '='") if maybeParenO == nil case maybeParenO.id when :PAREN_O then consume(:PAREN_O) params = paramsExpr() consume(:PAREN_C) consume(:OP_EQ) act = equation() val = FuncExpr.new(params, act) when :OP_EQ then consume(:OP_EQ) val = equation() else parseError(maybeParenO, "Expected complete function declaration or '='") end AssignExpr.new(name, val) end
Parses the syntax rule of equalLikeExpr
equalLikeExpr = addLikeExpr ((OP_EQ | OP_NE | OP_LT | OP_GT | OP_LE | OP_GE) addLikeExpr)?
@return [Calcula::Expr]
# File lib/Parser.rb, line 214 def equalLikeExpr return consumeWhen(:addLikeExpr, BinopExpr) do |id, _txt| EQL_LIKE_OPS.index(id) != nil end end
Parses the syntax rule of expr
equation = logicOrExpr
@return [Calcula::Expr]
# File lib/Parser.rb, line 103 def equation logicOrExpr() end
Parses the syntax rule for exponentExpr
exponentExpr = parenExpr (OP_POW parenExpr)*
@return [Calcula::Expr]
# File lib/Parser.rb, line 290 def exponentExpr return consumeWhen(:parenExpr, BinopExpr) do |id, _txt| id == :OP_POW end end
Parses the syntax rule for identExpr
identExpr = ID
@return [Calcula::Expr]
# File lib/Parser.rb, line 398 def identExpr tok = consume(:ID) return IdentExpr.new(tok) end
Parses the syntax rule for lambdaExpr
lambdaExpr = LAMBDA paramsExpr equation
@return [Calcula::Expr]
# File lib/Parser.rb, line 372 def lambdaExpr consume(:LAMBDA) params = paramsExpr() return FuncExpr.new(params, equation()) end
Parses the syntax rule of logicAndExpr
logicAndExpr = equalLikeExpr (OR equalLikeExpr)*
@return [Calcula::Expr]
# File lib/Parser.rb, line 204 def logicAndExpr return consumeWhen(:equalLikeExpr, BinopExpr) do |id, _txt| id == :OR end end
Parses the syntax rule of logicNotExpr
logicNotExpr = NOT logicOrExpr
@return [Calcula::Expr]
# File lib/Parser.rb, line 153 def logicNotExpr tmp = lookahead parseError(nil, "Expected an equation") if tmp == nil if tmp.id == :NOT then tmp = consume(tmp.id) end rst = logicOrExpr if tmp.id == :NOT then rst = UnaryExpr.mkPrefix(tmp, rst) end return rst end
Parses the syntax rule of logicOrExpr
logicOrExpr = logicAndExpr (AND logicAndExpr)*
@return [Calcula::Expr]
# File lib/Parser.rb, line 194 def logicOrExpr return consumeWhen(:logicAndExpr, BinopExpr) do |id, _txt| id == :AND end end
Finds the next non-ignored token and returns it
@return [Calcula::Token, nil] nil if the next non-ignored token nil. This could mean end of script.
# File lib/Parser.rb, line 66 def lookahead return nil if @toks[@i] == nil while @toks[@i].id == :WS || @toks[@i].id == :COMMENT do @i += 1 return nil if @toks[@i] == nil end if @i < @toks.length then return @toks[@i] else return nil end end
Parses the syntax rule of mulLikeExpr
mulLikeExpr = prefixExpr ((OP_REM | OP_MUL | OP_DIV) mulLikeExpr)? | prefixExpr (PAREN_O equation mulLikeExpr)?
@return [Calcula::Expr]
# File lib/Parser.rb, line 235 def mulLikeExpr rst = prefixExpr tmp = lookahead while tmp != nil && MUL_LIKE_OPS.index(tmp.id) != nil do if tmp.id == :PAREN_O then paren = consume(tmp.id) appliedOn = equation consume(:PAREN_C) rst = BinopExpr.new(paren, rst, appliedOn) else rst = BinopExpr.new(consume(tmp.id), rst, prefixExpr) end tmp = lookahead end return rst end
Parses the syntax rule for numExpr
numExpr = NUM (RAT NUM)?
@return [Calcula::Expr]
# File lib/Parser.rb, line 382 def numExpr tok = consume(:NUM) maybeRat = lookahead() if maybeRat != nil and maybeRat.id == :RAT then consume(:RAT) bottom = consume(:NUM) return RatExpr.new(tok, bottom) else return NumExpr.new(tok) end end
Parses the syntax rule for paramsExpr
paramsExpr = ID (COMMA ID)+ | ID
@return [Calcula::Expr]
# File lib/Parser.rb, line 353 def paramsExpr # NOTE: Do not change this part to using the `consumeWhen` method. # it does not work like that. params = [consume(:ID)] loop do begin consume(:COMMA) rescue RuntimeError break end params << consume(:ID) end return ParamsExpr.new(params) end
Parses the syntax rule of parenExpr
parenExpr = PAREN_O equation PAREN_C | SQUARE_O equation SQUARE_C | basicValExpr
@return [Calcula::Expr]
# File lib/Parser.rb, line 302 def parenExpr tmp = lookahead() parseError(nil, "Expected PAREN_O or an equation") if tmp == nil case tmp.id when :PAREN_O then head = consume(:PAREN_O) inner = equation() tail = consume(:PAREN_C) return BracedExpr.new(head, tail, inner) when :SQUARE_O then head = consume(:SQUARE_O) inner = equation() tail = consume(:SQUARE_C) return BracedExpr.new(head, tail, inner) else return basicValExpr() end end
Begins to parse the list of tokens and tries to convert it into an expression tree
@return [Calcula::Expr, nil] If an expression tree can be constructed, nil otherwise
# File lib/Parser.rb, line 82 def parse tmp = lookahead if tmp == nil then nil else case tmp.id when :LET then defExpr when :COMMA then consume(:COMMA) # Comma is like semicolons in ruby. Optional! parse else printExpr end end end
Raises an error with seemingly useful message
@param unexpectedTok [Calcula::Token, nil] The token that was found instead, nil if End of file was found. @param msg [String] The additional message or the reason of the error @raise [RuntimeError] Calling this method raises this error
# File lib/Parser.rb, line 37 def parseError(unexpectedTok, msg) if unexpectedTok == nil then raise "Unexpected EOF: #{msg != '' ? msg : '<no message>'}" end raise "Unexpected token #{unexpectedTok}: #{msg != '' ? msg : '<no message>'}" end
Parses the syntax rule for postfixExpr
postfixExpr = exponentExpr ROUND_DOLLAR?
@return [Calcula::Expr]
# File lib/Parser.rb, line 273 def postfixExpr base = exponentExpr maybePostfix = lookahead if maybePostfix != nil then case maybePostfix.id when :ROUND_DOLLAR then return UnaryExpr.mkPostfix(consume(:ROUND_DOLLAR), base) end # Default case fallsthough and base itself gets returned end return base end
Parses the syntax rule for prefixExpr
prefixExpr = (OP_SUB | OP_ADD)? postfixExpr
@return [Calcula::Expr]
# File lib/Parser.rb, line 256 def prefixExpr tmp = lookahead parseError(nil, "Expected a prefix operator or equation") if tmp == nil case tmp.id when :OP_SUB, :OP_ADD then op = consume(tmp.id) return UnaryExpr.mkPrefix(op, postfixExpr) else return postfixExpr end end
Parses the syntax ruke for printExpr
printExpr = equation (DISP | ASSERT)?
# File lib/Parser.rb, line 139 def printExpr base = equation() maybeDisp = lookahead() if maybeDisp != nil && STMT_TERMINATOR.index(maybeDisp.id) != nil then UnaryExpr.mkPostfix(consume(maybeDisp.id), base) else base end end
Resets the state of the parser
# File lib/Parser.rb, line 404 def reset @i = 0 end