def require(stream: TokenStream, category: TokenCat, desc: str = "", consume=False): """Requires the next token in the stream to match a specified category. Consumes and discards it if consume==True. """ if stream.peek().kind != category: raise InputError(f"Expecting {desc or category}, but saw {stream.peek()} instead") if consume: stream.take() return
def _stmt(stream: TokenStream) -> expr.Expr: """ stmt ::= exp ['=' exp] """ left = _expr(stream) if stream.peek().kind is TokenCat.EQUALS: stream.take() right = _expr(stream) return expr.Equals(left, right) else: return left
def _stmt(stream: TokenStream) -> expr.Expr: """ stmt ::= exp ['=' exp] """ left = _expr(stream) if stream.peek().kind is TokenCat.ASSIGN: if not isinstance(left, expr.Var): raise InputError("Can only assign to a variable") stream.take() right = _expr(stream) return expr.Assign(left, right) else: return left
def _term(stream: TokenStream) -> expr.Expr: """term ::= primary { ('*'|'/') primary }""" left = _secondary(stream) log.debug(f"term starts with {left}") while stream.peek().value in ["*", "/"]: op = stream.take() right = _secondary(stream) if op.value == "*": left = expr.Times(left, right) elif op.value == "/": left = expr.Div(left, right) else: raise InputError(f"Expecting multiplicative op, got {op}") return left
def _secondary(stream: TokenStream) -> expr.Expr: """term ::= secondary { ('^'|'|') secondary }""" left = _primary(stream) log.debug(f"term starts with {left}") while stream.peek().value in ["^", "|"]: op = stream.take() right = _primary(stream) if op.value == "^": left = expr.Raise(left, right) elif op.value == "|": left = expr.Root(left, right) else: raise InputError(f"Expecting Exponential op, got {op}") return left
def _block(stream: TokenStream) -> expr.Expr: """ block ::= { stmt } """ log.debug(f"Parsing block from token {stream.peek()}") if stream.peek().kind not in first["stmt"]: return expr.Pass() left = _stmt(stream) log.debug(f"Starting block with {left}") while stream.peek().kind in first["stmt"]: right = _stmt(stream) log.debug(f"Adding statement to block: {right}") left = expr.Seq(left, right) return left
def _primary(stream: TokenStream) -> expr.Expr: """Unary operations, Constants, Variables, input, and parenthesized expressions""" log.debug(f"Parsing primary with starting token {stream.peek()}") token = stream.take() if token.kind is TokenCat.INT: log.debug(f"Returning IntConst node from token {token}") return expr.IntConst(int(token.value)) elif token.kind is TokenCat.VAR: log.debug(f"Variable {token.value}") return expr.Var(token.value) elif token.kind is TokenCat.READ: log.debug("Read") return expr.Read() elif token.kind is TokenCat.ABS: operand = _primary(stream) return expr.Abs(operand) elif token.kind is TokenCat.NEG: operand = _primary(stream) return expr.Neg(operand) elif token.kind is TokenCat.LPAREN: nested = _expr(stream) require(stream, TokenCat.RPAREN, consume=True) return nested else: raise InputError(f"Confused about {token} in expression")
def _expr(stream: TokenStream) -> expr.Expr: """ expr ::= term { ('+'|'-') term } """ log.debug(f"parsing sum starting from token {stream.peek()}") left = _term(stream) log.debug(f"sum begins with {left}") while stream.peek().value in ["+", "-"]: op = stream.take() log.debug(f"expr addition op {op}") right = _term(stream) if op.value == "+": left = expr.Plus(left, right) elif op.value == "-": left = expr.Minus(left, right) else: raise InputError(f"What's that op? {op}") return left
def _rel(stream: TokenStream) -> expr.Comparison: left = _expr(stream) op = stream.take() right = _expr(stream) if op.kind in COMPARISONS: clazz = COMPARISONS[op.kind] return clazz(left, right) else: raise InputError(f"Expecting comparison, saw '{op.value}' instead")
def _if(stream: TokenStream) -> expr.If: require(stream, TokenCat.IF, consume=True) cond = _rel(stream) require(stream, TokenCat.THEN, consume=True) then_block = _block(stream) if stream.peek().kind == TokenCat.ELSE: require(stream, TokenCat.ELSE, consume=True) else_block = _block(stream) result = expr.If(cond, then_block, else_block) else: result = expr.If(cond, then_block, elsepart=expr.Pass()) require(stream, TokenCat.FI, consume=True) return result
def parse(srcfile: TextIO) -> expr.Expr: """Interface function to LL parser""" stream = TokenStream(srcfile) return _program(stream)
def _stmt(stream: TokenStream) -> expr.Expr: """ # stmt ::= assign | loop | ifstmt | printstmt assignment ::= IDENT '=' expression ';' """ if stream.peek().kind is TokenCat.WHILE: return _while(stream) if stream.peek().kind is TokenCat.IF: return _if(stream) if stream.peek().kind is TokenCat.PRINT: return _print(stream) if stream.peek().kind is not TokenCat.VAR: raise InputError( f"Expecting identifier at beginning of assignment, got {stream.peek()}" ) target = expr.Var(stream.take().value) if stream.peek().kind is not TokenCat.ASSIGN: raise InputError(f"Expecting assignment symbol, got {stream.peek()}") stream.take() # Discard token value = _expr(stream) if stream.peek().kind is not TokenCat.SEMI: raise InputError( f"Expecting semicolon after assignment, got {stream.peek()}") stream.take() # Discard token return expr.Assign(target, value)