151 lines
3.2 KiB
Go
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
|
|
}
|