10000 GitHub - guntas-13/CS327-Compilers: Compiler for our language osl. alongwith a custom JSON parser and a complete version of Forth Language
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Compiler for our language osl. alongwith a custom JSON parser and a complete version of Forth Language

License

Notifications You must be signed in to change notification settings

guntas-13/CS327-Compilers

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CS327-Compilers

Overall Grammar

program → declaration* EOF;

declaration → funDecl | varDecl | statement;

funDecl → "fn" IDENTIFIER "(" parameters? ")" block;
varDecl → "var" IDENTIFIER (":=" expression)? ";";
statement → ifStmt | printStmt | returnStmt | block | expressionStmt;

ifStmt → "if" expression statement ("else" statement)?;
printStmt → "print" "(" expression ")" ";";
returnStmt → "return" (expression)? ";";
block → "{" declaration* "}";

expressionStmt → expression ";";
expression → assignment | expB;
assignment → IDENTIFIER ":=" expB;

parameters → IDENTIFIER ("," IDENTIFIER)*;

expB → logicOr;
logicOr → logicAnd ("||" logicAnd)*;
logicAnd → comparison ("&&" comparison)*;
comparison → add (("<" | ">" | "<=" | ">=" | "=" | "!=") add)*;
add → mul (("+" | "-") mul)*;
mul → exp (("*" | "/" | "%") exp)*;
exp → unary ("^" unary)*;
unary → ("-" | "√") unary | atom;

atom → NUMBER | IDENTIFIER | funCall | "(" expression ")";
funCall → IDENTIFIER "(" arguments? ")";
arguments → expression ("," expression)*;

NUMBER → DIGIT+ ("." DIGIT*)? | "." DIGIT+;
DIGIT → "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
IDENTIFIER → LETTER (LETTER | DIGIT | "_")*;
LETTER → "a" .. "z" | "A" .. "Z";

Run Project Euler Analysis

python3 eulerProblems.py

Project Euler Problems in osl.

euler_p1 = """
letFunc F(x, s) {
    if (x = 1000) return s;
    if (x % 3 = 0 || x % 5 = 0)
        return F(x + 1, s + x);
    return F(x + 1, s);
}
F(0, 0);
"""
euler_p2 = """
letFunc fib(a, b, s) {
    if (a >= 4000000) return s;
    if (a % 2 = 0)
        return fib(b, a + b, s + a);
    return fib(b, a + b, s);
}
fib(0, 1, 0);
"""
euler_p3 = """
letFunc prime(n, i) {
    if (i * i > n) return n;
    if (n % i = 0)
        return prime(n / i, i);
    return prime(n, i + 1);
}
var n := 600851475143;
prime(n, 2);
"""
euler_p4 = """
letFunc isPal(n, rev, org) {
    if (n = 0) return rev = org;
    return isPal(n/10, rev*10 + n%10, org);
}
letFunc F(i, j, maxPal) {
    if (i < 100) return maxPal;
    if (j < 100) return F(i - 1, i - 1, maxPal);
    var prod := i * j;
    if ((prod > maxPal) && (isPal(prod, 0, prod)))
        maxPal := prod;
    return F(i, j - 1, maxPal);
}
F(999, 999, 0);
"""

Coverage with Automated Unit Tests

python3 -m pytest test_unit_tests.py --cov=calculator_extended_resolved --cov-report=html

./test_unit_tests.py

Coverage Report (./htmlcov/index.html)

Overall Coverage

Function Coverage

Class Coverage

Make Custom Tests

python3 unit_tests.py

Addition of Assignment (22 March 2025)

@dataclass
class Assign(AST):
    var: AST
    e1: AST

def parse(s: str) -> AST:
    t = peekable(lex(s))
    i = 0

    def parse_expression():
        # expression -> expB | assignment
        # first parse the lhs, if it's a variable and next token is ':=' then it's an assignment
        # otherwise it's an expB so return it as is.
        ast = parse_bool()
        if not isinstance(ast, Variable) and peek() == OperatorToken(":="):
            raise ParseErr(f"Expected variable on the left side of assignment := operator at index {i}")
        if isinstance(ast, Variable) and peek() == OperatorToken(":="):
            consume(OperatorToken, ":=")
            e1 = parse_bool()
            return Assign(ast, e1)
        return ast


def resolve(program: AST, env: Environment = None) -> AST:

    case Assign(Variable(varName, _), e1):
            re1 = resolve_(e1)
            return Assign(Variable(varName, env.get(varName)), re1)

def e(tree: AST, env: Environment = None) -> int | float | bool:

    case Assign(Variable(varName, i), e1):
            v1 = e_(e1)
            env.update(f"{varName}:{i}", v1)
            return None
exp = """
var x := 0;
var n := 121;

x := (x * 10) + (n % 10);
n := n / 10;
print(x);
print(n);

x := (x * 10) + (n % 10);
n := n / 10;
print(x);
print(n);

x := (x * 10) + (n % 10);
n := n / 10;
print(x);
n;
"""

Addition of Closures (19 March 2025)

@dataclass
class FunObj:
    params: List[AST]
    body: AST
    env: Environment

def e(tree: AST, env: Environment = None) -> int | float | bool:

    match tree:
        case LetFun(Variable(varName, i), params, body):
            # Closure -> Copy of Environment taken along with the declaration!
            funObj = FunObj(params, body, None)
            env.add(f"{varName}:{i}", funObj)
            funObj.env = env.copy()
            return None

        case CallFun(Variable(varName, i), args):
            fun = env.get(f"{varName}:{i}")
            rargs = [e_(arg) for arg in args]

            # use the environment that was copied when the function was defined
            call_env = fun.env.copy()
            call_env.enter_scope()
            for param, arg in zip(fun.params, rargs):
                call_env.add(f"{param.varName}:{param.id}", arg)

            rbody = e(fun.body, call_env)
            return rbody
exp = """
letFunc f1()
{
    var x := 10;
    letFunc f2()
    {
        return x;
    }
    return f2;
}
var msg := f1();
msg();
"""
exp = """
var x := 6;

letFunc F(x)
{
    letFunc G()
    {
        return x;
    }
    return G;
}

var y := F(5);
y() * y();
"""
exp = """
letFunc fact(n)
{
    if (n = 0)
        return 1;
    return n * fact(n - 1);
}
fact(5);
"""

Dangling "else" matched to latest "if" (20 March 2025)

@dataclass
class IfUnM(AST):
    condition: AST
    then_body: AST
def parse(s: str) -> AST:
    def parse_if():
            consume(KeyWordToken, "if")
            condition = parse_expression()
            then_body = parse_statement()
            if peek() == KeyWordToken("else"):
                consume(KeyWordToken, "else")
                else_body = parse_statement()
                return If(condition, then_body, else_body)
            return IfUnM(condition, then_body)
exp = """
var x := 15;
if (x > 10)
if (x < 20)
print(x + 1);
else print(x - 1);
x;
"""

Functions as First-Class Objects (like variables)

@dataclass
class LetFun(AST):
    name: AST   # considering functions as first-class just like variables else it'll be str
    params: List[AST]
    body: AST
    expr: AST

@dataclass
class CallFun(AST):
    fn: AST     # considering functions as first-class just like variables else it'll be str
    args: List[AST]
def resolve(program: AST, env: Environment = None) -> AST:
    if env is None:
        env = Environment()

    def resolve_(program: AST) -> AST:
        return resolve(program, env)

    match program:
        case LetFun(Variable(varName, _), params, body, expr):
                    env.enter_scope()
                    env.add(varName, i := fresh())
                    env.enter_scope()
                    new_params = []
                    for param in params:
                        env.add(param.varName, j := fresh())
                        new_params.append(Variable(param.varName, j))
                    new_body = resolve_(body)
                    env.exit_scope()
                    new_expr = resolve_(expr)
                    env.exit_scope()
                    return LetFun(Variable(varName, i), new_params, new_body, new_expr)

        case CallFun(fn, args):
            rfn = resolve_(fn)
            rargs = [resolve_(arg) for arg in args]
            return CallFun(rfn, rargs)

Updated - Lexer, Parser Added!

Add new token for call - the lexer checks if previously yielded token is KeyWordToken(letFun) then the next expected token is the function variable in the definition i.e VariableToken(<func-name>), otherwise it's a FunCallToken(<func-name>).

@dataclass
class FunCallToken(Token):
    funName: str

OLDER VERSIONS (From 12 Feb 2025)

Project Euler Q1

exp = """
letFunc func(x, s)
{
     if x = 1000 then
         s
     else if x % 3 = 0 || x % 5 = 0 then
         func(x + 1, s + x)
     else
         func(x + 1, s)
}
in
func(0, 0)
end
"""
## PROJECT EULER 1
exp = LetFun(Variable("func"),
             [Variable("x"), Variable("s")],
             If(BinOp("=", Variable("x"), Number("1000")),
                Variable("s"),
                If(BinOp("||", BinOp("=", BinOp("%", Variable("x"), Number("3")), Number("0")), BinOp("=", BinOp("%", Variable("x"), Number("5")), Number("0"))),
                   CallFun(Variable("func"), [BinOp("+", Variable("x"), Number("1")), BinOp("+", Variable("s"), Variable("x"))]),
                   CallFun(Variable("func"), [BinOp("+", Variable("x"), Number("1")), Variable("s")])
                   )
                ),
             CallFun(Variable("func"), [Number("0"), Number("0")]))

Project Euler Q2

exp = """
letFunc fib(a, b, s)
{
    if a >= 4000000 then
        s
    else if a % 2 = 0 then
        fib(b, a + b, s + a)
    else
        fib(b, a + b, s)
}
in
fib(0, 1, 0)
end
"""
## PROJECT EULER 2
exp = LetFun(Variable("fib_sum"),
             [Variable("a"), Variable("b"), Variable("s")],
             If(BinOp(">=", Variable("a"), Number("4000000")),
                Variable("s"),
                If(BinOp("=", BinOp("%", Variable("a"), Number("2")), Number("0")),
                   CallFun(Variable("fib_sum"), [Variable("b"), BinOp("+", Variable("a"), Variable("b")), BinOp("+", Variable("s"), Variable("a"))]),
                   CallFun(Variable("fib_sum"), [Variable("b"), BinOp("+", Variable("a"), Variable("b")), Variable("s")])
                   )
                ),
             CallFun(Variable("fib_sum"), [Number("0"), Number("1"), Number("0")]))

Factorial Code

exp = """
letFunc fact(n)
{
    if n = 0 then
        1
    else
        let x := fact(n - 1) in
        n * x
        end
}
in
fact(5)
end
"""

Static Scoping

exp = """
let x := 5 in
letFunc f(y) {
    x
}
in
letFunc g(z) {
    let x := 6
    in f(z)
    end
}
in
g(0)
end
end
end
"""

Unit Tests

Color coded unit_tests.py for better readability. (fail shown deliberately by not passing through resolve())

Even Older Versions

exp_cond1 = """
if 2 < 3 then
    0 + 5
else
    1 * 6
end
"""
ast = parse(exp_cond1)
dot = visualize_ast(ast)
dot.render("ast_cond", format="png", cleanup=True)
exp_cond = """
if 2 < 3 then
    if 4 > 5 then
        1
    else
        if 6 <= 7 then
            8
        else
            9
        end
    end
else
    10
end
"""
ast = parse(exp_cond)
dot = visualize_ast(ast)
dot.render("ast_nested_cond", format="png", cleanup=True)
exp_sq = "\u221a(4 + 12) + \u221a(9)"
ast = parse(exp_sq)
dot = visualize_ast(ast)
dot.render("ast_sqrt", format="png", cleanup=True)

√(4 + 12) + √(9)

About

Compiler for our language osl. alongwith a custom JSON parser and a complete version of Forth Language

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published
0