const std = @import("std"); const plugin = @import("plugin.zig"); pub const ParseError = error{ NoCommandGiven, UnknownCommand, ArgRequired, }; pub const CommandType = enum { Noop, Load, Quicksave, RunQS, Amp, RFlanger, Eq, Phaser, Mbeq, Chorus, PitchScaler, Reverb, Highpass, Delay, Vinyl, RevDelay, Noise, WildNoise, Write, Rotate, }; pub const Command = struct { command: CommandType, args: ArgList, cur_idx: usize = 0, pub fn print(self: *const Command) void { std.debug.warn("cmd:{}\n", self.command); } pub fn argAt(self: *const Command, idx: usize) ![]const u8 { const args = self.args.toSliceConst(); if (idx > (args.len - 1)) { std.debug.warn("Expected argument at index {}\n", idx); return ParseError.ArgRequired; } return args[idx]; } pub fn usizeArgAt(self: *const Command, idx: usize) !usize { var arg = try self.argAt(idx); return try std.fmt.parseInt(usize, arg, 10); } pub fn consumePosition(self: *Command) !plugin.Position { self.cur_idx = 2; return plugin.Position{ .split = try self.usizeArgAt(0), .index = try self.usizeArgAt(1), }; } pub fn intArgAt(self: *const Command, idx: usize) !i32 { var arg = try self.argAt(idx); return try std.fmt.parseInt(i32, arg, 10); } pub fn floatArgAt(self: *const Command, idx: usize) !f32 { var arg = try self.argAt(idx); return try std.fmt.parseFloat(f32, arg); } pub fn floatArgMany( self: *const Command, allocator: *std.mem.Allocator, start_index: usize, elements: usize, default: f32, ) ![]const f32 { var i: usize = start_index; var arr = std.ArrayList(f32).init(allocator); while (i < elements) : (i += 1) { var value: f32 = self.floatArgAt(i) catch |err| blk: { std.debug.warn("\tdoing default on arg {}\n", i); break :blk default; }; try arr.append(value); } return arr.toSliceConst(); } pub fn appendParam( self: *Command, params: *plugin.ParamList, symbol: []const u8, ) !void { var val = try self.floatArgAt(self.cur_idx); self.cur_idx += 1; try params.append(plugin.Param{ .sym = symbol, .value = val, }); } pub fn appendParamMap( self: *Command, map: *plugin.ParamMap, symbol: []const u8, ) !void { var val = try self.floatArgAt(self.cur_idx); self.cur_idx += 1; _ = try map.put(symbol, val); } }; pub const CommandList = std.ArrayList(*Command); pub const ArgList = std.ArrayList([]const u8); pub const KeywordMap = std.AutoHashMap([]const u8, CommandType); pub const Lang = struct { allocator: *std.mem.Allocator, keywords: KeywordMap, pub fn init(allocator: *std.mem.Allocator) Lang { return Lang{ .allocator = allocator, .keywords = KeywordMap.init(allocator), }; } pub fn deinit(self: *Lang) void { self.keywords.deinit(); } fn fillKeywords(self: *Lang) !void { _ = try self.keywords.put("noop", .Noop); _ = try self.keywords.put("load", .Load); _ = try self.keywords.put("quicksave", .Quicksave); _ = try self.keywords.put("runqs", .RunQS); _ = try self.keywords.put("amp", .Amp); _ = try self.keywords.put("rflanger", .RFlanger); _ = try self.keywords.put("eq", .Eq); _ = try self.keywords.put("mbeq", .Mbeq); _ = try self.keywords.put("phaser", .Phaser); _ = try self.keywords.put("chorus", .Chorus); _ = try self.keywords.put("pitchscaler", .PitchScaler); _ = try self.keywords.put("reverb", .Reverb); _ = try self.keywords.put("highpass", .Highpass); _ = try self.keywords.put("delay", .Delay); _ = try self.keywords.put("vinyl", .Vinyl); _ = try self.keywords.put("revdelay", .RevDelay); // custom implementations (not lv2) _ = try self.keywords.put("noise", .Noise); _ = try self.keywords.put("wildnoise", .WildNoise); _ = try self.keywords.put("write", .Write); // even more custom _ = try self.keywords.put("rotate", .Rotate); } // TODO remove this once AutoHashMap is fixed. pub fn getCommand(self: *Lang, stmt: []const u8) ?CommandType { const commands = [_][]const u8{ "noop", "load", "quicksave", "runqs", "amp", "rflanger", "eq", "phaser", "mbeq", "chorus", "pitchscaler", "reverb", "highpass", "delay", "vinyl", "revdelay", "noise", "wildnoise", "write", "rotate", }; const command_types = [_]CommandType{ .Noop, .Load, .Quicksave, .RunQS, .Amp, .RFlanger, .Eq, .Phaser, .Mbeq, .Chorus, .PitchScaler, .Reverb, .Highpass, .Delay, .Vinyl, .RevDelay, .Noise, .WildNoise, .Write, .Rotate, }; std.debug.assert(commands.len == command_types.len); for (commands) |command, idx| { if (std.mem.eql(u8, stmt, command)) return command_types[idx]; } return null; } pub fn parse(self: *Lang, data: []const u8) !CommandList { var splitted_it = std.mem.separate(data, ";"); try self.fillKeywords(); var cmds = CommandList.init(self.allocator); while (splitted_it.next()) |stmt_orig| { var stmt = std.mem.trimRight(u8, stmt_orig, "\n"); stmt = std.mem.trimLeft(u8, stmt, "\n"); if (stmt.len == 0) continue; if (stmt[0] == '#') continue; // TODO better tokenizer instead of just tokenize(" "); var tok_it = std.mem.tokenize(stmt, " "); var cmd_opt = tok_it.next(); if (cmd_opt == null) return ParseError.NoCommandGiven; var command = cmd_opt.?; var ctype_opt = self.getCommand(command); var ctype: CommandType = undefined; if (ctype_opt) |ctype_val| { ctype = ctype_val; } else { std.debug.warn("Unknown command: '{}' ({})\n", command, command.len); return ParseError.UnknownCommand; } var args = ArgList.init(self.allocator); while (tok_it.next()) |arg| { try args.append(arg); } // construct final Command based on command var cmd_ptr = try self.allocator.create(Command); cmd_ptr.* = Command{ .command = ctype, .args = args }; try cmds.append(cmd_ptr); } return cmds; } }; test "noop" { var lang = Lang.init(std.heap.direct_allocator); defer lang.deinit(); var cmds = try lang.parse("noop;"); defer cmds.deinit(); std.testing.expectEqual(cmds.len, 1); std.testing.expectEqual(cmds.at(0).command, .Noop); } test "load, phaser, quicksave" { var lang = Lang.init(std.heap.direct_allocator); defer lang.deinit(); const prog = \\load :0; \\phaser 3 1 25 0.25 0 1; \\quicksave; ; var cmds = try lang.parse(prog); defer cmds.deinit(); std.testing.expectEqual(cmds.len, 3); std.testing.expectEqual(cmds.at(0).command, .Load); std.testing.expectEqual(cmds.at(1).command, .Phaser); std.testing.expectEqual(cmds.at(2).command, .Quicksave); }