Skip to content

Instantly share code, notes, and snippets.

@Tetralux
Last active March 11, 2023 10:37
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Tetralux/51be6106bc915728ced10504d6fec7a8 to your computer and use it in GitHub Desktop.
Save Tetralux/51be6106bc915728ced10504d6fec7a8 to your computer and use it in GitHub Desktop.
A simple example of a calculator programming language that compiles to native code!
//
// A simple example of a calculator programming language, that compiles to native code!
//
// Written by Tetralux <tetraluxonpc@gmail.com>, 2023-03-09.
//
// Programs are a string that you pass as an argument in the form of a mathmetical expression.
// e.g: '10 + 4 - 1 + 7'.
// This program will generate some Zig code that computes the answer, and prints it to stdout.
// It then will invoke the Zig compiler as a subprocess to compile and run this program.
//
const std = @import("std");
const TokenKind = enum {
none,
plus,
minus,
number,
};
const Token = struct {
kind: TokenKind,
str: []const u8,
location: Loc,
};
const Loc = struct {
line: u32,
column: u32,
length: u16,
};
const Lexer = struct {
buf: []const u8,
current_location: Loc = .{ .line = 1, .column = 1, .length = 1 },
fn eat(lex: *Lexer) !Token {
lex.skip_whitespace();
if (lex.buf.len == 0) return error.EOF;
switch (lex.buf[0]) {
'0'...'9' => return lex.eat_number(),
'+' => return lex.eat_chars_as_token(1, .plus),
'-' => return lex.eat_chars_as_token(1, .minus),
else => return error.UnexpectedByte,
}
}
fn eat_chars_as_token(lex: *Lexer, num_bytes: usize, kind: TokenKind) !Token {
if (lex.buf.len < num_bytes) return error.EOF;
var loc = lex.current_location;
const str = lex.buf[0..num_bytes];
loc.length = @intCast(u16, num_bytes);
lex.buf = lex.buf[str.len..];
lex.current_location.column += @intCast(u32, str.len);
return Token {
.kind = kind,
.str = str,
.location = loc,
};
}
fn eat_number(lex: *Lexer) !Token {
var loc = lex.current_location;
var i: usize = 0;
while (i < lex.buf.len) : (i += 1) {
switch (lex.buf[i]) {
'0'...'9' => continue,
else => break,
}
}
if (i == 0) return error.BadNumber;
const str = lex.buf[0..i];
loc.length = @intCast(u16, str.len);
lex.buf = lex.buf[i..];
lex.current_location.column += @intCast(u32, str.len);
return Token {
.kind = .number,
.str = str,
.location = loc,
};
}
fn skip_whitespace(lex: *Lexer) void {
if (lex.buf.len == 0) return;
var i: usize = 0;
while (i < lex.buf.len) : (i += 1) {
switch (lex.buf.ptr[i]) {
' ', '\t' => lex.current_location.column += 1,
'\n' => {
lex.current_location.line += 1;
lex.current_location.column = 1;
},
else => break,
}
}
lex.buf = lex.buf[i..];
}
fn expect(lex: *Lexer, kind: TokenKind) !Token {
const token = try lex.eat();
if (token.kind == kind) return token;
return error.UnexpectedToken;
}
};
pub fn main() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
const ally = arena.allocator();
const args = try std.process.argsAlloc(ally);
if (args.len == 1) {
std.debug.print("error: no program text specified.\n", .{});
std.debug.print("e.g: {s} 10 + 4 - 1 + 7\n", .{ args[0] });
std.os.exit(1);
}
const input = try std.mem.join(ally, " ", args[1..]);
var out = try std.ArrayList(u8).initCapacity(ally, 4096);
var err_loc: Loc = undefined;
const program = program_from_string(&out, input, &err_loc) catch |err| {
const w = std.io.getStdErr().writer();
try w.print("syntax error: {s}\n", .{ @errorName(err) });
try show_context(w, input, err_loc);
std.os.exit(2);
};
// std.debug.print("{s}\n", .{ program });
try std.fs.cwd().writeFile("tiny_output.zig", program);
// defer std.fs.cwd().deleteFile("tiny_output.zig") catch {};
var exe = std.ChildProcess.init(&.{"zig", "run", "tiny_output.zig"}, ally);
_ = try exe.spawnAndWait();
}
fn program_from_string(out: *std.ArrayList(u8), input: []const u8, err_loc: *Loc) ![]const u8 {
try out.appendSlice(
\\const std = @import("std");
\\
\\pub fn main() !void {
\\ var result: i64 = 0;
\\
);
var lex = Lexer {
.buf = input,
};
errdefer err_loc.* = lex.current_location;
const initial = try lex.expect(.number);
try out.writer().print(" result = {s};\n", .{ initial.str });
while (true) {
const operator = lex.eat() catch |e| switch (e) {
error.EOF => break,
else => return e,
};
const value = try lex.expect(.number);
try out.writer().print(" result = result {s} {s};\n", .{ operator.str, value.str });
}
try out.appendSlice(
\\
\\ std.io.getStdOut().writer().print("{}\n", .{ result }) catch |err| {
\\ std.debug.print("error: unable to write to stdout: {s}\n", .{ @errorName(err) });
\\ };
\\
);
try out.appendSlice("}");
const zig_string = try out.toOwnedSlice();
return zig_string;
}
fn show_context(w: anytype, input: []const u8, err_loc: Loc) !void {
try w.writeAll(" | ");
try w.writeAll(input);
try w.writeAll("\n |");
for (0..err_loc.column) |_| try w.writeAll("-");
try w.writeAll("^");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment