feat: pattern support args and multiple variants; add zed extension for highlight
All checks were successful
build-dsl-smoke / Build judge (push) Successful in 13s
build-dsl-smoke / debug / clang / linux (push) Successful in 6s
build-dsl-smoke / debug / gcc / linux (push) Successful in 8s
build-dsl-smoke / release / clang / linux (push) Successful in 9s
build-dsl-smoke / release / gcc / linux (push) Successful in 7s
build-dsl-smoke / sanitized / clang / linux (push) Successful in 8s
build-dsl-smoke / sanitized / gcc / linux (push) Successful in 7s
build-dsl-smoke / debug / clang / windows (push) Successful in 16s
build-dsl-smoke / debug-valgrind / gcc / linux (push) Successful in 14s
build-dsl-smoke / debug / msvc / windows (push) Successful in 18s
build-dsl-smoke / release / clang / windows (push) Successful in 17s
build-dsl-smoke / release / msvc / windows (push) Successful in 17s
build-dsl-smoke / SUMMARY (push) Successful in 5s

This commit is contained in:
2026-04-11 14:37:43 +03:00
parent dacae83dc6
commit 7f9f6a0a6e
29 changed files with 11429 additions and 94 deletions

View File

@@ -3,14 +3,18 @@ package dsl
import (
"fmt"
"math"
"os"
"path/filepath"
"strconv"
"time"
)
type Parser struct {
tokens []Token
pos int
warns []string
tokens []Token
pos int
warns []string
includeBaseDir string
visited map[string]bool
}
func NewParser(tokens []Token) *Parser {
@@ -72,9 +76,67 @@ func Parse(src string) (*File, []string, error) {
if err != nil {
return nil, parser.Warnings(), err
}
if err := parser.finalize(file); err != nil {
return nil, parser.Warnings(), err
}
return file, parser.Warnings(), nil
}
func ParseFile(path string) (*File, []string, error) {
abs, err := filepath.Abs(path)
if err != nil {
return nil, nil, fmt.Errorf("resolve %q: %w", path, err)
}
visited := map[string]bool{}
parser, file, err := parseFileOnDisk(abs, visited)
if err != nil {
var warns []string
if parser != nil {
warns = parser.Warnings()
}
return nil, warns, err
}
if err := parser.finalize(file); err != nil {
return nil, parser.Warnings(), err
}
return file, parser.Warnings(), nil
}
func parseFileOnDisk(absPath string, visited map[string]bool) (*Parser, *File, error) {
if visited[absPath] {
return nil, nil, fmt.Errorf("circular include: %s", absPath)
}
visited[absPath] = true
src, err := os.ReadFile(absPath)
if err != nil {
return nil, nil, fmt.Errorf("read %s: %w", absPath, err)
}
tokens, err := NewLexer(string(src)).Tokenize()
if err != nil {
return nil, nil, fmt.Errorf("%s: %w", absPath, err)
}
p := NewParser(tokens)
p.includeBaseDir = filepath.Dir(absPath)
p.visited = visited
file, err := p.parseFile()
if err != nil {
return p, nil, fmt.Errorf("%s: %w", absPath, err)
}
return p, file, nil
}
func (p *Parser) finalize(f *File) error {
if err := p.validateWeights(f); err != nil {
return err
}
if err := validateBuilds(f); err != nil {
return err
}
return nil
}
func (p *Parser) parseFile() (*File, error) {
f := &File{}
@@ -107,7 +169,10 @@ func (p *Parser) parseFile() (*File, error) {
if err != nil {
return nil, err
}
f.BuildDefaults = bc
if f.BuildDefaults == nil {
f.BuildDefaults = &BuildConfig{}
}
f.BuildDefaults.MergeFrom(bc)
case "toolchains":
p.advance()
@@ -115,7 +180,45 @@ func (p *Parser) parseFile() (*File, error) {
if err != nil {
return nil, err
}
f.Toolchains = specs
existing := map[string]bool{}
for _, tc := range f.Toolchains {
existing[tc.Name] = true
}
for _, tc := range specs {
if existing[tc.Name] {
return nil, fmt.Errorf("duplicate toolchain %q", tc.Name)
}
existing[tc.Name] = true
f.Toolchains = append(f.Toolchains, tc)
}
case "include":
p.advance()
s, err := p.expect(TOKEN_STRING)
if err != nil {
return nil, err
}
if p.includeBaseDir == "" {
return nil, fmt.Errorf("%d:%d: `include` requires file context (use ParseFile, not Parse)", s.Line, s.Col)
}
target := s.Value
if !filepath.IsAbs(target) {
target = filepath.Join(p.includeBaseDir, target)
}
abs, err := filepath.Abs(target)
if err != nil {
return nil, fmt.Errorf("%d:%d: resolve include %q: %w", s.Line, s.Col, s.Value, err)
}
childParser, child, err := parseFileOnDisk(abs, p.visited)
if err != nil {
return nil, fmt.Errorf("%d:%d: include %q: %w", s.Line, s.Col, s.Value, err)
}
for _, w := range childParser.Warnings() {
p.warn(w)
}
if err := mergeFiles(f, child); err != nil {
return nil, fmt.Errorf("%d:%d: include %q: %w", s.Line, s.Col, s.Value, err)
}
case "build_linux":
p.advance()
@@ -216,13 +319,6 @@ func (p *Parser) parseFile() (*File, error) {
}
}
if err := p.validateWeights(f); err != nil {
return nil, err
}
if err := validateBuilds(f); err != nil {
return nil, err
}
return f, nil
}
@@ -683,25 +779,40 @@ func (p *Parser) parsePattern() (*Pattern, error) {
if _, err := p.expect(TOKEN_ASSIGN); err != nil {
return nil, err
}
val, err := p.expect(TOKEN_STRING)
if err != nil {
return nil, err
}
switch t.Value {
case "input":
val, err := p.expect(TOKEN_STRING)
if err != nil {
return nil, err
}
if pat.DirsGlob != "" {
pat.InputFile = val.Value
} else {
pat.InputGlob = val.Value
}
case "output":
val, err := p.expect(TOKEN_STRING)
if err != nil {
return nil, err
}
if pat.DirsGlob != "" {
pat.OutputFile = val.Value
} else {
pat.OutputGlob = val.Value
}
case "dirs":
val, err := p.expect(TOKEN_STRING)
if err != nil {
return nil, err
}
pat.DirsGlob = val.Value
case "args":
xs, err := p.parseStringList()
if err != nil {
return nil, err
}
pat.Args = xs
default:
return nil, fmt.Errorf("%d:%d: unknown pattern field %q", t.Line, t.Col, t.Value)
}