Project - Simple arithmetic expressions

Martin Sulzmann

Project Description

We extend the language of arithmetic expression with boolean expressions.

 N ::= 0 | 1 | 2 

 B ::= true | false

 V ::= N | B

 E ::= V | (E) | E + E | E * E | E && E | E || E

Your tasks are.

  1. Implement a parser.

  2. Implement a type inferencer.

You may either use a top-down parser or parser combinator functions. For both cases, rewrite the grammar such that parsing becomes deterministic.

You will also need to extend the AST to represent boolean expressions.

Below you find a top-down parser and type checker for integer expressions. You may take this code and add the necessary extensions (additional code + documentation).

Sample source code

package main

import "fmt"
import "strconv"

// parser with type checker

/*

SYNTAX:

We consider  integer expressions of the following form.

N ::= 0 | 1 | 2

E ::= N | (E) | E + E | E * E


As we know the above grammar is ambigous. We make the grammar unambigous
by giving * a higher precedence than +.

E ::= E + T | T

T ::= T * F | F

F ::= N | (E)

The above grammar is unambiguous but not suitable for top-down parsing.
We remove left-recursion and obtain the following grammar.

E  ::= T E2

E2 ::= + T E2 |

T  ::= F T2

T2 ::= * F T2 |

F ::= N | (E)

N ::= 0 | 1 | 2


In your project, you need to carry out the above steps
for the enriched expression language that includes Boolean expressions.

 TYPES:

We demand that integer expressions must be well-typed.
We don't consider variables which makes the typing rules rather simple.

          i is an integer (covers 0, 1, 2)
 (Int)   -------------------------
          |- i : int


        |- e1 : int    |- e2 : int
 (+)  -------------------------------
         |- e1 + e2 : int

        |- e1 : int    |- e2 : int
 (*)  -------------------------------
         |- e1 * e2 : int


Typing rules are defined in terms of ASTs.
In the above, we write

 e1 + e2

to refer to the AST

    +
   / \
  e1  e2

In your project, you need to extend the typing rules to include Boolean expressions,
 and you need to extend the implementation of the infer method (see below).


*/

type Type int

const (
    TyIllTyped Type = 0
    TyInt      Type = 1
    TyBool     Type = 2
)

func showType(t Type) string {
    var s string
    switch {
    case t == TyInt:
        s = "Int"
    case t == TyBool:
        s = "Bool"
    case t == TyIllTyped:
        s = "Illtyped"
    }
    return s
}

// AST

type Exp interface {
    pretty() string
    infer() Type
}

type Num int
type Mult [2]Exp
type Plus [2]Exp

// pretty print

func (x Num) pretty() string {
    return strconv.Itoa(int(x))
}

func (e Mult) pretty() string {

    var x string
    x = "("
    x += e[0].pretty()
    x += "*"
    x += e[1].pretty()
    x += ")"

    return x
}

func (e Plus) pretty() string {

    var x string
    x = "("
    x += e[0].pretty()
    x += "+"
    x += e[1].pretty()
    x += ")"

    return x
}

// Type inferencer/checker

func (x Num) infer() Type {
    return TyInt
}

func (e Mult) infer() Type {
    t1 := e[0].infer()
    t2 := e[1].infer()
    if t1 == TyInt && t2 == TyInt {
        return TyInt
    }
    return TyIllTyped
}

func (e Plus) infer() Type {
    t1 := e[0].infer()
    t2 := e[1].infer()
    if t1 == TyInt && t2 == TyInt {
        return TyInt
    }
    return TyIllTyped
}

// Simple scanner/lexer

// Tokens
const (
    EOS   = 0 // End of string
    ZERO  = 1
    ONE   = 2
    TWO   = 3
    OPEN  = 4
    CLOSE = 5
    PLUS  = 6
    MULT  = 7
)

func scan(s string) (string, int) {
    for {
        switch {
        case len(s) == 0:
            return s, EOS
        case s[0] == '0':
            return s[1:len(s)], ZERO
        case s[0] == '1':
            return s[1:len(s)], ONE
        case s[0] == '2':
            return s[1:len(s)], TWO
        case s[0] == '+':
            return s[1:len(s)], PLUS
        case s[0] == '*':
            return s[1:len(s)], MULT
        case s[0] == '(':
            return s[1:len(s)], OPEN
        case s[0] == ')':
            return s[1:len(s)], CLOSE
        default: // simply skip everything else
            s = s[1:len(s)]
        }

    }
}

type State struct {
    s   *string
    tok int
}

func next(s *State) {
    s2, tok := scan(*s.s)

    s.s = &s2
    s.tok = tok
}

// E  ::= T E'
func parseE(s *State) (bool, Exp) {
    b, e := parseT(s)
    if !b {
        return false, e
    }
    return parseE2(s, e)
}

// E' ::= + T E' |
func parseE2(s *State, e Exp) (bool, Exp) {
    if s.tok == PLUS {
        next(s)
        b, f := parseT(s)
        if !b {
            return false, e
        }
        t := (Plus)([2]Exp{e, f})
        return parseE2(s, t)
    }

    return true, e
}

// T  ::= F T'
func parseT(s *State) (bool, Exp) {
    b, e := parseF(s)
    if !b {
        return false, e
    }
    return parseT2(s, e)
}

// T' ::= * F T' |
func parseT2(s *State, e Exp) (bool, Exp) {
    if s.tok == MULT {
        next(s)
        b, f := parseF(s)
        if !b {
            return false, e
        }
        t := (Mult)([2]Exp{e, f})
        return parseT2(s, t)
    }
    return true, e
}

// F ::= N | (E)
func parseF(s *State) (bool, Exp) {
    switch {
    case s.tok == ZERO:
        next(s)
        return true, (Num)(0)
    case s.tok == ONE:
        next(s)
        return true, (Num)(1)
    case s.tok == TWO:
        next(s)
        return true, (Num)(2)
    case s.tok == OPEN:
        next(s)
        b, e := parseE(s)
        if !b {
            return false, e
        }
        if s.tok != CLOSE {
            return false, e
        }
        next(s)
        return true, e
    }

    return false, (Num)(0)
}

func parse(s string) Exp {
    st := State{&s, EOS}
    next(&st)
    _, e := parseE(&st)
    if st.tok == EOS {
        return e
    }
    return (Num)(0) // dummy value
}

func debug(s string) {
    fmt.Printf("%s", s)
}

func test(s string) {
    e := parse(s)
    fmt.Printf("\n %s", e.pretty())
    fmt.Printf("\n %s", showType(e.infer()))
}

func testParserGood() {
    fmt.Printf("\n GOOD \n")
    test("1")
    test("1+0")
    test("1 * 2 ")
    test(" (1) ")
    test(" (1 * (2)) ")
    test(" (1 + 2) * 0 ")
}

// Helper functions to build ASTs by hand

func number(x int) Exp {
    return Num(x)
}

func plus(x, y Exp) Exp {
    return (Plus)([2]Exp{x, y})

    // The type Plus is defined as the two element array consisting of Exp elements.
    // Plus and [2]Exp are isomorphic but different types.
    // We first build the AST value [2]Exp{x,y}.
    // Then cast this value (of type [2]Exp) into a value of type Plus.

}

func mult(x, y Exp) Exp {
    return (Mult)([2]Exp{x, y})
}

func examplesAST() {
    ast1 := plus(mult(number(1), number(2)), number(0))

    fmt.Printf("\n %s", ast1.pretty())

    fmt.Printf("\n %s", showType(ast1.infer()))

}

func main() {

    fmt.Printf("\n")
    //  testParserGood()

    examplesAST()
}