Compare commits
No commits in common. "b2a1b7a79bf78a0992d103915f6c5cef9a90bd0d" and "29044dc74cdd37e75db3fd19b0dd1f1aefc63e7e" have entirely different histories.
b2a1b7a79b
...
29044dc74c
@ -1,6 +1,6 @@
|
|||||||
# Mako
|
# Mako
|
||||||
|
|
||||||
This is the repo for the Mako language spec. This is a reference parser, and its only job is to parse source to AST or bytecode. Other projects will come along for VM implementations.
|
Scripting language!
|
||||||
|
|
||||||
```
|
```
|
||||||
// C-style comments
|
// C-style comments
|
||||||
|
@ -43,16 +43,6 @@ func (as *AssignStatement) String() string {
|
|||||||
return fmt.Sprintf("%s = %s", as.Name.String(), as.Value.String())
|
return fmt.Sprintf("%s = %s", as.Name.String(), as.Value.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// EchoStatement represents echo output statements
|
|
||||||
type EchoStatement struct {
|
|
||||||
Value Expression
|
|
||||||
}
|
|
||||||
|
|
||||||
func (es *EchoStatement) statementNode() {}
|
|
||||||
func (es *EchoStatement) String() string {
|
|
||||||
return fmt.Sprintf("echo %s", es.Value.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// ElseIfClause represents an elseif condition
|
// ElseIfClause represents an elseif condition
|
||||||
type ElseIfClause struct {
|
type ElseIfClause struct {
|
||||||
Condition Expression
|
Condition Expression
|
||||||
|
@ -105,8 +105,6 @@ func (p *Parser) parseStatement() Statement {
|
|||||||
return nil
|
return nil
|
||||||
case IF:
|
case IF:
|
||||||
return p.parseIfStatement()
|
return p.parseIfStatement()
|
||||||
case ECHO:
|
|
||||||
return p.parseEchoStatement()
|
|
||||||
case ASSIGN:
|
case ASSIGN:
|
||||||
p.addError("assignment operator '=' without left-hand side identifier")
|
p.addError("assignment operator '=' without left-hand side identifier")
|
||||||
return nil
|
return nil
|
||||||
@ -147,21 +145,6 @@ func (p *Parser) parseAssignStatement() *AssignStatement {
|
|||||||
return stmt
|
return stmt
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseEchoStatement parses echo statements
|
|
||||||
func (p *Parser) parseEchoStatement() *EchoStatement {
|
|
||||||
stmt := &EchoStatement{}
|
|
||||||
|
|
||||||
p.nextToken() // move past 'echo'
|
|
||||||
|
|
||||||
stmt.Value = p.ParseExpression(LOWEST)
|
|
||||||
if stmt.Value == nil {
|
|
||||||
p.addError("expected expression after 'echo'")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return stmt
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseIfStatement parses if/elseif/else/end statements
|
// parseIfStatement parses if/elseif/else/end statements
|
||||||
func (p *Parser) parseIfStatement() *IfStatement {
|
func (p *Parser) parseIfStatement() *IfStatement {
|
||||||
stmt := &IfStatement{}
|
stmt := &IfStatement{}
|
||||||
@ -604,8 +587,6 @@ func tokenTypeString(t TokenType) string {
|
|||||||
return "else"
|
return "else"
|
||||||
case END:
|
case END:
|
||||||
return "end"
|
return "end"
|
||||||
case ECHO:
|
|
||||||
return "echo"
|
|
||||||
case EOF:
|
case EOF:
|
||||||
return "end of file"
|
return "end of file"
|
||||||
case ILLEGAL:
|
case ILLEGAL:
|
||||||
|
@ -1,121 +0,0 @@
|
|||||||
package parser_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"git.sharkk.net/Sharkk/Mako/parser"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBasicEchoStatement(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
input string
|
|
||||||
expectedVal any
|
|
||||||
isExpr bool
|
|
||||||
desc string
|
|
||||||
}{
|
|
||||||
{`echo 42`, 42.0, false, "echo number"},
|
|
||||||
{`echo "hello"`, "hello", false, "echo string"},
|
|
||||||
{`echo true`, true, false, "echo boolean"},
|
|
||||||
{`echo nil`, nil, false, "echo nil"},
|
|
||||||
{`echo x`, "x", false, "echo identifier"},
|
|
||||||
{`echo 1 + 2`, "(1.00 + 2.00)", true, "echo expression"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.desc, func(t *testing.T) {
|
|
||||||
l := parser.NewLexer(tt.input)
|
|
||||||
p := parser.NewParser(l)
|
|
||||||
program := p.ParseProgram()
|
|
||||||
checkParserErrors(t, p)
|
|
||||||
|
|
||||||
if len(program.Statements) != 1 {
|
|
||||||
t.Fatalf("expected 1 statement, got %d", len(program.Statements))
|
|
||||||
}
|
|
||||||
|
|
||||||
stmt, ok := program.Statements[0].(*parser.EchoStatement)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("expected EchoStatement, got %T", program.Statements[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
if tt.isExpr {
|
|
||||||
if stmt.Value.String() != tt.expectedVal.(string) {
|
|
||||||
t.Errorf("expected %s, got %s", tt.expectedVal.(string), stmt.Value.String())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch expected := tt.expectedVal.(type) {
|
|
||||||
case float64:
|
|
||||||
testNumberLiteral(t, stmt.Value, expected)
|
|
||||||
case string:
|
|
||||||
if expected == "x" {
|
|
||||||
testIdentifier(t, stmt.Value, expected)
|
|
||||||
} else {
|
|
||||||
testStringLiteral(t, stmt.Value, expected)
|
|
||||||
}
|
|
||||||
case bool:
|
|
||||||
testBooleanLiteral(t, stmt.Value, expected)
|
|
||||||
case nil:
|
|
||||||
testNilLiteral(t, stmt.Value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEchoStringRepresentation(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
input string
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{`echo "hello"`, `echo "hello"`},
|
|
||||||
{`echo 42`, `echo 42.00`},
|
|
||||||
{`echo x + y`, `echo (x + y)`},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.input, func(t *testing.T) {
|
|
||||||
l := parser.NewLexer(tt.input)
|
|
||||||
p := parser.NewParser(l)
|
|
||||||
program := p.ParseProgram()
|
|
||||||
checkParserErrors(t, p)
|
|
||||||
|
|
||||||
stmt := program.Statements[0].(*parser.EchoStatement)
|
|
||||||
if stmt.String() != tt.expected {
|
|
||||||
t.Errorf("expected %s, got %s", tt.expected, stmt.String())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEchoWithComplexExpressions(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
input string
|
|
||||||
desc string
|
|
||||||
}{
|
|
||||||
{`echo {1, 2, 3}`, "echo table array"},
|
|
||||||
{`echo {x = 1, y = 2}`, "echo table hash"},
|
|
||||||
{`echo 0xFF + 0b1010`, "echo extended numbers"},
|
|
||||||
{`echo [[multiline string]]`, "echo multiline string"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.desc, func(t *testing.T) {
|
|
||||||
l := parser.NewLexer(tt.input)
|
|
||||||
p := parser.NewParser(l)
|
|
||||||
program := p.ParseProgram()
|
|
||||||
checkParserErrors(t, p)
|
|
||||||
|
|
||||||
if len(program.Statements) != 1 {
|
|
||||||
t.Fatalf("expected 1 statement, got %d", len(program.Statements))
|
|
||||||
}
|
|
||||||
|
|
||||||
stmt, ok := program.Statements[0].(*parser.EchoStatement)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("expected EchoStatement, got %T", program.Statements[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
if stmt.Value == nil {
|
|
||||||
t.Error("expected non-nil echo value")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -138,75 +138,3 @@ func TestErrorMessages(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEchoErrors(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
input string
|
|
||||||
expectedError string
|
|
||||||
desc string
|
|
||||||
}{
|
|
||||||
{"echo", "expected expression after 'echo'", "echo without expression"},
|
|
||||||
{"echo +", "unexpected operator '+'", "echo with invalid expression"},
|
|
||||||
{"echo (", "unexpected end of input", "echo with incomplete expression"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.desc, func(t *testing.T) {
|
|
||||||
l := parser.NewLexer(tt.input)
|
|
||||||
p := parser.NewParser(l)
|
|
||||||
p.ParseProgram()
|
|
||||||
|
|
||||||
if !p.HasErrors() {
|
|
||||||
t.Fatal("expected parsing errors")
|
|
||||||
}
|
|
||||||
|
|
||||||
errors := p.Errors()
|
|
||||||
found := false
|
|
||||||
for _, err := range errors {
|
|
||||||
if strings.Contains(err.Message, tt.expectedError) {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !found {
|
|
||||||
errorMsgs := make([]string, len(errors))
|
|
||||||
for i, err := range errors {
|
|
||||||
errorMsgs[i] = err.Message
|
|
||||||
}
|
|
||||||
t.Errorf("expected error containing %q, got %v", tt.expectedError, errorMsgs)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTokenTypeStringWithEcho(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
input string
|
|
||||||
expectedMessage string
|
|
||||||
}{
|
|
||||||
{"echo", "Parse error at line 1, column 1: expected expression after 'echo' (near '')"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.input, func(t *testing.T) {
|
|
||||||
l := parser.NewLexer(tt.input)
|
|
||||||
p := parser.NewParser(l)
|
|
||||||
p.ParseProgram()
|
|
||||||
|
|
||||||
if !p.HasErrors() {
|
|
||||||
t.Fatal("expected parsing errors")
|
|
||||||
}
|
|
||||||
|
|
||||||
errors := p.Errors()
|
|
||||||
if len(errors) == 0 {
|
|
||||||
t.Fatal("expected at least one error")
|
|
||||||
}
|
|
||||||
|
|
||||||
errorStr := errors[0].Error()
|
|
||||||
if !strings.Contains(errorStr, "Parse error at line") {
|
|
||||||
t.Errorf("expected formatted error message, got: %s", errorStr)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -201,61 +201,3 @@ end`
|
|||||||
t.Errorf("expected 4 table pairs, got %d", len(table.Pairs))
|
t.Errorf("expected 4 table pairs, got %d", len(table.Pairs))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMixedStatementsWithEcho(t *testing.T) {
|
|
||||||
input := `x = 42
|
|
||||||
echo x
|
|
||||||
if x then
|
|
||||||
echo "found x"
|
|
||||||
end
|
|
||||||
echo {result = x}`
|
|
||||||
|
|
||||||
l := parser.NewLexer(input)
|
|
||||||
p := parser.NewParser(l)
|
|
||||||
program := p.ParseProgram()
|
|
||||||
checkParserErrors(t, p)
|
|
||||||
|
|
||||||
if len(program.Statements) != 4 {
|
|
||||||
t.Fatalf("expected 4 statements, got %d", len(program.Statements))
|
|
||||||
}
|
|
||||||
|
|
||||||
// First: assignment
|
|
||||||
_, ok := program.Statements[0].(*parser.AssignStatement)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("statement 0: expected AssignStatement, got %T", program.Statements[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Second: echo
|
|
||||||
echo1, ok := program.Statements[1].(*parser.EchoStatement)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("statement 1: expected EchoStatement, got %T", program.Statements[1])
|
|
||||||
}
|
|
||||||
testIdentifier(t, echo1.Value, "x")
|
|
||||||
|
|
||||||
// Third: if statement with echo in body
|
|
||||||
ifStmt, ok := program.Statements[2].(*parser.IfStatement)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("statement 2: expected IfStatement, got %T", program.Statements[2])
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(ifStmt.Body) != 1 {
|
|
||||||
t.Fatalf("expected 1 body statement, got %d", len(ifStmt.Body))
|
|
||||||
}
|
|
||||||
|
|
||||||
bodyEcho, ok := ifStmt.Body[0].(*parser.EchoStatement)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("if body: expected EchoStatement, got %T", ifStmt.Body[0])
|
|
||||||
}
|
|
||||||
testStringLiteral(t, bodyEcho.Value, "found x")
|
|
||||||
|
|
||||||
// Fourth: echo with table
|
|
||||||
echo2, ok := program.Statements[3].(*parser.EchoStatement)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("statement 3: expected EchoStatement, got %T", program.Statements[3])
|
|
||||||
}
|
|
||||||
|
|
||||||
_, ok = echo2.Value.(*parser.TableLiteral)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("expected TableLiteral in echo, got %T", echo2.Value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -33,7 +33,6 @@ const (
|
|||||||
ELSEIF
|
ELSEIF
|
||||||
ELSE
|
ELSE
|
||||||
END
|
END
|
||||||
ECHO
|
|
||||||
|
|
||||||
// Special
|
// Special
|
||||||
EOF
|
EOF
|
||||||
@ -80,7 +79,6 @@ func lookupIdent(ident string) TokenType {
|
|||||||
"elseif": ELSEIF,
|
"elseif": ELSEIF,
|
||||||
"else": ELSE,
|
"else": ELSE,
|
||||||
"end": END,
|
"end": END,
|
||||||
"echo": ECHO,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if tok, ok := keywords[ident]; ok {
|
if tok, ok := keywords[ident]; ok {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user