Files
zzg a22c99344e
ci / test (push) Canceled after 0s
initial commit
2026-09-25 22:12:55 -07:00

151 lines
3.2 KiB
Go

package parser
import (
"testing"
"github.com/zg/asm-calc/lexer"
)
func TestParseNumbers(t *testing.T) {
node, err := Parse("42")
if err != nil {
t.Fatal(err)
}
n, ok := node.(*Number)
if !ok {
t.Fatalf("got %T, want *Number", node)
}
if n.Value != 42 {
t.Errorf("= %d, want 42", n.Value)
}
}
// Precedence and associativity are structural, so check the tree shape.
func TestPrecedence(t *testing.T) {
// 2 + 3 * 4 must group as 2 + (3 * 4).
node, err := Parse("2 + 3 * 4")
if err != nil {
t.Fatal(err)
}
top, ok := node.(*BinaryOp)
if !ok || top.Op != lexer.PLUS {
t.Fatalf("top of tree = %T %v, want a PLUS", node, top)
}
right, ok := top.Right.(*BinaryOp)
if !ok || right.Op != lexer.STAR {
t.Errorf("right branch = %T, want a STAR", top.Right)
}
}
func TestLeftAssociativity(t *testing.T) {
// 1 - 2 - 3 must group as (1 - 2) - 3.
node, _ := Parse("1 - 2 - 3")
top := node.(*BinaryOp)
if _, ok := top.Left.(*BinaryOp); !ok {
t.Error("subtraction should be left-associative")
}
if n, ok := top.Right.(*Number); !ok || n.Value != 3 {
t.Error("right operand should be the last number")
}
}
func TestPowerRightAssociativity(t *testing.T) {
// 2 ^ 3 ^ 2 must group as 2 ^ (3 ^ 2).
node, _ := Parse("2 ^ 3 ^ 2")
top := node.(*BinaryOp)
if top.Op != lexer.CARET {
t.Fatalf("top = %v, want CARET", top.Op)
}
if n, ok := top.Left.(*Number); !ok || n.Value != 2 {
t.Error("left operand should be 2")
}
if _, ok := top.Right.(*BinaryOp); !ok {
t.Error("exponentiation should be right-associative")
}
}
func TestUnaryMinus(t *testing.T) {
node, err := Parse("-5")
if err != nil {
t.Fatal(err)
}
if _, ok := node.(*UnaryOp); !ok {
t.Fatalf("got %T, want *UnaryOp", node)
}
// Double negation nests.
node, _ = Parse("--5")
outer := node.(*UnaryOp)
if _, ok := outer.Operand.(*UnaryOp); !ok {
t.Error("double negation should nest")
}
}
func TestAssignment(t *testing.T) {
node, err := Parse("x = 1 + 2")
if err != nil {
t.Fatal(err)
}
a, ok := node.(*Assignment)
if !ok {
t.Fatalf("got %T, want *Assignment", node)
}
if a.Name != "x" {
t.Errorf("name = %q, want x", a.Name)
}
if _, ok := a.Value.(*BinaryOp); !ok {
t.Error("value should be an expression")
}
}
// A variable at the start of an expression must not be mistaken for an
// assignment.
func TestVariableIsNotAssignment(t *testing.T) {
node, err := Parse("x + 1")
if err != nil {
t.Fatal(err)
}
if _, ok := node.(*Assignment); ok {
t.Error("x + 1 is an expression, not an assignment")
}
}
func TestParseErrors(t *testing.T) {
tests := []string{
"",
"1 +",
"* 2",
"(1 + 2",
"1 + 2)",
"1 2",
"@",
"99999999999999999999999999",
}
for _, in := range tests {
t.Run(in, func(t *testing.T) {
if _, err := Parse(in); err == nil {
t.Error("expected a parse error")
}
})
}
}
// Errors must point at a column so the user can see where the problem is.
func TestErrorsCarryColumn(t *testing.T) {
_, err := Parse("1 + @")
if err == nil {
t.Fatal("expected an error")
}
if !contains(err.Error(), "column") {
t.Errorf("error lacks a column: %q", err)
}
}
func contains(s, sub string) bool {
for i := 0; i+len(sub) <= len(s); i++ {
if s[i:i+len(sub)] == sub {
return true
}
}
return false
}