Skip to content

Commit

Permalink
Initial Evaluator, Object System, Tests cases
Browse files Browse the repository at this point in the history
Evalutator: Added Infix Expressions, Prefix expressions, Integer, boolean, Null
Object sys: Object Type with Integer, boolean and Null types.
Test Cases to cover changes
Toolchain upKeep: 0.12.x raises error to unmutated variables, thus mandating using `const`
  • Loading branch information
kamva9697 committed Nov 23, 2023
1 parent d9dda59 commit fd36fdb
Show file tree
Hide file tree
Showing 10 changed files with 408 additions and 40 deletions.
2 changes: 1 addition & 1 deletion src/ast.zig
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ pub const Node = struct {
}
},
.Block => {
var blockNode = node.cast(.Block).?;
const blockNode = node.cast(.Block).?;
for (blockNode.statements.items) |st| {
try st.toString(writer);
}
Expand Down
215 changes: 215 additions & 0 deletions src/evaluator.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
const std = @import("std");
const Lexer = @import("lexer.zig");
const Parser = @import("parser.zig");
const Ast = @import("ast.zig");
const Node = Ast.Node;
const _Object = @import("object.zig");
const Object = _Object.Object;
const ObjectType = Object.ObjectType;
const Allocator = std.mem.Allocator;
const Math = std.math;

// Interned values
const TRUE_VALUE = Object.Boolean{ .value = true };
const FALSE_VALUE = Object.Boolean{ .value = false };
const NULL_VALUE = Object.Null{};

// Todo: Block, FunctionLiteral,
pub fn eval(alloc: Allocator, node: *Ast.Node) !?*Object {
return switch (node.id) {
.IntegerLiteral => {
const intNode = node.cast(.IntegerLiteral).?;
var obj = try createObject(
Object.Integer,
alloc,
Object.Integer{
.value = intNode.value,
},
);
return &obj.base;
},
.Boolean => {
const boolNode = node.cast(.Boolean).?;
var obj = try createObject(
Object.Boolean,
alloc,
if (boolNode.value) TRUE_VALUE else FALSE_VALUE,
);
return &obj.base;
},
.PrefixExpression => {
const prefNode = node.cast(.PrefixExpression).?;

const right = try eval(alloc, prefNode.rightExprPtr.?);
return try evalPrefixExpressions(alloc, prefNode.operator, right.?);
},
.InfixExpression => {
const infixNode = node.cast(.InfixExpression).?;

const left = try eval(alloc, infixNode.leftExprPtr.?);
const right = try eval(alloc, infixNode.rightExprPtr.?);
return try evalInfixExpression(
alloc,
infixNode.operator,
right.?,
left.?,
);
},
else => {
var obj = try createObject(Object.Null, alloc, NULL_VALUE);
return &obj.base;
},
};
}

pub fn evalStatements(alloc: Allocator, tree: Ast.Tree) !?*Object {
var result: ?*Object = null;

for (tree.statements.items) |stmt| {
result = (try eval(alloc, stmt)).?;
}
return result;
}

pub fn evalInfixExpression(
alloc: Allocator,
op: Ast.Operator,
right: *Object,
left: *Object,
) !*Object {
if (right.ty == left.ty) {
return try evalIntegerInfixExpressions(
alloc,
op,
right,
left,
);
}
var obj = try createObject(Object.Null, alloc, NULL_VALUE);
return &obj.base;
}

pub fn evalIntegerInfixExpressions(
alloc: Allocator,
op: Ast.Operator,
right: *Object,
left: *Object,
) !*Object {
const leftIntegerObj = left.cast(.Integer).?;
const leftVal = leftIntegerObj.value;
const rightIntegerObj = right.cast(.Integer).?;
const rightVal = rightIntegerObj.value;

return switch (op) {
.plus => {
var obj = try createObject(
Object.Integer,
alloc,
Object.Integer{
.value = leftVal + rightVal,
},
);
return &obj.base;
},
.minus => {
var obj = try createObject(
Object.Integer,
alloc,
Object.Integer{
.value = leftVal - rightVal,
},
);
return &obj.base;
},
.multiply => {
var obj = try createObject(
Object.Integer,
alloc,
Object.Integer{
.value = leftVal * rightVal,
},
);
return &obj.base;
},
.divide => {
var obj = try createObject(
Object.Integer,
alloc,
Object.Integer{
.value = try Math.divTrunc(i64, leftVal, rightVal),
},
);
return &obj.base;
},
else => {
var obj = try createObject(Object.Null, alloc, NULL_VALUE);
return &obj.base;
},
};
}

pub fn evalPrefixExpressions(
alloc: Allocator,
op: Ast.Operator,
right: *Object,
) !*Object {
return switch (op) {
.not => try evalBangOperatorExpressions(alloc, right),
.minus => try evalMinusOperatorExpressions(alloc, right),
else => {
var obj = try createObject(Object.Null, alloc, NULL_VALUE);
return &obj.base;
},
};
}

pub fn evalMinusOperatorExpressions(
alloc: Allocator,
right: *Object,
) !*Object {
if (right.ty != .Integer) {
var obj = try createObject(Object.Null, alloc, NULL_VALUE);
return &obj.base;
}

const boolNode = right.cast(.Integer).?;
const value = boolNode.value;
var obj = try createObject(
Object.Integer,
alloc,
Object.Integer{ .value = -value },
);
return &obj.base;
}

pub fn evalBangOperatorExpressions(alloc: Allocator, right: *Object) !*Object {
return switch (right.ty) {
.Boolean => {
const boolObj = right.cast(.Boolean).?;
return switch (boolObj.value) {
true => {
var obj = try createObject(Object.Boolean, alloc, FALSE_VALUE);
return &obj.base;
},
false => {
var obj = try createObject(Object.Boolean, alloc, TRUE_VALUE);
return &obj.base;
},
};
},
.Null => {
var obj = try createObject(Object.Null, alloc, NULL_VALUE);
return &obj.base;
},
else => {
var obj = try createObject(Object.Boolean, alloc, FALSE_VALUE);
return &obj.base;
},
};
}

fn createObject(comptime T: type, alloc: Allocator, value: T) !*T {
const obj = try alloc.create(T);
obj.* = value;
return obj;
}
82 changes: 82 additions & 0 deletions src/evaluator_tests.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
const std = @import("std");
const Lexer = @import("lexer.zig");
const _Parser = @import("parser.zig");
const Parser = _Parser.Parser;
const _Object = @import("object.zig");
const Ast = @import("ast.zig");
const Object = _Object.Object;
const ObjectType = Object.ObjectType;
const evaluator = @import("evaluator.zig");
const testing = std.testing;

/// Global Allocator
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
var arena = std.heap.ArenaAllocator.init(gpa.allocator());
var alloc = arena.allocator(); // the ast Test Deallocates

test "testEvalIntegerExpressions" {
try testIntegerExpressions("5", 5);
try testIntegerExpressions("10", 10);
try testIntegerExpressions("-5", -5);
try testIntegerExpressions("-10", -10);
try testIntegerExpressions("5 + 5", 10);
try testIntegerExpressions("5 + 5 + 5", 15);
try testIntegerExpressions("5 + 5 + 5 + 5 - 10", 10);
try testIntegerExpressions("2 * 2 * 2 * 2 * 2", 32);
try testIntegerExpressions("-50 + 100 + -50", 0);
try testIntegerExpressions("5 * 2 + 10", 20);
try testIntegerExpressions("5 + 2 * 10", 25);
try testIntegerExpressions("20 + 2 * -10", 0);
try testIntegerExpressions("50 / 2 * 2 + 10", 60);
try testIntegerExpressions("2 * (5 + 10)", 30);
try testIntegerExpressions("3 * 3 * 3 + 10", 37);
try testIntegerExpressions("3 * (3 * 3) + 10", 37);
try testIntegerExpressions("(5 + 10 * 2 + 15 / 3) * 2 + -10", 50);
}

test "testEvalBooleanExpressions" {
try testBooleanExpressions("true", true);
try testBooleanExpressions("false", false);
try testBooleanExpressions("!true", false);
try testBooleanExpressions("!false", true);
try testBooleanExpressions("!5", false);
try testBooleanExpressions("!!true", true);
try testBooleanExpressions("!!false", false);
try testBooleanExpressions("!!5", true);

arena.deinit();
}

fn testBangOperator(input: [:0]const u8, expected: bool) !void {
const evaluated = (try testEval(input)).?;
try testBooleanObject(evaluated, expected);
}

fn testBooleanExpressions(input: [:0]const u8, expected: bool) !void {
const evaluated = (try testEval(input)).?;
try testBooleanObject(evaluated, expected);
}

fn testIntegerExpressions(input: [:0]const u8, expected: i64) !void {
const evaluated = (try testEval(input)).?;
try testIntegerObject(evaluated, expected);
}

fn testEval(input: [:0]const u8) !?*Object {
var parser = Parser.init(input, alloc);
const astTree = (try parser.parseProgram());

return evaluator.evalStatements(alloc, astTree);
}

fn testIntegerObject(obj: *Object, expected: i64) !void {
try testing.expectEqual(ObjectType.Integer, obj.ty);
const integerValue = obj.cast(.Integer).?;
try testing.expectEqual(expected, integerValue.value);
}

fn testBooleanObject(obj: *Object, expected: bool) !void {
try testing.expectEqual(ObjectType.Boolean, obj.ty);
const boolValue = obj.cast(.Boolean).?;
try testing.expectEqual(expected, boolValue.value);
}
8 changes: 4 additions & 4 deletions src/lexer.zig
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub const Lexer = struct {
const Self = @This();

pub fn init(source: [:0]const u8) Self {
var lexer = Lexer{ .input = source };
const lexer = Lexer{ .input = source };
return lexer;
}

Expand Down Expand Up @@ -132,7 +132,7 @@ test "ReadChar" {
test "NextToken" {
const input = "let five = 5;";
var lex = Lexer.init(input);
var tok = lex.nextToken();
const tok = lex.nextToken();

try testing.expect(.LET == tok.Type);
try testing.expectEqualStrings("let", tok.Literal);
Expand All @@ -142,14 +142,14 @@ test "ReadNumber" {
const input = "523 ";
var lex = Lexer.init(input);
lex.readChar();
var num = lex.readNumber();
const num = lex.readNumber();
try testing.expectEqualStrings("523", num);
}

test "ReadIndentifier" {
const input = "five ";
var lex = Lexer.init(input);
lex.readChar();
var ident = lex.readIdentifier();
const ident = lex.readIdentifier();
try testing.expectEqualStrings("five", ident);
}
4 changes: 2 additions & 2 deletions src/lexerTest.zig
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ pub const arithmeticOperators = [_]Test{
test "LexerEOF" {
const input = "";
var lex = Lexer.init(input);
var tok = lex.nextToken();
const tok = lex.nextToken();
try testing.expect(TokenType.EOF == tok.Type);
}

Expand Down Expand Up @@ -133,7 +133,7 @@ test "VariableAssignmentTest" {

fn run_test(expectedTokens: []const Test, lex: *Lexer) !void {
for (expectedTokens) |tc| {
var tok = lex.nextToken();
const tok = lex.nextToken();
try testing.expectEqual(tc.expectedType, tok.Type);
try testing.expectEqualStrings(tc.expectedLiteral, tok.Literal);
}
Expand Down
Loading

0 comments on commit fd36fdb

Please sign in to comment.