hylia/parser/parser.go
2024-12-07 13:44:53 -05:00

196 lines
No EOL
5.5 KiB
Go

package parser
import (
"errors"
"fmt"
"io/ioutil"
"strings"
"path/filepath"
)
// THE HYLIA PARSER
type Element struct {
Name string
Content string
FilePath string
NestedElements []Element
Variables map[string]string
}
func ParseFile(filename string) ([]Element, map[string]string, error) {
// Hey, does the file we're trying to parse actually exist?
data, err := ioutil.ReadFile(filename)
if err != nil {
return nil, nil, err
}
elements, variables, err := parseElements(string(data), filename)
if err != nil {
return nil, nil, fmt.Errorf("failed to parse elements: %w", err)
}
importedElements := []Element{}
importedVariables := map[string]string{}
for _, element := range elements {
if element.Name == "import" {
importPath := extractAttributeValue("import", "src")
if importPath == "" {
return nil, nil, fmt.Errorf("Import is missing a source ('src' attribute)")
}
importFullPath := resolvePath(filename, importPath)
childElements, childVariables, err := ParseFile(importFullPath)
if err != nil {
return nil, nil, fmt.Errorf("failed to parse elements: %w", err)
}
importedElements = append(importedElements, childElements...)
for k, v := range childVariables {
importedVariables[k] = v
}
}
}
for k, v := range importedVariables {
if _, exists := variables[k]; !exists {
variables[k] = v
}
}
elements = append(importedElements, elements...)
return elements, variables, nil
}
func resolvePath(basePath, relativePath string) string {
dir := filepath.Dir(basePath)
return filepath.Join(dir, relativePath)
}
func parseElements(content, filename string) ([]Element, map[string]string, error) {
elements := []Element{}
variables := make(map[string]string)
classes := make(map[string]Element)
lines := strings.Split(content, "\n")
var currentElement *Element
var nestedContent strings.Builder
for _, line := range lines {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "<var ") {
name := extractAttributeValue(line, "name")
if name == "" {
return nil, nil, errors.New("Variable is missing a name ('name' attribute)")
}
valueStart := strings.Index(line, ">")
valueEnd := strings.Index(line, "</var>")
if valueStart == -1 || valueEnd == -1 {
return nil, nil, errors.New("invalid variable structure")
}
value := strings.TrimSpace(line[valueStart+1 : valueEnd])
variables[name] = value
} else if strings.HasPrefix(line, "<element") {
if currentElement != nil {
currentElement.Content = nestedContent.String()
parsedNested, nestedVars, _ := parseElements(nestedContent.String(), filename)
currentElement.NestedElements = parsedNested
mergeVariables(variables, nestedVars)
elements = append(elements, *currentElement)
nestedContent.Reset()
}
name := extractAttributeValue(line, "name")
if name == "" {
return nil, nil, errors.New("Element is missing a name ('name' attribute)")
}
currentElement = &Element{Name: name, FilePath: filename}
} else if strings.HasPrefix(line, "</element>") {
if currentElement != nil {
currentElement.Content = nestedContent.String()
parsedNested, nestedVars, _ := parseElements(nestedContent.String(), filename)
currentElement.NestedElements = parsedNested
mergeVariables(variables, nestedVars)
elements = append(elements, *currentElement)
nestedContent.Reset()
currentElement = nil
}
} else if strings.HasPrefix(line, "<class") {
if currentElement != nil {
currentElement.Content = nestedContent.String()
parsedNested, nestedVars, _ := parseElements(nestedContent.String(), filename)
currentElement.NestedElements = parsedNested
mergeVariables(variables, nestedVars)
elements = append(elements, *currentElement)
nestedContent.Reset()
currentElement = nil
}
name := extractAttributeValue(line, "name")
if name == "" {
return nil, nil, errors.New("class is missing a name ('name' attribute)")
}
currentElement = &Element{Name: name, FilePath: filename}
} else if strings.HasPrefix(line, "</class>") {
if currentElement != nil {
currentElement.Content = nestedContent.String()
parsedNested, nestedVars, _ := parseElements(nestedContent.String(), filename)
currentElement.NestedElements = parsedNested
mergeVariables(variables, nestedVars)
if classes == nil {
classes = make(map[string]Element)
}
classes[currentElement.Name] = *currentElement
nestedContent.Reset()
currentElement = nil
}
} else {
if currentElement != nil {
nestedContent.WriteString(line + "\n")
}
}
}
return elements, variables, nil
}
func mergeVariables(target, source map[string]string) {
for key, value := range source {
target[key] = value
}
}
// Extracts an attribute
func extractAttributeValue(tag, attribute string) string {
prefix := fmt.Sprintf(`%s="`, attribute)
start := strings.Index(tag, prefix)
if start == -1 {
return ""
}
start += len(prefix)
end := strings.Index(tag[start:], `"`)
if end == -1 {
return ""
}
return tag[start : start+end]
}
// Extracts an <element>
func extractElementContent(tag, content string) (string, error) {
start := strings.Index(content, tag)
if start == -1 {
return "", errors.New("element tag not found in content")
}
end := strings.Index(content[start:], "</element>")
if end == -1 {
return "", errors.New("missing end tag of element")
}
return content[start+len(tag) : start+end], nil
}