Add AST package: Node interface, NumberLit, BinaryExpr

- Sealed Node interface with unexported marker method
- NumberLit holds float64 value
- BinaryExpr holds Op (token.Type), Left and Right children
- Tests verify interface satisfaction, data integrity, and nesting
diff --git a/ast/ast.go b/ast/ast.go
new file mode 100644
index 0000000..f1e953d
--- /dev/null
+++ b/ast/ast.go
@@ -0,0 +1,26 @@
+package ast
+
+import "matheval/token"
+
+// Node is the interface all AST nodes implement.
+// The unexported marker method seals the interface — only types
+// in this package can implement it.
+type Node interface {
+	node() // sealed marker
+}
+
+// NumberLit represents a numeric literal (e.g. 3.14).
+type NumberLit struct {
+	Value float64
+}
+
+func (*NumberLit) node() {}
+
+// BinaryExpr represents a binary operation (e.g. 1 + 2).
+type BinaryExpr struct {
+	Op    token.Type // Plus, Minus, Star, Slash
+	Left  Node
+	Right Node
+}
+
+func (*BinaryExpr) node() {}
diff --git a/ast/ast_test.go b/ast/ast_test.go
new file mode 100644
index 0000000..2af413c
--- /dev/null
+++ b/ast/ast_test.go
@@ -0,0 +1,64 @@
+package ast
+
+import (
+	"matheval/token"
+	"testing"
+)
+
+// Compile-time check: both types satisfy Node.
+var (
+	_ Node = (*NumberLit)(nil)
+	_ Node = (*BinaryExpr)(nil)
+)
+
+func TestNumberLit(t *testing.T) {
+	n := &NumberLit{Value: 3.14}
+	if n.Value != 3.14 {
+		t.Fatalf("expected 3.14, got %f", n.Value)
+	}
+}
+
+func TestBinaryExpr(t *testing.T) {
+	left := &NumberLit{Value: 1}
+	right := &NumberLit{Value: 2}
+	expr := &BinaryExpr{
+		Op:    token.Plus,
+		Left:  left,
+		Right: right,
+	}
+
+	if expr.Op != token.Plus {
+		t.Fatalf("expected Plus, got %v", expr.Op)
+	}
+	if expr.Left != left {
+		t.Fatal("Left child mismatch")
+	}
+	if expr.Right != right {
+		t.Fatal("Right child mismatch")
+	}
+}
+
+func TestNestedBinaryExpr(t *testing.T) {
+	// Represents: (1 + 2) * 3
+	tree := &BinaryExpr{
+		Op: token.Star,
+		Left: &BinaryExpr{
+			Op:    token.Plus,
+			Left:  &NumberLit{Value: 1},
+			Right: &NumberLit{Value: 2},
+		},
+		Right: &NumberLit{Value: 3},
+	}
+
+	if tree.Op != token.Star {
+		t.Fatalf("expected Star, got %v", tree.Op)
+	}
+
+	inner, ok := tree.Left.(*BinaryExpr)
+	if !ok {
+		t.Fatal("Left should be *BinaryExpr")
+	}
+	if inner.Op != token.Plus {
+		t.Fatalf("expected Plus, got %v", inner.Op)
+	}
+}