1. New build system
All checks were successful
build-dsl-smoke / Build judge (push) Successful in 12s
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 8s
build-dsl-smoke / release / gcc / linux (push) Successful in 6s
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 13s
build-dsl-smoke / debug-valgrind / gcc / linux (push) Successful in 14s
build-dsl-smoke / release / clang / windows (push) Successful in 16s
build-dsl-smoke / debug / msvc / windows (push) Successful in 18s
build-dsl-smoke / release / msvc / windows (push) Successful in 17s
build-dsl-smoke / SUMMARY (push) Successful in 4s
Release / Build & publish (push) Successful in 48s
All checks were successful
build-dsl-smoke / Build judge (push) Successful in 12s
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 8s
build-dsl-smoke / release / gcc / linux (push) Successful in 6s
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 13s
build-dsl-smoke / debug-valgrind / gcc / linux (push) Successful in 14s
build-dsl-smoke / release / clang / windows (push) Successful in 16s
build-dsl-smoke / debug / msvc / windows (push) Successful in 18s
build-dsl-smoke / release / msvc / windows (push) Successful in 17s
build-dsl-smoke / SUMMARY (push) Successful in 4s
Release / Build & publish (push) Successful in 48s
Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
183
dsl/parser.go
183
dsl/parser.go
@@ -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{}
|
||||
|
||||
@@ -91,7 +153,72 @@ func (p *Parser) parseFile() (*File, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.Build = s.Value
|
||||
if p.peek().Type == TOKEN_LBRACE {
|
||||
bc, err := p.parseBuildBlock(s.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.Builds = append(f.Builds, bc)
|
||||
} else {
|
||||
f.Build = s.Value
|
||||
}
|
||||
|
||||
case "build_defaults":
|
||||
p.advance()
|
||||
bc, err := p.parseBuildBlock("")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if f.BuildDefaults == nil {
|
||||
f.BuildDefaults = &BuildConfig{}
|
||||
}
|
||||
f.BuildDefaults.MergeFrom(bc)
|
||||
|
||||
case "toolchains":
|
||||
p.advance()
|
||||
specs, err := p.parseToolchainsBlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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()
|
||||
@@ -192,11 +319,28 @@ func (p *Parser) parseFile() (*File, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if err := p.validateWeights(f); err != nil {
|
||||
return nil, err
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func validateBuilds(f *File) error {
|
||||
hasLegacy := f.Build != "" || f.BuildLinux != "" || f.BuildWindows != "" || f.BuildDarwin != ""
|
||||
hasStructured := f.BuildDefaults != nil || len(f.Builds) > 0
|
||||
|
||||
if hasLegacy && hasStructured {
|
||||
return fmt.Errorf("cannot mix legacy `build \"shell\"` with structured `build \"name\" { ... }` in the same suite")
|
||||
}
|
||||
|
||||
return f, nil
|
||||
seen := map[string]bool{}
|
||||
for _, b := range f.Builds {
|
||||
if b.Name == "" {
|
||||
return fmt.Errorf("structured build must have a name")
|
||||
}
|
||||
if seen[b.Name] {
|
||||
return fmt.Errorf("duplicate build name %q", b.Name)
|
||||
}
|
||||
seen[b.Name] = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Parser) validateWeights(f *File) error {
|
||||
@@ -635,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)
|
||||
}
|
||||
@@ -715,8 +874,6 @@ func (p *Parser) parseInt() (int, error) {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// parseSize accepts either a TOKEN_SIZE (e.g. "256MB", "1GiB", "512K") or a bare
|
||||
// TOKEN_INT interpreted as bytes. MiB/MB are both 1024² — we use IEC semantics.
|
||||
func (p *Parser) parseSize() (int64, error) {
|
||||
t := p.peek()
|
||||
switch t.Type {
|
||||
|
||||
Reference in New Issue
Block a user