diff --git a/src/lang.zig b/src/lang.zig index 88dcda1..3b65c9d 100644 --- a/src/lang.zig +++ b/src/lang.zig @@ -58,27 +58,93 @@ pub const Type = enum { }; pub const NewCommand = struct { - pub const Noop = struct {}; + tag: Tag, + + pub const Tag = enum { + noop, + load, + quicksave, + runqs, + + amp, + rflanger, + eq, + phaser, + mbeq, + chorus, + pitchscaler, + reverb, + highpass, + delay, + vinyl, + revdelay, + gate, + detune, + overdrive, + degrade, + repsycho, + talkbox, + dyncomp, + thruzero, + foverdrive, + gverb, + invert, + tapedelay, + moddelay, + multichorus, + saturator, + vintagedelay, + + noise, + wildnoise, + write, + embed, + + rotate, + }; + + pub fn cast(base: *@This(), comptime T: type) ?*T { + if (base.tag != T.base_tag) + return null; + + return @fieldParentPtr(T, "base", base); + } + + pub const Noop = struct { + pub const base_tag = Tag.noop; + base: NewCommand, + }; pub const Load = struct { + pub const base_tag = Tag.load; + base: NewCommand, path: []const u8, }; - pub const Quicksave = struct {}; + pub const Quicksave = struct { + pub const base_tag = Tag.quicksave; + base: NewCommand, + }; pub const RunQS = struct { + pub const base_tag = Tag.runqs; program: []const u8, + base: NewCommand, }; pub const Amp = struct { + pub const base_tag = Tag.amp; pub const base_type = Type.lv2_command; - gain: f64 + base: NewCommand, + gain: f32 }; pub const RFlanger = struct { + pub const base_tag = Tag.rflanger; pub const base_type = Type.lv2_command; - delay_depth_avg: f64, - law_freq: f64, + base: NewCommand, + delay_depth_avg: f32, + law_freq: f32, }; }; @@ -183,7 +249,7 @@ pub const Command = struct { } }; -pub const CommandList = std.ArrayList(Command); +pub const CommandList = std.ArrayList(NewCommand); pub const ArgList = std.ArrayList([]const u8); pub const KeywordMap = std.StringHashMap(CommandType); @@ -214,6 +280,14 @@ pub const Lang = struct { fn fillKeywords(self: *Lang) !void { inline for (@typeInfo(NewCommand).Struct.decls) |cmd_struct_decl| { + switch (cmd_struct_decl.data) { + .Type => |typ| switch (@typeInfo(typ)) { + .Struct => {}, + else => continue, + }, + else => continue, + } + const struct_name = cmd_struct_decl.name; comptime var lowered_command_name = [_]u8{0} ** struct_name.len; @@ -297,9 +371,42 @@ pub const Lang = struct { self.has_error = true; } - pub fn parse(self: *Lang, data: []const u8) ParseError!CommandList { + fn parseCommandArguments( + self: *@This(), + comptime command_struct: type, + tok_it: *std.mem.TokenIterator, + commands: *CommandList, + ) !void { + // Based on the command struct fields, we can parse the arguments. + var cmd = try self.allocator.create(command_struct); + + inline for (@typeInfo(command_struct).Struct.fields) |cmd_field| { + comptime { + if (std.mem.eql(u8, cmd_field.name, "base")) { + continue; + } + } + + // TODO: crash when no arguments are left but we still need + // arguments... + const arg = tok_it.next().?; + const argument_value = switch (cmd_field.field_type) { + usize => try std.fmt.parseInt(usize, arg, 10), + i32 => try std.fmt.parseInt(i32, arg, 10), + f32 => try std.fmt.parseFloat(f32, arg), + []const u8 => arg, + else => @panic("Invalid parameter type (" ++ @typeName(cmd_field.field_type) ++ ") left on command struct " ++ @typeName(command_struct) ++ "."), + }; + + @field(cmd, cmd_field.name) = argument_value; + } + + try commands.append(cmd.base); + } + + pub fn parse(self: *Lang, data: []const u8) !CommandList { var splitted_it = std.mem.split(data, ";"); - try self.fillKeywords(); + // try self.fillKeywords(); var cmds = CommandList.init(self.allocator); while (splitted_it.next()) |stmt_orig| { @@ -310,7 +417,7 @@ pub const Lang = struct { if (stmt.len == 0) continue; if (std.mem.startsWith(u8, stmt, "#")) continue; - // TODO better tokenizer instead of just tokenize(" "); + // TODO better tokenizer instead of just tokenize(" ")...maybe???? var tok_it = std.mem.tokenize(stmt, " "); var cmd_opt = tok_it.next(); @@ -318,32 +425,47 @@ pub const Lang = struct { self.doError("No command given", .{}); continue; } - var command = cmd_opt.?; + const command_string = cmd_opt.?; - var ctype_opt = self.getCommand(command); - var ctype: CommandType = undefined; - if (ctype_opt) |ctype_val| { - ctype = ctype_val; - } else { - self.doError("Unknown command '{}' ({})", .{ command, command.len }); + var found: bool = false; + + inline for (@typeInfo(NewCommand).Struct.decls) |cmd_struct_decl| { + switch (cmd_struct_decl.data) { + .Type => |typ| switch (@typeInfo(typ)) { + .Struct => {}, + else => continue, + }, + else => continue, + } + + const struct_name = cmd_struct_decl.name; + comptime var lowered_command_name = [_]u8{0} ** struct_name.len; + comptime { + for (struct_name) |c, i| { + lowered_command_name[i] = std.ascii.toLower(c); + } + } + + // if we have a match, we know the proper struct type + // to use. this actually works compared to storing command_struct + // in a variable because then that variable must be comptime. + + // the drawback of this approach is that our emitted code is basically linear + // because we don't use the hashmap anymore. maybe #5359 can help. + + if (std.mem.eql(u8, &lowered_command_name, command_string)) { + found = true; + const cmd_struct_type = cmd_struct_decl.data.Type; + try self.parseCommandArguments(cmd_struct_type, &tok_it, &cmds); + } + } + + if (!found) { + self.doError("Unknown command '{}' ({})", .{ command_string, command_string.len }); continue; } - var args = ArgList.init(self.allocator); - errdefer args.deinit(); - - while (tok_it.next()) |arg| { - try args.append(arg); - } - - // construct final Command based on command - var cmd = Command{ .command = ctype, .args = args }; - self.validateCommand(cmd) catch |err| { - //self.doError("error validating command '{}': {}", command, err); - continue; - }; - - try cmds.append(cmd); + // try cmds.append(cmd); } if (self.has_error) return ParseError.ParseFail; diff --git a/src/main.zig b/src/main.zig index 04bcf2d..9971f24 100644 --- a/src/main.zig +++ b/src/main.zig @@ -185,7 +185,8 @@ pub fn main() !void { const scri_path = try (args_it.next(allocator) orelse @panic("expected scri path or 'repl'")); if (std.mem.eql(u8, scri_path, "repl")) { - return try doRepl(allocator, &args_it); + @panic("TODO bring repl back"); + // return try doRepl(allocator, &args_it); } var file = try std.fs.cwd().openFile(scri_path, .{}); diff --git a/src/runner.zig b/src/runner.zig index 6276954..bde8eb9 100644 --- a/src/runner.zig +++ b/src/runner.zig @@ -374,6 +374,8 @@ pub const Runner = struct { try image.runPlugin("http://calf.sourceforge.net/plugins/VintageDelay", pos, params); } + fn newRunCommand(self: *@This(), cmd: lang.NewCommand) !void {} + fn runCommand(self: *Runner, cmd: *lang.Command) !void { var params = ParamList.init(self.allocator); defer params.deinit(); @@ -775,14 +777,9 @@ pub const Runner = struct { cmds: lang.CommandList, debug_flag: bool, ) !void { - for (cmds.items) |const_cmd| { - if (debug_flag) const_cmd.print(); - - // copy the command so we own its memory - var cmd = try const_cmd.copy(self.allocator); - defer self.allocator.destroy(cmd); - - try self.runCommand(cmd); + for (cmds.items) |cmd| { + // if (debug_flag) const_cmd.print(); + try self.newRunCommand(cmd); } } };