package scanner import ( "golox/errors" "golox/token" "testing" ) func TestScanTokens(t *testing.T) { tests := []struct { name string source string tokens []token.TokenType }{ { name: "Single character tokens", source: "(){}.,-+;*", tokens: []token.TokenType{ token.LEFT_PAREN, token.RIGHT_PAREN, token.LEFT_BRACE, token.RIGHT_BRACE, token.DOT, token.COMMA, token.MINUS, token.PLUS, token.SEMICOLON, token.STAR, }, }, { name: "Operators", source: "! != = == < <= > >=", tokens: []token.TokenType{ token.BANG, token.BANG_EQUAL, token.EQUAL, token.EQUAL_EQUAL, token.LESS, token.LESS_EQUAL, token.GREATER, token.GREATER_EQUAL, }, }, { name: "Comments", source: "// this is a comment\n+", tokens: []token.TokenType{ token.PLUS, }, }, { name: "Whitespace", source: " \r\t\n", tokens: []token.TokenType{}, }, { name: "String literals", source: `"hello world"`, tokens: []token.TokenType{ token.STRING, }, }, { name: "Number literals", source: "123 45.67", tokens: []token.TokenType{ token.NUMBER, token.NUMBER, }, }, { name: "Identifiers and keywords", source: "and class else false for fun if nil or print return super this true var while", tokens: []token.TokenType{ token.AND, token.CLASS, token.ELSE, token.FALSE, token.FOR, token.FUN, token.IF, token.NIL, token.OR, token.PRINT, token.RETURN, token.SUPER, token.THIS, token.TRUE, token.VAR, token.WHILE, }, }, { name: "Unterminated string", source: `"unterminated string`, tokens: []token.TokenType{}, }, { name: "Unexpected character", source: "@", tokens: []token.TokenType{}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { scanner := New(tt.source, errors.NewMockErrorLogger()) tokens := scanner.ScanTokens() if len(tokens) != len(tt.tokens)+1 { // +1 for EOF token t.Fatalf("expected %d tokens, got %d", len(tt.tokens)+1, len(tokens)) } for i, tokenType := range tt.tokens { if tokens[i].Type != tokenType { t.Errorf("expected token %v, got %v", tokenType, tokens[i].Type) } } if tokens[len(tokens)-1].Type != token.EOF { t.Errorf("expected EOF token, got %v", tokens[len(tokens)-1].Type) } }) } } func TestIsAtEnd(t *testing.T) { tests := []struct { name string source string expected bool }{ {"Not at end", "abc", false}, {"At end", "", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { scanner := New(tt.source, errors.NewMockErrorLogger()) if got := scanner.isAtEnd(); got != tt.expected { t.Errorf("expected %v, got %v", tt.expected, got) } }) } } func TestMatch(t *testing.T) { tests := []struct { name string source string expected bool char byte }{ {"Match character", "=", true, '='}, {"No match character", "!", false, '='}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { scanner := New(tt.source, errors.NewMockErrorLogger()) if got := scanner.match(tt.char); got != tt.expected { t.Errorf("expected %v, got %v", tt.expected, got) } }) } } func TestPeek(t *testing.T) { tests := []struct { name string source string expected byte }{ {"Peek character", "abc", 'a'}, {"Peek at end", "", '\000'}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { scanner := New(tt.source, errors.NewMockErrorLogger()) if got := scanner.peek(); got != tt.expected { t.Errorf("expected %v, got %v", tt.expected, got) } }) } } func TestPeekNext(t *testing.T) { tests := []struct { name string source string expected byte }{ {"Peek next character", "abc", 'b'}, {"Peek next at end", "a", '\000'}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { scanner := New(tt.source, errors.NewMockErrorLogger()) if got := scanner.peekNext(); got != tt.expected { t.Errorf("expected %v, got %v", tt.expected, got) } }) } } func TestAdvance(t *testing.T) { tests := []struct { name string source string expected byte }{ {"Advance character", "abc", 'a'}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { scanner := New(tt.source, errors.NewMockErrorLogger()) if got := scanner.advance(); got != tt.expected { t.Errorf("expected %v, got %v", tt.expected, got) } }) } } func TestIsAlpha(t *testing.T) { tests := []struct { name string char byte expected bool }{ {"Is alpha", 'a', true}, {"Is not alpha", '1', false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := isAlpha(tt.char); got != tt.expected { t.Errorf("expected %v, got %v", tt.expected, got) } }) } } func TestIsDigit(t *testing.T) { tests := []struct { name string char byte expected bool }{ {"Is digit", '1', true}, {"Is not digit", 'a', false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := isDigit(tt.char); got != tt.expected { t.Errorf("expected %v, got %v", tt.expected, got) } }) } } func TestString(t *testing.T) { tests := []struct { name string source string expected string }{ {"Valid string", `"hello"`, "hello"}, {"Unterminated string", `"hello`, ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { scanner := New(tt.source, errors.NewMockErrorLogger()) scanner.advance() // Move to the first character of the string scanner.string() if tt.expected == "" { if len(scanner.tokens) != 0 { t.Errorf("expected no tokens, got %d", len(scanner.tokens)) } } else { if len(scanner.tokens) != 1 { t.Errorf("expected 1 token, got %d", len(scanner.tokens)) } else if scanner.tokens[0].Literal != tt.expected { t.Errorf("expected %v, got %v", tt.expected, scanner.tokens[0].Literal) } } }) } } func TestNumber(t *testing.T) { tests := []struct { name string source string expected float64 }{ {"Integer number", "123", 123}, {"Floating point number", "45.67", 45.67}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { scanner := New(tt.source, errors.NewMockErrorLogger()) scanner.number() if tt.expected == 0 { if len(scanner.tokens) != 0 { t.Errorf("expected no tokens, got %d", len(scanner.tokens)) } } else { if len(scanner.tokens) != 1 { t.Errorf("expected 1 token, got %d", len(scanner.tokens)) } else if scanner.tokens[0].Literal != tt.expected { t.Errorf("expected %v, got %v", tt.expected, scanner.tokens[0].Literal) } } }) } } func TestScanToken(t *testing.T) { tests := []struct { name string source string expected token.TokenType lexeme string }{ {"Left paren", "(", token.LEFT_PAREN, "("}, {"Right paren", ")", token.RIGHT_PAREN, ")"}, {"Left brace", "{", token.LEFT_BRACE, "{"}, {"Right brace", "}", token.RIGHT_BRACE, "}"}, {"Comma", ",", token.COMMA, ","}, {"Dot", ".", token.DOT, "."}, {"Minus", "-", token.MINUS, "-"}, {"Plus", "+", token.PLUS, "+"}, {"Semicolon", ";", token.SEMICOLON, ";"}, {"Star", "*", token.STAR, "*"}, {"Bang", "!", token.BANG, "!"}, {"Bang equal", "!=", token.BANG_EQUAL, "!="}, {"Equal", "=", token.EQUAL, "="}, {"Equal equal", "==", token.EQUAL_EQUAL, "=="}, {"Less", "<", token.LESS, "<"}, {"Less equal", "<=", token.LESS_EQUAL, "<="}, {"Greater", ">", token.GREATER, ">"}, {"Greater equal", ">=", token.GREATER_EQUAL, ">="}, {"Slash", "/", token.SLASH, "/"}, {"Comment", "// comment\n", token.EOF, ""}, {"Whitespace", " \r\t\n", token.EOF, ""}, {"String", `"hello"`, token.STRING, `"hello"`}, {"Number", "123", token.NUMBER, "123"}, {"Identifier", "var", token.VAR, "var"}, {"Unexpected character", "@", token.EOF, ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { scanner := New(tt.source, errors.NewMockErrorLogger()) scanner.scanToken() if len(scanner.tokens) > 0 { if scanner.tokens[0].Type != tt.expected { t.Errorf("expected %v, got %v", tt.expected, scanner.tokens[0].Type) } if scanner.tokens[0].Lexeme != tt.lexeme { t.Errorf("expected %v, got %v", tt.lexeme, scanner.tokens[0].Lexeme) } } else if tt.expected != token.EOF { t.Errorf("expected %v, got no tokens", tt.expected) } }) } } func TestIdentifier(t *testing.T) { tests := []struct { name string source string expected token.TokenType }{ {"Keyword and", "and", token.AND}, {"Keyword class", "class", token.CLASS}, {"Keyword else", "else", token.ELSE}, {"Keyword false", "false", token.FALSE}, {"Keyword for", "for", token.FOR}, {"Keyword fun", "fun", token.FUN}, {"Keyword if", "if", token.IF}, {"Keyword nil", "nil", token.NIL}, {"Keyword or", "or", token.OR}, {"Keyword print", "print", token.PRINT}, {"Keyword return", "return", token.RETURN}, {"Keyword super", "super", token.SUPER}, {"Keyword this", "this", token.THIS}, {"Keyword true", "true", token.TRUE}, {"Keyword var", "var", token.VAR}, {"Keyword while", "while", token.WHILE}, {"Identifier", "myVar", token.IDENTIFIER}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { scanner := New(tt.source, errors.NewMockErrorLogger()) scanner.identifier() if len(scanner.tokens) != 1 { t.Fatalf("expected 1 token, got %d", len(scanner.tokens)) } if scanner.tokens[0].Type != tt.expected { t.Errorf("expected %v, got %v", tt.expected, scanner.tokens[0].Type) } }) } }