blob: a72ba9b474d9045b8691e5327c9a2ffb8bcbb83b [file] [log] [blame]
Akron57ee5582025-05-21 15:25:13 +02001package config
2
3import (
4 "fmt"
5 "os"
6
7 "github.com/KorAP/KoralPipe-TermMapper2/pkg/ast"
8 "github.com/KorAP/KoralPipe-TermMapper2/pkg/parser"
9 "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
37 var lists []MappingList
38 if err := yaml.Unmarshal(data, &lists); err != nil {
39 return nil, fmt.Errorf("failed to parse YAML: %w", err)
40 }
41
42 // Validate the configuration
43 for i, list := range lists {
44 if list.ID == "" {
45 return nil, fmt.Errorf("mapping list at index %d is missing an ID", i)
46 }
47 if len(list.Mappings) == 0 {
48 return nil, fmt.Errorf("mapping list '%s' has no mapping rules", list.ID)
49 }
50
51 // Validate each mapping rule
52 for j, rule := range list.Mappings {
53 if rule == "" {
54 return nil, fmt.Errorf("mapping list '%s' rule at index %d is empty", list.ID, j)
55 }
56 }
57 }
58
59 return &Config{Lists: lists}, nil
60}
61
62// ParseMappings parses all mapping rules in a list and returns a slice of parsed rules
63func (list *MappingList) ParseMappings() ([]*parser.MappingResult, error) {
64 // Create a grammar parser with the list's default foundries and layers
65 grammarParser, err := parser.NewGrammarParser("", "")
66 if err != nil {
67 return nil, fmt.Errorf("failed to create grammar parser: %w", err)
68 }
69
70 results := make([]*parser.MappingResult, len(list.Mappings))
71 for i, rule := range list.Mappings {
72 // Parse the mapping rule
73 result, err := grammarParser.ParseMapping(string(rule))
74 if err != nil {
75 return nil, fmt.Errorf("failed to parse mapping rule %d in list '%s': %w", i, list.ID, err)
76 }
77
78 // Apply default foundries and layers if not specified in the rule
79 if list.FoundryA != "" {
80 applyDefaultFoundryAndLayer(result.Upper.Wrap, list.FoundryA, list.LayerA)
81 }
82 if list.FoundryB != "" {
83 applyDefaultFoundryAndLayer(result.Lower.Wrap, list.FoundryB, list.LayerB)
84 }
85
86 results[i] = result
87 }
88
89 return results, nil
90}
91
92// applyDefaultFoundryAndLayer recursively applies default foundry and layer to terms that don't have them specified
93func applyDefaultFoundryAndLayer(node ast.Node, defaultFoundry, defaultLayer string) {
94 switch n := node.(type) {
95 case *ast.Term:
96 if n.Foundry == "" {
97 n.Foundry = defaultFoundry
98 }
99 if n.Layer == "" {
100 n.Layer = defaultLayer
101 }
102 case *ast.TermGroup:
103 for _, op := range n.Operands {
104 applyDefaultFoundryAndLayer(op, defaultFoundry, defaultLayer)
105 }
106 }
107}