Compare commits
7 Commits
6a52297cce
...
9eaa71cbd6
Author | SHA1 | Date |
---|---|---|
Luna | 9eaa71cbd6 | |
Luna | 9e32ff9e16 | |
Luna | c3f0b4b4d5 | |
Luna | 1c8eda7305 | |
Luna | b382c136ec | |
Luna | 3cd19e6515 | |
Luna | 5bb57116c0 |
23
README.md
23
README.md
|
@ -1,6 +1,6 @@
|
|||
# vig
|
||||
|
||||
a [v] parser in zig
|
||||
a [v] parser in zig, also a shitpost taken too far
|
||||
|
||||
[v]: https://vlang.io
|
||||
|
||||
|
@ -9,20 +9,33 @@ for more epic adventures)
|
|||
|
||||
## why
|
||||
|
||||
because i want to learn parsers and what best to do it with a language i'm
|
||||
- because i want to learn parsers and what best to do it with a language i'm
|
||||
negatively charged towards
|
||||
- theres an ast now it looks pretty
|
||||
- i finally understand recursive descent parsers
|
||||
|
||||
## variations
|
||||
|
||||
- `for` is split between `for` and `loop` because my fucking god i cant stand
|
||||
having *four* different variations of `for`.
|
||||
having *four* different variations of `for` to parse.
|
||||
|
||||
- struct initialization is with `Struct.{}`, not `Struct{}`, to remove parsing
|
||||
ambiguities
|
||||
ambiguities (`if a {}` and `a{}`, v solves that with case, e.g structs Must
|
||||
Be Properly Titled and i can't bother with that)
|
||||
|
||||
## todo
|
||||
|
||||
- no `for` yet
|
||||
- no methods yet (`fn (v Type) blah() blah {...}`)
|
||||
- do we really want a type system
|
||||
- do we really want to output c
|
||||
- do we really want to output llvm
|
||||
- do we really want to output x86
|
||||
|
||||
## how
|
||||
|
||||
```
|
||||
- step 1: dab
|
||||
- step 2: ```
|
||||
git clone https://gitdab.com/luna/vig.git
|
||||
cd vig
|
||||
zig build install --prefix ~/.local/
|
||||
|
|
|
@ -37,7 +37,7 @@ fn main(a int) int {
|
|||
println('skirts')
|
||||
}
|
||||
|
||||
cock_and_ball_torture('cbt', 1, 2, 3)
|
||||
test('asd', 1, 2, 3)
|
||||
|
||||
return 23
|
||||
|
||||
|
@ -48,5 +48,7 @@ fn main(a int) int {
|
|||
|
||||
p.x = 69
|
||||
|
||||
println(egg.scramble(3).with(cheddar))
|
||||
println(a.b(3).c(d))
|
||||
|
||||
v()()()
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@ pub const NodeType = enum {
|
|||
ConstDecl,
|
||||
Struct,
|
||||
Block,
|
||||
Expr,
|
||||
Stmt,
|
||||
};
|
||||
|
||||
|
@ -238,7 +237,6 @@ pub const Node = union(NodeType) {
|
|||
|
||||
Block: StmtList,
|
||||
|
||||
Expr: *Expr,
|
||||
Stmt: *Stmt,
|
||||
|
||||
pub fn mkRoot(allocator: *std.mem.Allocator) !*Node {
|
||||
|
|
|
@ -70,12 +70,6 @@ pub fn printNode(node: *Node, ident: usize) void {
|
|||
}
|
||||
},
|
||||
|
||||
.Expr => |expr| {
|
||||
printIdent(ident);
|
||||
printExpr(expr);
|
||||
std.debug.warn("\n");
|
||||
},
|
||||
|
||||
.Stmt => |stmt| {
|
||||
printIdent(ident);
|
||||
printStmt(ident, stmt);
|
||||
|
|
175
src/parser.zig
175
src/parser.zig
|
@ -88,6 +88,8 @@ pub const Parser = struct {
|
|||
return token;
|
||||
}
|
||||
|
||||
/// Consume the current token type, then walk to the next token.
|
||||
/// Returns the consumed token.
|
||||
fn consume(self: *Parser, ttype: TokenType, comptime msg: []const u8) !Token {
|
||||
if (self.check(ttype)) {
|
||||
var tok = self.peek();
|
||||
|
@ -99,16 +101,15 @@ pub const Parser = struct {
|
|||
return Result.CompileError;
|
||||
}
|
||||
|
||||
/// Consume the current token. Gives default error messages
|
||||
fn consumeSingle(self: *Parser, ttype: TokenType) !Token {
|
||||
std.debug.warn("consume {}..?", ttype);
|
||||
|
||||
if (self.check(ttype)) {
|
||||
var cur = self.peek();
|
||||
_ = try self.nextToken();
|
||||
std.debug.warn(" now has {}\n", self.peek());
|
||||
return cur;
|
||||
}
|
||||
|
||||
// TODO maybe this could be entirely comptime?
|
||||
var buf_main: [1000]u8 = undefined;
|
||||
var buf = try std.fmt.bufPrint(
|
||||
buf_main[0..],
|
||||
|
@ -116,11 +117,12 @@ pub const Parser = struct {
|
|||
ttype,
|
||||
self.peek().ttype,
|
||||
);
|
||||
try self.tokenError(self.peek(), buf);
|
||||
|
||||
try self.tokenError(self.peek(), buf);
|
||||
return Result.CompileError;
|
||||
}
|
||||
|
||||
/// check() against multiple tokens
|
||||
fn compareAnyOf(self: *@This(), ttypes: []TokenType) bool {
|
||||
for (ttypes) |ttype| {
|
||||
if (self.check(ttype)) return true;
|
||||
|
@ -129,6 +131,8 @@ pub const Parser = struct {
|
|||
return false;
|
||||
}
|
||||
|
||||
// TODO maybe move helper functions to ast_helper.zig?
|
||||
|
||||
fn mkFnDecl(
|
||||
self: *Parser,
|
||||
name: Token,
|
||||
|
@ -160,12 +164,6 @@ pub const Parser = struct {
|
|||
return node;
|
||||
}
|
||||
|
||||
fn mkExpr(self: *Parser, expr: *Expr) !*ast.Node {
|
||||
var node = try self.allocator.create(Node);
|
||||
node.* = Node{ .Expr = expr };
|
||||
return node;
|
||||
}
|
||||
|
||||
fn mkStmt(self: *Parser, stmt: *Stmt) !*ast.Node {
|
||||
var node = try self.allocator.create(Node);
|
||||
node.* = Node{ .Stmt = stmt };
|
||||
|
@ -411,17 +409,20 @@ pub const Parser = struct {
|
|||
errdefer consts.deinit();
|
||||
|
||||
_ = try self.consumeSingle(.Const);
|
||||
|
||||
_ = try self.consumeSingle(.LeftParen);
|
||||
|
||||
while (self.peek().ttype != .RightParen) {
|
||||
const const_name = try self.consumeSingle(.Identifier);
|
||||
_ = try self.consumeSingle(.Equal);
|
||||
|
||||
// const declarations dont have type, a future type system must
|
||||
// check the output type of the expression and assign it to the
|
||||
// const later on.
|
||||
|
||||
var expr = try self.parseExpr();
|
||||
try consts.append(ast.SingleConst{
|
||||
.name = const_name,
|
||||
.expr = expr.Expr,
|
||||
.expr = expr,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -463,31 +464,27 @@ pub const Parser = struct {
|
|||
.Struct => try self.parseStructDecl(),
|
||||
|
||||
else => |ttype| blk: {
|
||||
self.doError("(basic) expected fn/const, got {}\n", ttype);
|
||||
self.doError("expected Fn, Const, Struct, got {}\n", ttype);
|
||||
return Result.CompileError;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn parseBlock(self: *@This()) !*Node {
|
||||
var stmts = ast.StmtList.init(self.allocator);
|
||||
fn parseBlockInternal(self: *@This(), comptime T: type) !T {
|
||||
var stmts = T.init(self.allocator);
|
||||
errdefer stmts.deinit();
|
||||
|
||||
_ = try self.consumeSingle(.LeftBrace);
|
||||
|
||||
while (self.peek().ttype != .RightBrace) {
|
||||
var stmt = try self.parseDecl();
|
||||
var stmt = try self.parseStmt();
|
||||
printer.printNode(try self.mkStmt(stmt), 0);
|
||||
try stmts.append(stmt);
|
||||
}
|
||||
|
||||
_ = try self.consumeSingle(.RightBrace);
|
||||
|
||||
return try self.mkBlock(stmts);
|
||||
}
|
||||
|
||||
fn parseDecl(self: *@This()) !*Stmt {
|
||||
return try self.parseStmt();
|
||||
return stmts;
|
||||
}
|
||||
|
||||
fn parseStmt(self: *@This()) anyerror!*Stmt {
|
||||
|
@ -496,33 +493,25 @@ pub const Parser = struct {
|
|||
.Loop => try self.parseLoop(),
|
||||
.Println => try self.parsePrintln(),
|
||||
.Return => try self.parseReturn(),
|
||||
|
||||
// TODO make newlines tokens and consume newline?
|
||||
else => try self.parseStmtExpr(),
|
||||
};
|
||||
}
|
||||
|
||||
/// Copy of parseBlock for blocks in statements
|
||||
/// Parse a list of statements.
|
||||
fn parseBlock(self: *@This()) !*Node {
|
||||
var stmts = try self.parseBlockInternal(ast.StmtList);
|
||||
return try self.mkBlock(stmts);
|
||||
}
|
||||
|
||||
/// parse blocks inside statements
|
||||
fn parseStmtBlock(self: *@This()) !ast.Block {
|
||||
var block = ast.Block.init(self.allocator);
|
||||
errdefer block.deinit();
|
||||
|
||||
_ = try self.consumeSingle(.LeftBrace);
|
||||
|
||||
while (self.peek().ttype != .RightBrace) {
|
||||
var stmt = try self.parseDecl();
|
||||
printer.printNode(try self.mkStmt(stmt), 0);
|
||||
try block.append(stmt);
|
||||
}
|
||||
|
||||
_ = try self.consumeSingle(.RightBrace);
|
||||
|
||||
var block = try self.parseBlockInternal(ast.Block);
|
||||
return block;
|
||||
}
|
||||
|
||||
fn parseIfStmt(self: *@This()) !*Stmt {
|
||||
_ = try self.consumeSingle(.If);
|
||||
var condition = (try self.parseExpr()).Expr;
|
||||
var condition = try self.parseExpr();
|
||||
|
||||
const then_branch = try self.parseStmtBlock();
|
||||
|
||||
|
@ -546,11 +535,11 @@ pub const Parser = struct {
|
|||
var expr: ?*Expr = null;
|
||||
var body: ast.Block = undefined;
|
||||
|
||||
// infinite loop
|
||||
// 'loop {' = infinite loop
|
||||
if (self.check(.LeftBrace)) {
|
||||
body = try self.parseStmtBlock();
|
||||
} else {
|
||||
expr = (try self.parseExpr()).Expr;
|
||||
expr = try self.parseExpr();
|
||||
body = try self.parseStmtBlock();
|
||||
}
|
||||
|
||||
|
@ -559,7 +548,7 @@ pub const Parser = struct {
|
|||
|
||||
fn parseReturn(self: *@This()) !*Stmt {
|
||||
const tok = try self.consumeSingle(.Return);
|
||||
const expr = (try self.parseExpr()).Expr;
|
||||
const expr = try self.parseExpr();
|
||||
return try Stmt.mkReturn(self.allocator, tok, expr);
|
||||
}
|
||||
|
||||
|
@ -567,24 +556,23 @@ pub const Parser = struct {
|
|||
_ = try self.consumeSingle(.Println);
|
||||
|
||||
_ = try self.consumeSingle(.LeftParen);
|
||||
var expr = (try self.parseExpr()).Expr;
|
||||
var expr = try self.parseExpr();
|
||||
_ = try self.consumeSingle(.RightParen);
|
||||
|
||||
return try Stmt.mkPrintln(self.allocator, expr);
|
||||
}
|
||||
|
||||
fn parseStmtExpr(self: *@This()) !*Stmt {
|
||||
var expr = (try self.parseExpr()).Expr;
|
||||
var expr = try self.parseExpr();
|
||||
return try self.mkStmtExpr(expr);
|
||||
}
|
||||
|
||||
fn parseExpr(self: *@This()) anyerror!*Node {
|
||||
var expr: *Expr = try self.parseAssignment();
|
||||
return self.mkExpr(expr);
|
||||
fn parseExpr(self: *@This()) anyerror!*Expr {
|
||||
return try self.parseAssignment();
|
||||
}
|
||||
|
||||
fn parseAssignment(self: *@This()) anyerror!*Expr {
|
||||
// there can be two types coming out of this function:
|
||||
// there can be two assignments coming out of this function:
|
||||
// - a mutable/immutable variable declaration with :=
|
||||
// - an assignment to a variable with =
|
||||
|
||||
|
@ -592,53 +580,50 @@ pub const Parser = struct {
|
|||
// of this is an Expr, we wrap variable assignments in an Expr as well.
|
||||
var mutable: bool = false;
|
||||
|
||||
std.debug.warn("start assignment pass with cur={}\n", self.peek());
|
||||
|
||||
if (self.check(.Mut)) {
|
||||
_ = try self.consumeSingle(.Mut);
|
||||
mutable = true;
|
||||
}
|
||||
|
||||
var expr = try self.parseOr();
|
||||
std.debug.warn("lvalue: {}, cur: {}\n", expr, self.peek());
|
||||
|
||||
var value: *Expr = undefined;
|
||||
|
||||
var op: Token = undefined;
|
||||
|
||||
if (self.check(.ColonEqual) or self.check(.Equal)) {
|
||||
op = self.peek();
|
||||
_ = try self.nextToken();
|
||||
value = try self.parseAssignment();
|
||||
|
||||
switch (expr.*) {
|
||||
.Variable => {
|
||||
switch (op.ttype) {
|
||||
.ColonEqual => return try self.mkVarDecl(expr.Variable, value, mutable),
|
||||
.Equal => return try self.mkAssign(expr.Variable, value),
|
||||
else => unreachable,
|
||||
}
|
||||
},
|
||||
|
||||
.Get => |get| {
|
||||
if (op.ttype == .ColonEqual) {
|
||||
self.doError("can not initialize struct field");
|
||||
return Result.CompileError;
|
||||
}
|
||||
|
||||
return try self.mkSet(get.struc, get.name, value);
|
||||
},
|
||||
|
||||
else => |expr_typ| {
|
||||
self.doError("Invalid assignment target {}", expr_typ);
|
||||
return Result.CompileError;
|
||||
},
|
||||
}
|
||||
return try self.finishAssignment(expr, mutable);
|
||||
}
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
fn finishAssignment(self: *@This(), expr: *Expr, mutable: bool) !*Expr {
|
||||
var op = self.peek();
|
||||
_ = try self.nextToken();
|
||||
var value = try self.parseAssignment();
|
||||
|
||||
switch (expr.*) {
|
||||
.Variable => {
|
||||
switch (op.ttype) {
|
||||
.ColonEqual => return try self.mkVarDecl(expr.Variable, value, mutable),
|
||||
.Equal => return try self.mkAssign(expr.Variable, value),
|
||||
else => unreachable,
|
||||
}
|
||||
},
|
||||
|
||||
.Get => |get| {
|
||||
if (op.ttype == .ColonEqual) {
|
||||
self.doError("can not initialize struct field");
|
||||
return Result.CompileError;
|
||||
}
|
||||
|
||||
return try self.mkSet(get.struc, get.name, value);
|
||||
},
|
||||
|
||||
else => |expr_typ| {
|
||||
self.doError("Invalid assignment target {}", expr_typ);
|
||||
return Result.CompileError;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn parseOr(self: *@This()) !*Expr {
|
||||
var expr = try self.parseAnd();
|
||||
|
||||
|
@ -744,18 +729,14 @@ pub const Parser = struct {
|
|||
return expr;
|
||||
}
|
||||
|
||||
/// Parse either:
|
||||
/// - A function call
|
||||
/// - A struct initialization (Point.{...})
|
||||
/// - A struct Get expression (p.x)
|
||||
fn parseCall(self: *@This()) !*Expr {
|
||||
// we parse a primary expression instead of consuming a .Identifier
|
||||
// since parseCall is connected to the rest of the parser. doing
|
||||
// identifiers would break the rest of the rules that want primaries.
|
||||
|
||||
// nothing stops us from ensuring expr is a Variable though ;)
|
||||
var expr = try self.parsePrimary();
|
||||
|
||||
while (true) {
|
||||
std.debug.warn("maybe fncall / struct: {}\n", self.peek().ttype);
|
||||
printer.printExpr(expr);
|
||||
|
||||
if (self.check(.LeftParen)) {
|
||||
_ = try self.consumeSingle(.LeftParen);
|
||||
expr = try self.finishCall(expr);
|
||||
|
@ -766,7 +747,11 @@ pub const Parser = struct {
|
|||
_ = try self.consumeSingle(.LeftBrace);
|
||||
expr = try self.finishStructVal(expr);
|
||||
} else {
|
||||
var name = try self.consume(.Identifier, "Expect property name after '.'");
|
||||
var name = try self.consume(
|
||||
.Identifier,
|
||||
"Expect property name after '.'",
|
||||
);
|
||||
|
||||
expr = try self.mkGet(expr, name);
|
||||
}
|
||||
} else {
|
||||
|
@ -784,18 +769,17 @@ pub const Parser = struct {
|
|||
if (!self.check(.RightParen)) {
|
||||
|
||||
// emulating do-while really badly
|
||||
var arg = (try self.parseExpr()).Expr;
|
||||
var arg = try self.parseExpr();
|
||||
try args.append(arg);
|
||||
|
||||
while (self.check(.Comma)) {
|
||||
_ = try self.consumeSingle(.Comma);
|
||||
|
||||
arg = (try self.parseExpr()).Expr;
|
||||
arg = try self.parseExpr();
|
||||
try args.append(arg);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO shouldnt consume() return the current token, not nextToken?
|
||||
var paren = try self.consume(.RightParen, "Expected ')' after arguments");
|
||||
|
||||
return self.mkCall(callee, paren, args);
|
||||
|
@ -817,7 +801,7 @@ pub const Parser = struct {
|
|||
// TODO check .Comma for the quick initialization {val,val,val}
|
||||
|
||||
_ = try self.consumeSingle(.Colon);
|
||||
const field_value = (try self.parseExpr()).Expr;
|
||||
const field_value = try self.parseExpr();
|
||||
|
||||
try inits.append(ast.StructInit{
|
||||
.field = field_name,
|
||||
|
@ -845,7 +829,7 @@ pub const Parser = struct {
|
|||
|
||||
.LeftParen => blk: {
|
||||
_ = try self.nextToken();
|
||||
var expr = (try self.parseExpr()).Expr;
|
||||
var expr = try self.parseExpr();
|
||||
_ = try self.consume(.RightParen, "Expected ')' after expression");
|
||||
|
||||
// for groupings, we don't want to skip tokens as we already
|
||||
|
@ -860,7 +844,6 @@ pub const Parser = struct {
|
|||
};
|
||||
|
||||
_ = try self.nextToken();
|
||||
|
||||
return expr;
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue