You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

387 lines
9.4 KiB
Go

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)
}
})
}
}