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.
382 lines
8.9 KiB
Go
382 lines
8.9 KiB
Go
package scanner
|
|
|
|
import (
|
|
"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)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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
|
|
}{
|
|
{"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},
|
|
{"Number", "123", token.NUMBER},
|
|
{"Identifier", "var", token.VAR},
|
|
{"Unexpected character", "@", token.EOF},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
scanner := New(tt.source)
|
|
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)
|
|
}
|
|
} 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)
|
|
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)
|
|
}
|
|
})
|
|
}
|
|
}
|