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

new(toks) click to toggle source

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

addLikeExpr() click to toggle source

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
basicValExpr() click to toggle source

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
composeExpr() click to toggle source

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
consume(tokId) click to toggle source

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
consumeWhen(childrenMethod, exprClass) click to toggle source

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
defExpr() click to toggle source

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
equalLikeExpr() click to toggle source

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
equation() click to toggle source

Parses the syntax rule of expr

equation = logicOrExpr

@return [Calcula::Expr]

# File lib/Parser.rb, line 103
def equation
  logicOrExpr()
end
exponentExpr() click to toggle source

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
identExpr() click to toggle source

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
lambdaExpr() click to toggle source

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
logicAndExpr() click to toggle source

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
logicNotExpr() click to toggle source

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
logicOrExpr() click to toggle source

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
lookahead() click to toggle source

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
mulLikeExpr() click to toggle source

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
numExpr() click to toggle source

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
paramsExpr() click to toggle source

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
parenExpr() click to toggle source

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
parse() click to toggle source

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
parseError(unexpectedTok, msg) click to toggle source

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
postfixExpr() click to toggle source

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
prefixExpr() click to toggle source

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
printExpr() click to toggle source

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
reset() click to toggle source

Resets the state of the parser

# File lib/Parser.rb, line 404
def reset
  @i = 0
end