The Abominably Naive Go Compiler
In order to build this code, you need golang to be installed on your system.
To install go, see: https://golang.org/doc/install#install
After that, we need your $GOPATH to be set appropriately
and $GOPATH/bin should be in your $PATH:
export GOPATH=/tmp
export PATH=$PATH:$GOPATH/binNow install dep and gorunpkg:
go get github.com/golang/dep/cmd/dep
go get github.com/Vektah/gorunpkgAfter that ensure that the files are placed in $GOPATH/src/tango.
For example, if we have submitted final.zip, unzip it and then:
unzip final.zip
mv final $GOPATH/src/
cd $GOPATH/src
mv final tango
After that, just cd into the folder and run:
make
to fetch the necessary libs and tools and to generate the lexer and codegen.
To run, follow:
Usage: bin/tango [<options>] <file name>
Usage of bin/tango:
-o string
Filename of output executable. Only valid with language None (default "./a.out")
-x string
Language to output. Possible values are IR, ASM and None (default "None")
Some lexical tokens were made independent if they were used in more than one place:
*- Unary, Binary Operator as well as used in types+- Both unary and binary operator-- Both unary and binary operator^- Both unary and binary operator|- Last remaining addition-type Binary Operatormul_op- Multiply-type binary operatorsrel_op- Relational binary operatorsunary_op- Remaining unary operators
Different types of assignment statements were made independent lexical tokens
Few new lexical tokens were added:
<<<and>>>(|and|)[(and)]|||
Earlier syntax: int64(0)
New syntax: int64<<<0>>>
Earlier Syntax: rect{width: 10, height: 5}
New Syntax: rect[(width: 10, height: 5)]
Earlier Syntax: func test() (int, int) { //blah }
New Syntax: func test() (|int, int|) { //blah }
Earlier Syntax: func (g geometry) test() (int, int) { //blah }
New Syntax: func (|g geometry|) test() (int, int) { //blah }
Earlier Syntax: [i * i | i, _ := range arr]
New Syntax: [i * i ||| i, _ := range arr]
Apart from the usual golang imperative features (except goroutines and chans), we support the following extra features:
func myCurry(a int, d int)(b int)(c int) int {
if (d % 2 == 0) {
return a + b + c;
} else {
return a;
}
}
x1 = myCurry(4, 2)
x2 = myCurry(4, 2)(5)
x3 = myCurry(4, 1)(5)(6)
// Type of x1:
func(int)(int) int
// Type of x2:
func(int) int
// Type of x3:
int
// Type of myCurry
func(int)(int)(int) int
xs := []int{1, 2, 3}
doublexs := [x * 2 | _, x := range xs]
Our IR code is the list of instructions where each instruction is represented as structure
struct {
type IRType; // Enum values for Type are: {LBL, BOP, LOP, SOP, DOP, UOP, CBR, JMP, ASN, KEY}
op IROp; // Specific Values of op
arg1 SymbolTableEntry; //
arg2 SymbolTableEntry; //
dst SymbolTableEntry; //
}
A SymbolTableEntry can be of 3 types:
- SymbolTableLiteralEntry: A literal. Just contains value of the literal
- SymbolTableVariableEntry: A variable (virtual register). Contains the memory location of variable in data segment.
- SymbolTableTargetEntry: Contains the actual target for a jmp/branch instruction
The symbol table entries have to be encoded in a specific format in the IR:
- Literals: Must begin with a $
- Variable: Must begin with a r
- Target: Must begin with a #
- All programs have a
_func_mainlabel. - All functions have a label beginning with
_func.
A beginning of a Label. Contains only a dst field.
Instructions can be labelled (with labels being strings) to be referred as target in branch instructions as follows
label:
For binary operations (type = BOP), arg1 and arg2 are arguments for the operation in the same order and dst is the target variable where the result is stored after applying op.
dst = arg1 op arg2
op can have following values:
+: Add-: Subtract*: Multiply&: Bitwise AND|: Bitwise OR^: Bitwise XOR&&: Logical AND||: Logical OR
arg1 and arg2 can be either a variable or a literal.
For logical operations, arg1 and arg2 are arguments for the operation in the same order and dst is the target variable where the result is stored after applying op.
dst = arg1 op arg2
op can have following values:
<: Less Than>: Greater Than<=: Less Than Equal>=: Greater Than Equal==: Equals!=: Not Equals
arg1 and arg2 can be either a variable or a literal (having value either 0 or 1).
For shift operations, arg1 and arg2 are arguments for the operation in the same order and dst is the target variable where the result is stored after applying op.
dst = arg1 op arg2
op can have following values:
<<: Bitwise Shift Left>>: Bitwise Shift Right
arg1 and arg2 can be either a variable or a literal (having value either 0 or 1).
For division operations, arg1 and arg2 are arguments for the operation in the same order and dst is the target variable where the result is stored after applying op.
dst = arg1 op arg2
op can have following values:
/: Divide%: Remainder
arg1 and arg2 can be either a variable or a literal (having value either 0 or 1).
Unary operations are applied as
dst = op arg1
Here op can have following values:
neg: Negate a valuenot: Bitwise Not!: Logical Not
Assignment is fairly simple
dst = arg1
Branch operations can be conditional or unconditional.
Unconditional branches are just a simple call to JMP
jmp target
For conditional branches, following instructions are provided
breq target arg1 arg2 // Branch to target if arg1 == arg2
brlt target arg1 arg2 // Branch to target if arg2 < arg1
brgt target arg1 arg2 // Branch to targtet if arg2 > arg1
brlte arg1 arg2 target // Branch to targtet if arg2 <= arg1
brgte arg1 arg2 target // Branch to targtet if arg2 >= arg1
brneq target arg1 arg2 // Branch to targtet if arg2 != arg1
inc arg1 // Increments the virtual register
dec arg1 // Decrements the virtual register
call dst // calls the function at dst target
param arg1 // Push to stack
ret // return from a function
reti // return a value from a function
setret arg1 // set arg1 to return value of a function
halt // halts the program. We can pass a status code by using param
printi arg1 // Prints a
printc arg1 // Prints a
prints arg1 // Prints a
scani arg1 // Scan into a
scanc arg1 // Scan into a
scans arg1 // Scan into a