blob: 63943372bb81dd3a148276b10d1d57fe28ced1d3 [file] [log] [blame]
Akron57ee5582025-05-21 15:25:13 +02001package config
2
3import (
4 "fmt"
5 "os"
6
Akronc79d87e2025-05-26 15:03:27 +02007 "github.com/KorAP/KoralPipe-TermMapper2/ast"
8 "github.com/KorAP/KoralPipe-TermMapper2/parser"
Akron57ee5582025-05-21 15:25:13 +02009 "gopkg.in/yaml.v3"
10)
11
12// MappingRule represents a single mapping rule in the configuration
13type MappingRule string
14
15// MappingList represents a list of mapping rules with metadata
16type MappingList struct {
17 ID string `yaml:"id"`
18 FoundryA string `yaml:"foundryA,omitempty"`
19 LayerA string `yaml:"layerA,omitempty"`
20 FoundryB string `yaml:"foundryB,omitempty"`
21 LayerB string `yaml:"layerB,omitempty"`
22 Mappings []MappingRule `yaml:"mappings"`
23}
24
25// Config represents the root configuration containing multiple mapping lists
26type Config struct {
27 Lists []MappingList
28}
29
30// LoadConfig loads a YAML configuration file and returns a Config object
31func LoadConfig(filename string) (*Config, error) {
32 data, err := os.ReadFile(filename)
33 if err != nil {
34 return nil, fmt.Errorf("failed to read config file: %w", err)
35 }
36
Akrona5d88142025-05-22 14:42:09 +020037 // Check for empty file
38 if len(data) == 0 {
39 return nil, fmt.Errorf("EOF: config file is empty")
40 }
41
Akron57ee5582025-05-21 15:25:13 +020042 var lists []MappingList
43 if err := yaml.Unmarshal(data, &lists); err != nil {
44 return nil, fmt.Errorf("failed to parse YAML: %w", err)
45 }
46
47 // Validate the configuration
Akrona5d88142025-05-22 14:42:09 +020048 seenIDs := make(map[string]bool)
Akron57ee5582025-05-21 15:25:13 +020049 for i, list := range lists {
50 if list.ID == "" {
51 return nil, fmt.Errorf("mapping list at index %d is missing an ID", i)
52 }
Akrona5d88142025-05-22 14:42:09 +020053
54 // Check for duplicate IDs
55 if seenIDs[list.ID] {
56 return nil, fmt.Errorf("duplicate mapping list ID found: %s", list.ID)
57 }
58 seenIDs[list.ID] = true
59
Akron57ee5582025-05-21 15:25:13 +020060 if len(list.Mappings) == 0 {
61 return nil, fmt.Errorf("mapping list '%s' has no mapping rules", list.ID)
62 }
63
64 // Validate each mapping rule
65 for j, rule := range list.Mappings {
66 if rule == "" {
67 return nil, fmt.Errorf("mapping list '%s' rule at index %d is empty", list.ID, j)
68 }
69 }
70 }
71
72 return &Config{Lists: lists}, nil
73}
74
75// ParseMappings parses all mapping rules in a list and returns a slice of parsed rules
76func (list *MappingList) ParseMappings() ([]*parser.MappingResult, error) {
77 // Create a grammar parser with the list's default foundries and layers
78 grammarParser, err := parser.NewGrammarParser("", "")
79 if err != nil {
80 return nil, fmt.Errorf("failed to create grammar parser: %w", err)
81 }
82
83 results := make([]*parser.MappingResult, len(list.Mappings))
84 for i, rule := range list.Mappings {
Akrona5d88142025-05-22 14:42:09 +020085 // Check for empty rules first
86 if rule == "" {
87 return nil, fmt.Errorf("empty mapping rule at index %d in list '%s'", i, list.ID)
88 }
89
Akron57ee5582025-05-21 15:25:13 +020090 // Parse the mapping rule
91 result, err := grammarParser.ParseMapping(string(rule))
92 if err != nil {
93 return nil, fmt.Errorf("failed to parse mapping rule %d in list '%s': %w", i, list.ID, err)
94 }
95
96 // Apply default foundries and layers if not specified in the rule
97 if list.FoundryA != "" {
98 applyDefaultFoundryAndLayer(result.Upper.Wrap, list.FoundryA, list.LayerA)
99 }
100 if list.FoundryB != "" {
101 applyDefaultFoundryAndLayer(result.Lower.Wrap, list.FoundryB, list.LayerB)
102 }
103
104 results[i] = result
105 }
106
107 return results, nil
108}
109
110// applyDefaultFoundryAndLayer recursively applies default foundry and layer to terms that don't have them specified
111func applyDefaultFoundryAndLayer(node ast.Node, defaultFoundry, defaultLayer string) {
112 switch n := node.(type) {
113 case *ast.Term:
114 if n.Foundry == "" {
115 n.Foundry = defaultFoundry
116 }
117 if n.Layer == "" {
118 n.Layer = defaultLayer
119 }
120 case *ast.TermGroup:
121 for _, op := range n.Operands {
122 applyDefaultFoundryAndLayer(op, defaultFoundry, defaultLayer)
123 }
124 }
125}