From 2c6b6dd251b79dd35ebcec9831e24329f4930443 Mon Sep 17 00:00:00 2001 From: Deepak kudi Date: Thu, 4 Jun 2026 11:25:12 +0530 Subject: [PATCH] fix: allow numeric output filenames --- parser/parser.go | 28 ++++++++++++++++++++++------ parser/parser_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/parser/parser.go b/parser/parser.go index 39a4dcb4..18d3a09e 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -417,26 +417,42 @@ func (p *Parser) parseKeypress(ct token.Type) Command { func (p *Parser) parseOutput() Command { cmd := Command{Type: token.OUTPUT} - if p.peek.Type != token.STRING { + if p.peek.Type != token.STRING && p.peek.Type != token.NUMBER { p.errors = append(p.errors, NewError(p.cur, "Expected file path after output")) return cmd } - ext := filepath.Ext(p.peek.Literal) + path := p.parseOutputPath() + ext := filepath.Ext(path) if ext != "" { cmd.Options = ext } else { cmd.Options = ".png" - if !strings.HasSuffix(p.peek.Literal, "/") { - p.errors = append(p.errors, NewError(p.peek, "Expected folder with trailing slash")) + if !strings.HasSuffix(path, "/") { + p.errors = append(p.errors, NewError(p.cur, "Expected folder with trailing slash")) } } - cmd.Args = p.peek.Literal - p.nextToken() + cmd.Args = path return cmd } +func (p *Parser) parseOutputPath() string { + p.nextToken() + path := p.cur.Literal + + for (p.peek.Type == token.STRING || p.peek.Type == token.NUMBER) && isAdjacentToken(p.cur, p.peek) { + p.nextToken() + path += p.cur.Literal + } + + return path +} + +func isAdjacentToken(left, right token.Token) bool { + return left.Line == right.Line && left.Column+len(left.Literal) == right.Column +} + // parseSet parses a set command. // A set command takes a setting name and a value. // diff --git a/parser/parser_test.go b/parser/parser_test.go index fd3a4467..d933df3a 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -117,6 +117,31 @@ Sleep Bar` } } +func TestParseOutputNumericFilename(t *testing.T) { + l := lexer.New("Output 1.gif") + p := New(l) + + cmds := p.Parse() + + if len(p.errors) != 0 { + t.Fatalf("Expected no errors, got %v", p.errors) + } + if len(cmds) != 1 { + t.Fatalf("Expected 1 command, got %d", len(cmds)) + } + + cmd := cmds[0] + if cmd.Type != token.OUTPUT { + t.Fatalf("Expected command type %s, got %s", token.OUTPUT, cmd.Type) + } + if cmd.Options != ".gif" { + t.Fatalf("Expected output extension %q, got %q", ".gif", cmd.Options) + } + if cmd.Args != "1.gif" { + t.Fatalf("Expected output path %q, got %q", "1.gif", cmd.Args) + } +} + func TestParseTapeFile(t *testing.T) { input, err := os.ReadFile("../examples/fixtures/all.tape") if err != nil {