compiler: finish parser (for math expressions)

This commit is contained in:
Luna 2019-06-01 21:32:25 -03:00
parent 230fef20b5
commit 0f8e19adf1
2 changed files with 152 additions and 6 deletions

View file

@ -13,7 +13,8 @@ const TokenType = tokens.TokenType;
const Value = values.Value; const Value = values.Value;
const OpCode = chunks.OpCode; const OpCode = chunks.OpCode;
pub const Parser = struct { /// Holds parser state for the compiler.
const Parser = struct {
previous: Token = undefined, previous: Token = undefined,
current: Token = undefined, current: Token = undefined,
@ -22,6 +23,93 @@ pub const Parser = struct {
panicMode: bool = false, panicMode: bool = false,
}; };
/// Represents the order of operations in the parser.
const Precedence = enum(u5) {
None,
Assignment, // =
Or, // or
And, // and
Equality, // == !=
Comparison, // < > <= >=
Term, // + -
Factor, // * /
Unary, // ! -
Call, // . () []
Primary,
};
const ParseFn = fn (*Compiler) anyerror!void;
const ParseRule = struct {
prefix: ?ParseFn = null,
infix: ?ParseFn = null,
precedence: Precedence = Precedence.None,
};
/// For each token, this defines a parse rule for it.
var rules = []ParseRule{
// for LEFT_PAREN, we determine it as a call precedence
// plus a prefix parse function of grouping
ParseRule{ .prefix = Compiler.grouping, .precedence = .Call },
ParseRule{},
ParseRule{},
ParseRule{},
ParseRule{},
// dot token, means a call too, for things like a.b
ParseRule{ .precedence = .Call },
// specific to -, as it can be an unary operator when its a prefix
// of something, or a binary one, when its a infix or another thing.
ParseRule{
.prefix = Compiler.unary,
.infix = Compiler.binary,
.precedence = .Term,
},
ParseRule{ .infix = Compiler.binary, .precedence = .Term },
ParseRule{},
// slash is a binary operator, as well as star.
ParseRule{ .infix = Compiler.binary, .precedence = .Factor },
ParseRule{ .infix = Compiler.binary, .precedence = .Factor },
// as the token enum says, those are 1/2 char tokens.
ParseRule{},
// this is specifically for the != operator
ParseRule{ .precedence = .Equality },
ParseRule{},
// this is specifically for the == operator
ParseRule{ .precedence = .Equality },
// all the comparison ones
ParseRule{ .precedence = .Comparison },
ParseRule{ .precedence = .Comparison },
ParseRule{ .precedence = .Comparison },
ParseRule{ .precedence = .Comparison },
ParseRule{},
ParseRule{},
ParseRule{ .prefix = Compiler.number },
ParseRule{ .precedence = .And },
ParseRule{},
ParseRule{},
ParseRule{},
ParseRule{},
ParseRule{},
ParseRule{},
ParseRule{},
ParseRule{ .precedence = .Or },
ParseRule{},
ParseRule{},
ParseRule{},
ParseRule{},
ParseRule{},
ParseRule{},
ParseRule{},
ParseRule{},
};
pub const Compiler = struct { pub const Compiler = struct {
src: []const u8, src: []const u8,
stdout: vm.StdOut, stdout: vm.StdOut,
@ -29,12 +117,14 @@ pub const Compiler = struct {
parser: Parser, parser: Parser,
scanr: Scanner = undefined, scanr: Scanner = undefined,
chunk: *chunks.Chunk, chunk: *chunks.Chunk,
debug_flag: bool = false,
pub fn init( pub fn init(
allocator: *Allocator, allocator: *Allocator,
chunk: *chunks.Chunk, chunk: *chunks.Chunk,
stdout: vm.StdOut, stdout: vm.StdOut,
source: []const u8, source: []const u8,
debug_flag: bool,
) Compiler { ) Compiler {
return Compiler{ return Compiler{
.src = source, .src = source,
@ -42,6 +132,7 @@ pub const Compiler = struct {
.allocator = allocator, .allocator = allocator,
.stdout = stdout, .stdout = stdout,
.parser = Parser{}, .parser = Parser{},
.debug_flag = debug_flag,
}; };
} }
@ -117,6 +208,10 @@ pub const Compiler = struct {
fn end(self: *Compiler) !void { fn end(self: *Compiler) !void {
try self.emitReturn(); try self.emitReturn();
if (self.debug_flag and !self.parser.hadError) {
try self.currentChunk().disassemble(self.stdout, "code");
}
} }
fn grouping(self: *Compiler) !void { fn grouping(self: *Compiler) !void {
@ -126,21 +221,66 @@ pub const Compiler = struct {
/// Emits bytecode for a number being loaded into the code. /// Emits bytecode for a number being loaded into the code.
fn number(self: *Compiler) !void { fn number(self: *Compiler) !void {
var value: f64 = try std.fmt.parseFloat(f64, parser.previous.lexeme); var value: f64 = try std.fmt.parseFloat(
f64,
self.parser.previous.lexeme,
);
try self.emitConstant(value); try self.emitConstant(value);
} }
/// Emits bytecode for a given unary. /// Emits bytecode for a given unary.
fn unary(self: *Compiler) !void { fn unary(self: *Compiler) !void {
var ttype = self.parser.previous.ttype; var ttype = self.parser.previous.ttype;
try self.expression(); try self.parsePrecedence(.Unary);
switch (ttype) { switch (ttype) {
.MINUS => try self.emitByte(OpCode.Negate), .MINUS => try self.emitByte(OpCode.Negate),
else => unreachable, else => unreachable,
} }
} }
fn expression(self: *Compiler) !void {} fn binary(self: *Compiler) !void {
var op_type = self.parser.previous.ttype;
var rule: *ParseRule = self.getRule(op_type);
try self.parsePrecedence(@intToEnum(Precedence, @enumToInt(rule.precedence) + 1));
switch (op_type) {
.PLUS => try self.emitByte(OpCode.Add),
.MINUS => try self.emitByte(OpCode.Subtract),
.STAR => try self.emitByte(OpCode.Multiply),
.SLASH => try self.emitByte(OpCode.Divide),
else => unreachable,
}
}
fn parsePrecedence(self: *Compiler, precedence: Precedence) !void {
try self.advance();
var as_int = @enumToInt(precedence);
var prefix_rule_opt = self.getRule(self.parser.previous.ttype).prefix;
if (prefix_rule_opt) |prefix_rule| {
try prefix_rule(self);
while (as_int <= @enumToInt(self.getRule(self.parser.current.ttype).precedence)) {
try self.advance();
var infix_rule_opt = self.getRule(self.parser.previous.ttype).infix;
if (infix_rule_opt) |infix_rule| {
try infix_rule(self);
}
}
} else {
self.errorPrevious("Expect expression.");
return;
}
}
fn getRule(self: *Compiler, ttype: TokenType) *ParseRule {
return &rules[@enumToInt(ttype)];
}
fn expression(self: *Compiler) !void {
try self.parsePrecedence(.Assignment);
}
/// Compile the source given when initializing the compiler /// Compile the source given when initializing the compiler
/// into the given chunk. /// into the given chunk.
@ -148,7 +288,7 @@ pub const Compiler = struct {
self.scanr = try scanner.Scanner.init(self.allocator, self.src); self.scanr = try scanner.Scanner.init(self.allocator, self.src);
try self.advance(); try self.advance();
//try self.expression(); try self.expression();
try self.consume(.EOF, "Expect end of expression."); try self.consume(.EOF, "Expect end of expression.");
try self.end(); try self.end();

View file

@ -167,7 +167,13 @@ pub const VM = struct {
//return res; //return res;
var chk = try Chunk.init(self.allocator); var chk = try Chunk.init(self.allocator);
var cmpr = Compiler.init(self.allocator, &chk, self.stdout, self.src); var cmpr = Compiler.init(
self.allocator,
&chk,
self.stdout,
self.src,
self.debug_flag,
);
if (!try cmpr.compile(&chk)) { if (!try cmpr.compile(&chk)) {
return InterpretResult.CompileError; return InterpretResult.CompileError;
} }