aboutsummaryrefslogtreecommitdiff
path: root/internal/ast/processor.go
blob: 89ffbffa9c6e43c38a7777519b5b5fe96e3494a6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package ast

import (
	"fmt"
	"strconv"

	"go.neonxp.ru/conf/internal/parser"
	"go.neonxp.ru/conf/model"
)

func ToDoc(config *Node) (model.Doc, error) {
	if len(config.Children) < 1 {
		return nil, fmt.Errorf("invalid ast tree")
	}

	doc := config.Children[0]

	return processDoc(doc), nil
}

func processDoc(docNode *Node) model.Doc {
	doc := make(model.Doc, len(docNode.Children))
	for i, stmt := range docNode.Children {
		doc[i] = processStmt(stmt)
	}
	return doc
}

func processStmt(stmt *Node) any {
	ident := extractIdent(stmt.Children[0])
	nodeBody := stmt.Children[1]
	switch nodeBody.Symbol {
	case parser.Command:
		return processCommand(ident, nodeBody)
	case parser.Assignment:
		return processAssignment(ident, nodeBody)
	default:
		return nil
	}
}

func processCommand(ident string, command *Node) *model.Command {
	result := &model.Command{
		Name: ident,
	}

	for _, child := range command.Children {
		// Can be arguments OR body OR both
		switch child.Symbol {
		case parser.Values:
			result.Arguments = extractValues(child)
		case parser.Body:
			// Children[0] = '{', Children[1] = Body, Children[2] = '}'
			result.Body = processDoc(child.Children[1])
		}
	}

	return result
}

func processAssignment(ident string, assignment *Node) *model.Assignment {
	result := &model.Assignment{
		Key:   ident,
		Value: extractValues(assignment.Children[1]), // Children[0] = '=', Children[1] = Values, Children[2] = ';'
	}

	return result
}

func extractIdent(word *Node) string {
	return word.Children[0].Source
}

func extractValues(args *Node) []model.Value {
	result := make([]model.Value, len(args.Children))
	for i, child := range args.Children {
		v, err := extractValue(child)
		if err != nil {
			result[i] = err
			continue
		}
		result[i] = v
	}

	return result
}

func extractValue(value *Node) (any, error) {
	v := value.Children[0]
	s := v.Children[0].Source
	switch v.Symbol {
	case parser.Word:
		return model.Word(s), nil
	case parser.String:
		return unquote(s), nil
	case parser.Number:
		d, err := strconv.Atoi(s)
		if err == nil {
			return d, nil
		}
		fl, err := strconv.ParseFloat(s, 32)
		if err == nil {
			return fl, nil
		}
		return nil, fmt.Errorf("invalid number: %s (%s)", v.Source, s)
	case parser.Boolean:
		return s == "true", nil
	default:
		return nil, fmt.Errorf("unknown value type: %s (%s)", v.Symbol, s)
	}
}

func unquote(str string) string {
	if len(str) == 0 {
		return ""
	}
	if str[0:1] == `"` || str[0:1] == "'" || str[0:1] == "`" {
		return str[1 : len(str)-1]
	}
	return str
}