const std = @import("std"); const plugin = @import("plugin.zig"); pub const ParseError = error{ NoCommandGiven, UnknownCommand, ArgRequired, }; pub const CommandType = enum { Noop, Load, Quicksave, Amp, RFlanger, Eq, Phaser, Mbeq, Chorus, PitchScaler, Reverb, Highpass, }; pub const Command = struct { command: CommandType, args: ArgList, 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: *const Command) !plugin.Position { 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: *const Command, params: *plugin.ParamList, symbol: []const u8, idx: usize, ) !void { var val = try self.floatArgAt(idx); try params.append(plugin.Param{ .sym = symbol, .value = val, }); } }; pub const CommandList = std.ArrayList(*Command); pub const ArgList = std.ArrayList([]const u8); pub const PluginKeywords = [_]CommandType{ .Amp, .RFlanger }; 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), }; } 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("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); } 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 kv_opt = self.keywords.get(command); var ctype: CommandType = undefined; if (kv_opt) |kv| { ctype = kv.value; } else { std.debug.warn("Unknown command: '{}'\n", command); 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; } }; // TODO tests