Files
five/compiler/lexer/lexer_test.go
Charles KWON OhJun 59568f3301 Five v0.9 — Harbour + Go fusion language
- Compiler: PP → Lexer → Parser → Analyzer → Gengo pipeline
- Parser: 232/236 (98%) Harbour compatibility, registry-based dispatch
- RTL: 351 Harbour-compatible functions
- RDD: DBF/NTX/CDX engines with Rushmore bitmap optimization
- Go Interop: IMPORT + pkg.Func() + obj:Method() with FastPath (15M calls/sec)
- HB_FUNC API: Full Harbour C API compatible Go bridge
- Concurrency: SPAWN/LAUNCH/GOROUTINE, <-, WATCH, PARALLEL FOR, ASYNC/AWAIT
- Extensions: Multi-return, DEFER, Slice, f-string, Nil-safe ?:, CONST
- Macro Compiler: Runtime AST parsing and evaluation
- Debugger: TUI debugger with source display, breakpoints, stepping
- FRB: Native + Pcode dual mode runtime binary
- Tests: 13 packages ALL PASS

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 09:41:50 +09:00

261 lines
7.7 KiB
Go

// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
// All rights reserved.
package lexer
import (
"five/compiler/token"
"testing"
)
func expectTokens(t *testing.T, source string, expected []token.Kind) {
t.Helper()
tokens := Tokenize("test.prg", source)
// Filter out NEWLINEs and EOF for easier comparison
var got []token.Kind
for _, tok := range tokens {
if tok.Kind != token.NEWLINE && tok.Kind != token.EOF {
got = append(got, tok.Kind)
}
}
if len(got) != len(expected) {
t.Errorf("token count: got %d, want %d", len(got), len(expected))
for i, tok := range tokens {
t.Logf(" [%d] %v %q", i, tok.Kind, tok.Literal)
}
return
}
for i, want := range expected {
if got[i] != want {
t.Errorf("token[%d]: got %v, want %v", i, got[i], want)
}
}
}
func TestBasicArithmetic(t *testing.T) {
expectTokens(t, "1 + 2 * 3", []token.Kind{
token.INT, token.PLUS, token.INT, token.STAR, token.INT,
})
}
func TestAssignment(t *testing.T) {
expectTokens(t, "x := 10", []token.Kind{
token.IDENT, token.ASSIGN, token.INT,
})
}
func TestCompoundAssignment(t *testing.T) {
expectTokens(t, "n += 5", []token.Kind{
token.IDENT, token.PLUSEQ, token.INT,
})
}
func TestStringLiteral(t *testing.T) {
tokens := Tokenize("test.prg", `"Hello, World!"`)
if tokens[0].Kind != token.STRING || tokens[0].Literal != "Hello, World!" {
t.Errorf("got %v %q", tokens[0].Kind, tokens[0].Literal)
}
}
func TestSingleQuoteString(t *testing.T) {
tokens := Tokenize("test.prg", `'single'`)
if tokens[0].Kind != token.STRING || tokens[0].Literal != "single" {
t.Errorf("got %v %q", tokens[0].Kind, tokens[0].Literal)
}
}
func TestLogicalLiterals(t *testing.T) {
expectTokens(t, ".T. .F.", []token.Kind{token.TRUE, token.FALSE})
}
func TestLogicalOperators(t *testing.T) {
expectTokens(t, ".AND. .OR. .NOT.", []token.Kind{token.AND, token.OR, token.NOT})
}
func TestLogicalCaseInsensitive(t *testing.T) {
expectTokens(t, ".and. .or. .not. .t. .f.", []token.Kind{
token.AND, token.OR, token.NOT, token.TRUE, token.FALSE,
})
}
func TestKeywords(t *testing.T) {
expectTokens(t, "FUNCTION Main", []token.Kind{token.FUNCTION_KW, token.IDENT})
expectTokens(t, "function main", []token.Kind{token.FUNCTION_KW, token.IDENT})
expectTokens(t, "LOCAL n := 0", []token.Kind{token.LOCAL, token.IDENT, token.ASSIGN, token.INT})
expectTokens(t, "IF x > 10", []token.Kind{token.IF, token.IDENT, token.GT, token.INT})
expectTokens(t, "DO WHILE i <= 10", []token.Kind{token.DO, token.WHILE, token.IDENT, token.LTE, token.INT})
expectTokens(t, "RETURN NIL", []token.Kind{token.RETURN, token.NIL_LIT})
}
func TestXBaseCommands(t *testing.T) {
expectTokens(t, "USE customers", []token.Kind{token.USE, token.IDENT})
expectTokens(t, "SEEK cKey", []token.Kind{token.SEEK, token.IDENT})
expectTokens(t, "REPLACE name WITH cNewName", []token.Kind{
token.REPLACE, token.IDENT, token.WITH, token.IDENT,
})
expectTokens(t, "APPEND BLANK", []token.Kind{token.APPEND, token.BLANK})
expectTokens(t, "GO TOP", []token.Kind{token.GO, token.TOP})
}
func TestClassDeclaration(t *testing.T) {
expectTokens(t, "CLASS Person", []token.Kind{token.CLASS, token.IDENT})
expectTokens(t, "DATA cName INIT", []token.Kind{token.DATA, token.IDENT, token.IDENT})
expectTokens(t, "METHOD New", []token.Kind{token.METHOD, token.IDENT})
expectTokens(t, "ENDCLASS", []token.Kind{token.ENDCLASS})
}
func TestArrowAndColons(t *testing.T) {
expectTokens(t, "cust->name", []token.Kind{
token.IDENT, token.ARROW, token.IDENT,
})
expectTokens(t, "obj:greet()", []token.Kind{
token.IDENT, token.COLON, token.IDENT, token.LPAREN, token.RPAREN,
})
expectTokens(t, "::name", []token.Kind{token.COLONCOLON, token.IDENT})
}
func TestCodeBlock(t *testing.T) {
expectTokens(t, "{|x| x + 1}", []token.Kind{
token.LBRACE, token.PIPE, token.IDENT, token.PIPE,
token.IDENT, token.PLUS, token.INT, token.RBRACE,
})
}
func TestHashLiteral(t *testing.T) {
expectTokens(t, `{"a" => 1}`, []token.Kind{
token.LBRACE, token.STRING, token.DBLARROW, token.INT, token.RBRACE,
})
}
func TestComparison(t *testing.T) {
expectTokens(t, "a == b", []token.Kind{token.IDENT, token.EXEQ, token.IDENT})
expectTokens(t, "a != b", []token.Kind{token.IDENT, token.NEQ, token.IDENT})
expectTokens(t, "a <> b", []token.Kind{token.IDENT, token.NEQ, token.IDENT})
expectTokens(t, "a # b", []token.Kind{token.IDENT, token.NEQ, token.IDENT})
expectTokens(t, "a <= b", []token.Kind{token.IDENT, token.LTE, token.IDENT})
expectTokens(t, "a >= b", []token.Kind{token.IDENT, token.GTE, token.IDENT})
}
func TestDoubleNumber(t *testing.T) {
tokens := Tokenize("test.prg", "3.14")
if tokens[0].Kind != token.DOUBLE || tokens[0].Literal != "3.14" {
t.Errorf("got %v %q", tokens[0].Kind, tokens[0].Literal)
}
}
func TestHexNumber(t *testing.T) {
tokens := Tokenize("test.prg", "0xFF")
if tokens[0].Kind != token.INT || tokens[0].Literal != "0xFF" {
t.Errorf("got %v %q", tokens[0].Kind, tokens[0].Literal)
}
}
func TestMacroOperator(t *testing.T) {
expectTokens(t, "&cVar", []token.Kind{token.AMPERSAND, token.IDENT})
}
func TestImport(t *testing.T) {
expectTokens(t, `IMPORT "net/http"`, []token.Kind{token.IMPORT, token.STRING})
}
func TestPreprocessor(t *testing.T) {
tokens := Tokenize("test.prg", "#include")
if tokens[0].Kind != token.PP_INCLUDE {
t.Errorf("got %v, want PP_INCLUDE", tokens[0].Kind)
}
tokens = Tokenize("test.prg", "#define")
if tokens[0].Kind != token.PP_DEFINE {
t.Errorf("got %v, want PP_DEFINE", tokens[0].Kind)
}
tokens = Tokenize("test.prg", "#pragma")
if tokens[0].Kind != token.PP_PRAGMA {
t.Errorf("got %v, want PP_PRAGMA", tokens[0].Kind)
}
}
func TestLineComment(t *testing.T) {
expectTokens(t, "x := 10 // comment", []token.Kind{
token.IDENT, token.ASSIGN, token.INT,
})
}
func TestAmpAmpComment(t *testing.T) {
expectTokens(t, "x := 10 && comment", []token.Kind{
token.IDENT, token.ASSIGN, token.INT,
})
}
func TestBlockComment(t *testing.T) {
expectTokens(t, "x /* skip */ + y", []token.Kind{
token.IDENT, token.PLUS, token.IDENT,
})
}
func TestLineContinuation(t *testing.T) {
// Semicolon at end of line = continuation
expectTokens(t, "x + ;\n y", []token.Kind{
token.IDENT, token.PLUS, token.IDENT,
})
}
func TestNewlineAsTerminator(t *testing.T) {
tokens := Tokenize("test.prg", "x\ny")
kinds := make([]token.Kind, 0)
for _, tok := range tokens {
if tok.Kind != token.EOF {
kinds = append(kinds, tok.Kind)
}
}
// Should have: IDENT NEWLINE IDENT
if len(kinds) != 3 || kinds[1] != token.NEWLINE {
t.Errorf("expected IDENT NEWLINE IDENT, got %v", kinds)
}
}
func TestPosition(t *testing.T) {
tokens := Tokenize("test.prg", "x := 10")
if tokens[0].Pos.Line != 1 || tokens[0].Pos.Col != 1 {
t.Errorf("x position: line=%d col=%d", tokens[0].Pos.Line, tokens[0].Pos.Col)
}
}
// Full program test
func TestFullProgram(t *testing.T) {
src := `FUNCTION Main()
LOCAL n := 10
? "Hello", n
RETURN NIL`
tokens := Tokenize("test.prg", src)
var kinds []token.Kind
for _, tok := range tokens {
if tok.Kind != token.NEWLINE && tok.Kind != token.EOF {
kinds = append(kinds, tok.Kind)
}
}
expected := []token.Kind{
token.FUNCTION_KW, token.IDENT, token.LPAREN, token.RPAREN,
token.LOCAL, token.IDENT, token.ASSIGN, token.INT,
token.QMARK, token.STRING, token.COMMA, token.IDENT,
token.RETURN, token.NIL_LIT,
}
if len(kinds) != len(expected) {
t.Errorf("token count: got %d, want %d", len(kinds), len(expected))
for i, tok := range tokens {
if tok.Kind != token.NEWLINE && tok.Kind != token.EOF {
t.Logf(" [%d] %v %q", i, tok.Kind, tok.Literal)
}
}
return
}
for i, want := range expected {
if kinds[i] != want {
t.Errorf("token[%d]: got %v %q, want %v", i, kinds[i], tokens[i].Literal, want)
}
}
}