Test new approach with AST (AI assisted)
diff --git a/pkg/ast/ast.go b/pkg/ast/ast.go
new file mode 100644
index 0000000..aac7016
--- /dev/null
+++ b/pkg/ast/ast.go
@@ -0,0 +1,73 @@
+package ast
+
+// NodeType represents the type of a node in the AST
+type NodeType string
+
+const (
+ TokenNode NodeType = "token"
+ TermGroupNode NodeType = "termGroup"
+ TermNode NodeType = "term"
+)
+
+// RelationType represents the type of relation between nodes
+type RelationType string
+
+const (
+ AndRelation RelationType = "and"
+ OrRelation RelationType = "or"
+)
+
+// MatchType represents the type of match operation
+type MatchType string
+
+const (
+ MatchEqual MatchType = "eq"
+ MatchNotEqual MatchType = "ne"
+)
+
+// Node represents a node in the AST
+type Node interface {
+ Type() NodeType
+}
+
+// Token represents a token node in the query
+type Token struct {
+ Wrap Node `json:"wrap"`
+}
+
+func (t *Token) Type() NodeType {
+ return TokenNode
+}
+
+// TermGroup represents a group of terms with a relation
+type TermGroup struct {
+ Operands []Node `json:"operands"`
+ Relation RelationType `json:"relation"`
+}
+
+func (tg *TermGroup) Type() NodeType {
+ return TermGroupNode
+}
+
+// Term represents a terminal node with matching criteria
+type Term struct {
+ Foundry string `json:"foundry"`
+ Key string `json:"key"`
+ Layer string `json:"layer"`
+ Match MatchType `json:"match"`
+ Value string `json:"value,omitempty"`
+}
+
+func (t *Term) Type() NodeType {
+ return TermNode
+}
+
+// Pattern represents a pattern to match in the AST
+type Pattern struct {
+ Root Node
+}
+
+// Replacement represents a replacement pattern
+type Replacement struct {
+ Root Node
+}
diff --git a/pkg/ast/ast_test.go b/pkg/ast/ast_test.go
new file mode 100644
index 0000000..1a01d0b
--- /dev/null
+++ b/pkg/ast/ast_test.go
@@ -0,0 +1,190 @@
+package ast
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestNodeTypes(t *testing.T) {
+ tests := []struct {
+ name string
+ node Node
+ expected NodeType
+ }{
+ {
+ name: "Token node returns correct type",
+ node: &Token{Wrap: &Term{}},
+ expected: TokenNode,
+ },
+ {
+ name: "TermGroup node returns correct type",
+ node: &TermGroup{
+ Operands: []Node{&Term{}},
+ Relation: AndRelation,
+ },
+ expected: TermGroupNode,
+ },
+ {
+ name: "Term node returns correct type",
+ node: &Term{
+ Foundry: "opennlp",
+ Key: "DET",
+ Layer: "p",
+ Match: MatchEqual,
+ },
+ expected: TermNode,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ assert.Equal(t, tt.expected, tt.node.Type())
+ })
+ }
+}
+
+func TestTermGroupConstruction(t *testing.T) {
+ term1 := &Term{
+ Foundry: "opennlp",
+ Key: "DET",
+ Layer: "p",
+ Match: MatchEqual,
+ }
+
+ term2 := &Term{
+ Foundry: "opennlp",
+ Key: "AdjType",
+ Layer: "m",
+ Match: MatchEqual,
+ Value: "Pdt",
+ }
+
+ group := &TermGroup{
+ Operands: []Node{term1, term2},
+ Relation: AndRelation,
+ }
+
+ assert.Len(t, group.Operands, 2)
+ assert.Equal(t, AndRelation, group.Relation)
+ assert.Equal(t, TermGroupNode, group.Type())
+
+ // Test operands are correctly set
+ assert.Equal(t, term1, group.Operands[0])
+ assert.Equal(t, term2, group.Operands[1])
+}
+
+func TestTokenConstruction(t *testing.T) {
+ term := &Term{
+ Foundry: "opennlp",
+ Key: "DET",
+ Layer: "p",
+ Match: MatchEqual,
+ }
+
+ token := &Token{Wrap: term}
+
+ assert.Equal(t, TokenNode, token.Type())
+ assert.Equal(t, term, token.Wrap)
+}
+
+func TestTermConstruction(t *testing.T) {
+ tests := []struct {
+ name string
+ term *Term
+ foundry string
+ key string
+ layer string
+ match MatchType
+ hasValue bool
+ value string
+ }{
+ {
+ name: "Term without value",
+ term: &Term{
+ Foundry: "opennlp",
+ Key: "DET",
+ Layer: "p",
+ Match: MatchEqual,
+ },
+ foundry: "opennlp",
+ key: "DET",
+ layer: "p",
+ match: MatchEqual,
+ hasValue: false,
+ },
+ {
+ name: "Term with value",
+ term: &Term{
+ Foundry: "opennlp",
+ Key: "AdjType",
+ Layer: "m",
+ Match: MatchEqual,
+ Value: "Pdt",
+ },
+ foundry: "opennlp",
+ key: "AdjType",
+ layer: "m",
+ match: MatchEqual,
+ hasValue: true,
+ value: "Pdt",
+ },
+ {
+ name: "Term with not equal match",
+ term: &Term{
+ Foundry: "opennlp",
+ Key: "DET",
+ Layer: "p",
+ Match: MatchNotEqual,
+ },
+ foundry: "opennlp",
+ key: "DET",
+ layer: "p",
+ match: MatchNotEqual,
+ hasValue: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ assert.Equal(t, TermNode, tt.term.Type())
+ assert.Equal(t, tt.foundry, tt.term.Foundry)
+ assert.Equal(t, tt.key, tt.term.Key)
+ assert.Equal(t, tt.layer, tt.term.Layer)
+ assert.Equal(t, tt.match, tt.term.Match)
+ if tt.hasValue {
+ assert.Equal(t, tt.value, tt.term.Value)
+ } else {
+ assert.Empty(t, tt.term.Value)
+ }
+ })
+ }
+}
+
+func TestPatternAndReplacement(t *testing.T) {
+ // Create a simple pattern
+ patternTerm := &Term{
+ Foundry: "opennlp",
+ Key: "DET",
+ Layer: "p",
+ Match: MatchEqual,
+ }
+ pattern := Pattern{Root: patternTerm}
+
+ // Create a simple replacement
+ replacementTerm := &Term{
+ Foundry: "opennlp",
+ Key: "COMBINED_DET",
+ Layer: "p",
+ Match: MatchEqual,
+ }
+ replacement := Replacement{Root: replacementTerm}
+
+ // Test pattern
+ assert.NotNil(t, pattern.Root)
+ assert.Equal(t, patternTerm, pattern.Root)
+
+ // Test replacement
+ assert.NotNil(t, replacement.Root)
+ assert.Equal(t, replacementTerm, replacement.Root)
+}