nil

Compact Pl

Implementing an OO-compact programming language

The base class to represent all forms of data.

In [1]:
abstract class Data

Create some forms of data.

In [2]:
class IntData(val value: Int): Data() {
    override fun toString(): String
    = "Int($value)"
}
In [3]:
IntData(42)
Out[3]:
Int(42)
In [4]:
class BoolData(val value: Boolean): Data() {
    override fun toString(): String
    = if(value) {
        "yes"
    } else {
        "no"
    }
}
In [5]:
BoolData(true)
Out[5]:
yes

Create a class to represent scopes, which are also known as contexts.

In [49]:
class Context(): HashMap<String, Data>() {
    constructor(parent: Context): this() {
        this.putAll(parent)
    }
}

Create the base class to represent all forms of expressions.

Bold objective

Everything will be expressions.

In [50]:
abstract class Expr {
    abstract fun eval(scope:Context): Data
}

Helper function to evaluate expressions for us.

In [51]:
fun run(program: Expr) {
    try {
        val data = program.eval(Context())
        println("=> ${data}")
    } catch(e: Exception) {
        println("[err] ${e}")
    }
}

Data literals

42
In [52]:
class IntLiteral(val value: Int): Expr() {
    override fun eval(scope:Context): Data 
    = IntData(value)
}
In [53]:
// Use the IntLiteral to write a small program

IntLiteral(42).eval(Context())
Out[53]:
Int(42)

Support standard binary arithmetics on integers

42 + 1
(20 + (11*2)) + 1
In [54]:
enum class Op {
    Add,
    Sub,
    Mul,
    Div
}
In [55]:
class Arithmetic(
    val op: Op,
    val left: Expr,
    val right: Expr): Expr() {
    override fun eval(scope: Context): Data {
        val x = (left.eval(scope) as IntData).value
        val y = (right.eval(scope) as IntData).value
        return IntData(
            when(op) {
                Op.Add -> x + y
                Op.Mul -> x * y
                Op.Sub -> x - y
                Op.Div -> x / y
            }
        )
    }
}
In [56]:
// Use BinaryOp to implement 42 + 1
Arithmetic(Op.Add, IntLiteral(42), IntLiteral(1))
.eval(Context())
Out[56]:
Int(43)
In [57]:
// Use BinaryOp to implement (20 + (11*2)) + 1

Arithmetic(Op.Add,
        Arithmetic(Op.Add,
                 IntLiteral(20),
                 Arithmetic(Op.Mul,
                         IntLiteral(11),
                         IntLiteral(2))),
         IntLiteral(1))
.eval(Context())
Out[57]:
Int(43)

Binding as an expression.

x = 42
In [58]:
class Bind(val name: String, val expr: Expr): Expr() {
    override fun eval(scope: Context): Data {
        val data = expr.eval(scope)
        scope[name] = data
        return data
    }
}
In [59]:
// Use bind to update scope with x=42
val scope = Context()
println("> $scope")
Bind("x", IntLiteral(42)).eval(scope)
println("> $scope")
> {}
> {x=Int(42)}

Dereference symbol value as an expression.

x
In [60]:
class Deref(val name: String): Expr() {
    override fun eval(scope: Context): Data {
        val data: Data? = scope[name]
        if(data == null)  {
            throw Exception("Variable ${name} does not exist.")
        } else {
            return data
        }
    }
}
In [61]:
run(Deref("x"))
[err] java.lang.Exception: Variable x does not exist.

Block is a list of expressions. Its result is the result of the last expression.

x = 42
y = 1
x + y
In [62]:
class Block(val exprList: List<Expr>): Expr() {
    override fun eval(scope: Context): Data {
        var result: Data = IntData(0)
        exprList.forEach {
            result = it.eval(scope)
        }
        return result
    }
}
In [63]:
// Run multiple expressions in a single block
val program = Block(listOf(
    Bind("x", IntLiteral(42)),
    Bind("y", IntLiteral(1)),
    Arithmetic(Op.Add, Deref("x"), Deref("y"))
))

run(program)
=> Int(43)

Boolean operations as expressions

Comparison expression

In [64]:
enum class Comparator {
    LT, // <
    LE, // <=
    GT, // >
    GE, // >=
    EQ, // ==
    NE, // !=
}
In [65]:
class Compare(
    val cmp: Comparator, val left: Expr, val right: Expr
): Expr() {
    override fun eval(scope: Context): Data {
        val x = (left.eval(scope) as IntData).value
        val y = (right.eval(scope) as IntData).value
        return BoolData(
            when(cmp) {
                Comparator.LT -> x < y
                Comparator.LE -> x <= y
                Comparator.GT -> x > y
                Comparator.GE -> x >= y
                Comparator.EQ -> x == y
                Comparator.NE -> x != y
            }
        )
    }
}
In [66]:
run(
    Compare(
        Comparator.EQ,
        IntLiteral(42),
        IntLiteral(1)
    )
)
=> no

IfElse as an expression

x = 42
y = 1
if(x < y) {
  x - y
} else {
  x + y
}
In [67]:
class IfElse(
    val cond: Expr,
    val trueExpr: Expr,
    val falseExpr: Expr
) : Expr() {
    override fun eval(scope: Context): Data {
       val condData = cond.eval(scope)
       val flag = (condData as BoolData).value
       return if(flag) {
           trueExpr.eval(scope)
       } else {
           falseExpr.eval(scope)
       }
    }
}
In [68]:
// Use IfElse to implement the code

Block(listOf(
    Bind("x", IntLiteral(42)),
    Bind("y", IntLiteral(1)),
    IfElse(
        Compare(Comparator.LT,
               Deref("x"),
               Deref("y")),
        Arithmetic(Op.Sub,
                Deref("x"),
                Deref("y")),
        Arithmetic(Op.Add,
                Deref("x"),
                Deref("y"))
    )
)).eval(Context())
Out[68]:
Int(43)

Loop

The while loop is similar to IfElse:

sum = 0
i = 0
loop i < 1000 {
  sum = sum + i
  i = i + 1
}
sum
In [69]:
class Loop(val cond: Expr, val body: Expr) : Expr() {
    override fun eval(scope: Context): Data {
        val MAX_ITER = 10000
        var flag = cond.eval(scope) as BoolData
        var result:Data = IntData(0)
        var counter = 0
        
        while(flag.value && counter < MAX_ITER) {
            result = body.eval(scope)
            counter += 1
            flag = cond.eval(scope) as BoolData
        }
        
        if(counter == MAX_ITER) {
            throw Exception("Did you forget to stop your loop? ${MAX_ITER}")
        }
        
        return result
    }
}
sum = 0
i = 0
loop i < 1000 {
  sum = sum + i
  i = i + 0
}
sum
In [70]:
val program = Block(listOf(
    Bind("sum", IntLiteral(0)),
    Bind("i", IntLiteral(0)),
    Loop(
        // Condition
        Compare(
            Comparator.LT,
            Deref("i"),
            IntLiteral(1000)
        ),
        // Body
        Block(listOf(
            Bind("sum", Arithmetic(Op.Add, Deref("sum"), Deref("i"))),
            Bind("i", Arithmetic(Op.Add, Deref("i"), IntLiteral(1)))
        )),
    ),
    Deref("sum")
))

run(program)
=> Int(499500)

Function

We need a FuncData which will describe a function declaration.

In [71]:
class FuncData(
    val name: String,
    val parameters: List<String>,
    val body: Expr
): Data() {
    override fun toString(): String {
        val params = parameters.joinToString(",")
        return "${name}($params)"
    }
}
In [72]:
FuncData(
    "add3",
    listOf("i", "j", "k"),
    Arithmetic(Op.Add,
              Deref("i"),
              Arithmetic(Op.Add, Deref("j"), Deref("k"))))
Out[72]:
add3(i,j,k)

Function declaration, similar to binding.

add = function(i,j,k) {
  i + j + k
}
In [73]:
class Declare(
    val funcname: String,
    val parameters: List<String>,
    val body: Expr
): Expr() {
    override fun eval(scope: Context): Data {
        val f = FuncData(funcname, parameters, body)
        scope[funcname] = f
        return f
    }
}

Function invocation

add(10, 20, 30)
In [74]:
class Invoke(
    val funcname: String,
    val args: List<Expr>
): Expr() {
    override fun eval(scope: Context): Data {
        val f:Data? = scope[funcname]
        if(f == null) {
            throw Exception("Function {$funcname} does not exist.")
        }
        if(!(f is FuncData)) {
            throw Exception("${f} is not a function.")
        }
        if(f.parameters.size != args.size) {
            throw Exception("${f} expects ${f.parameters.size} arguments.")
        }
        
        val childscope = Context(scope)
        f.parameters.zip(
            args,
            fun (parameter, arg) {
                childscope[parameter] = arg.eval(scope)
            })
        
        return f.body.eval(childscope)
    }
}
i = 10
x = 20
function add3(i,j,k) {
  i + j + k
}

add3(1,2,3)

i
In [80]:
val program = Block(
    listOf(
        // x = 10
        Bind("i", IntLiteral(10)),
        // y = 20
        Bind("x", IntLiteral(20)),
        // declaration
        Declare("add3",
               listOf("i", "j", "k"),
               Arithmetic(Op.Add,
                  Deref("i"),
                  Arithmetic(Op.Add, Deref("j"), Deref("k")))),
        // invoke the function
        Invoke("add3", listOf(
            Deref("i"),
            Deref("x"),
            Deref("x"),
        )),
    )
)

run(program)
=> Int(50)

Use loop to implement sum.

function sum(n) {
  i = 1
  total = 0
  while i <= n {
    total = total + i
    i += 1
  }
  total
}

sum(1000)
In [85]:
val program = Block(
    listOf(
        Declare("sum", listOf("n"),
            Block(
                listOf(
                    Bind("i", IntLiteral(1)),
                    Bind("total", IntLiteral(0)),
                    Loop(
                        Compare(
                            Comparator.LE,
                            Deref("i"),
                            Deref("n")
                        ),
                        Block(
                            listOf(
                                Bind("total", 
                                     Arithmetic(Op.Add,
                                                Deref("total"),
                                                Deref("i"))),
                                Bind("i",
                                    Arithmetic(Op.Add,
                                              Deref("i"),
                                              IntLiteral(1)))))
                    ),
                    Deref("total")
                )
            )),
        Invoke("sum", listOf(IntLiteral(3)))
    )
)

run(program)
=> Int(6)

Use recursion to implement sum.

function sum_recur(n) {
  if n == 0 {
    0
  } else {
    sum_recur(n-1) + n
  }
}

sum_recur(1000)
In [86]:
val program = Block(
    listOf(
        Declare("sum_recur",
           listOf("n"),
           IfElse(
               Compare(
                   Comparator.EQ,
                   Deref("n"),
                   IntLiteral(0)
               ),
               IntLiteral(0),
               Arithmetic(
                   Op.Add,
                   Invoke("sum_recur", listOf(
                       Arithmetic(Op.Sub, Deref("n"), IntLiteral(1))
                   )),
                   Deref("n")
               )
           )),
        Invoke("sum_recur", listOf(IntLiteral(3)))
    )
)

run(program)
=> Int(6)
In [ ]: