token: add Type enum, Token struct, and String() method
diff --git a/token/token.go b/token/token.go
new file mode 100644
index 0000000..d50eb36
--- /dev/null
+++ b/token/token.go
@@ -0,0 +1,48 @@
+package token
+
+import "fmt"
+
+// Type represents the type of a lexical token.
+type Type int
+
+const (
+	Number Type = iota // numeric literal
+	Plus               // +
+	Minus              // -
+	Star               // *
+	Slash              // /
+	LParen             // (
+	RParen             // )
+	EOF                // end of input
+)
+
+// String returns a human-readable name for the token type.
+func (t Type) String() string {
+	switch t {
+	case Number:
+		return "Number"
+	case Plus:
+		return "+"
+	case Minus:
+		return "-"
+	case Star:
+		return "*"
+	case Slash:
+		return "/"
+	case LParen:
+		return "("
+	case RParen:
+		return ")"
+	case EOF:
+		return "EOF"
+	default:
+		return fmt.Sprintf("Unknown(%d)", int(t))
+	}
+}
+
+// Token represents a single lexical token.
+type Token struct {
+	Type    Type   // the kind of token
+	Literal string // raw text (e.g. "3.14", "+")
+	Pos     int    // byte offset in input string
+}
diff --git a/token/token_test.go b/token/token_test.go
new file mode 100644
index 0000000..05045b0
--- /dev/null
+++ b/token/token_test.go
@@ -0,0 +1,25 @@
+package token
+
+import "testing"
+
+func TestTypeString(t *testing.T) {
+	tests := []struct {
+		typ  Type
+		want string
+	}{
+		{Number, "Number"},
+		{Plus, "+"},
+		{Minus, "-"},
+		{Star, "*"},
+		{Slash, "/"},
+		{LParen, "("},
+		{RParen, ")"},
+		{EOF, "EOF"},
+		{Type(99), "Unknown(99)"},
+	}
+	for _, tc := range tests {
+		if got := tc.typ.String(); got != tc.want {
+			t.Errorf("Type(%d).String() = %q, want %q", int(tc.typ), got, tc.want)
+		}
+	}
+}