// Package azalea implements a proof-of-concept, domain-specific language for // Rosa OS software packaging. package azalea import ( "errors" "io" "strconv" "text/scanner" ) // idents are runes accepted in an identifier. var idents = [...]bool{ '0': true, '1': true, '2': true, '3': true, '4': true, '5': true, '6': true, '7': true, '8': true, '9': true, 'A': true, 'B': true, 'C': true, 'D': true, 'E': true, 'F': true, 'G': true, 'H': true, 'I': true, 'J': true, 'K': true, 'L': true, 'M': true, 'N': true, 'O': true, 'P': true, 'Q': true, 'R': true, 'S': true, 'T': true, 'U': true, 'V': true, 'W': true, 'X': true, 'Y': true, 'Z': true, 'a': true, 'b': true, 'c': true, 'd': true, 'e': true, 'f': true, 'g': true, 'h': true, 'i': true, 'j': true, 'k': true, 'l': true, 'm': true, 'n': true, 'o': true, 'p': true, 'q': true, 'r': true, 's': true, 't': true, 'u': true, 'v': true, 'w': true, 'x': true, 'y': true, 'z': true, '-': true, '_': true, } // TokenError describes an unexpected token. type TokenError [2]rune func (e TokenError) Error() string { return "expected " + scanner.TokenString(e[0]) + ", found " + scanner.TokenString(e[1]) } // ExprError is an unexpected token encountered while parsing an expression. type ExprError rune func (e ExprError) Error() string { return "unexpected token " + scanner.TokenString(rune(e)) } // must1 returns v, or panics if err is not nil. func must1[T any](v T, err error) T { if err != nil { panic(err) } return v } // scanAs advances the scanner for an expected token. func scanAs(s *scanner.Scanner, expects rune) { e := TokenError{expects, s.Scan()} if e[0] != e[1] { panic(e) } } // parseInt parses the current token as a base 10 representation of a 64-bit // signed integer. func parseInt(s *scanner.Scanner) int64 { return must1(strconv.ParseInt(s.TokenText(), 10, 64)) } // A String represents an identifier or string literal. type String struct { Value string Ident bool } // A StringSpec describes a statement evaluating down to a string value. type StringSpec []String // parseString parses the current token as a string. func parseString(s *scanner.Scanner) string { return must1(strconv.Unquote(s.TokenText())) } // appendStringSpec parses from the next token until the end of the StringSpec. // It always advances to the delimiter. func appendStringSpec( s *scanner.Scanner, op bool, p StringSpec, ) (v StringSpec, tok rune, ok bool) { ok = true v = p for { if op { switch tok = s.Scan(); tok { case '+': break default: return } } switch tok = s.Scan(); tok { case scanner.String, scanner.RawString: v = append(v, String{parseString(s), false}) case scanner.Ident: v = append(v, String{s.TokenText(), true}) default: ok = op return } op = true } } // A KV holds a key/value pair. type KV struct { K string V any } // An Arg represents an argument of [Func]. type Arg struct { K string V any R bool } // Func is a function call or package declaration. type Func struct { // Function or package identifier. Ident string // Whether this is a package declaration. Package bool // Key-value arguments. Args []Arg } // parseExpr scans and parses the next expression. A nil return indicates // [scanner.EOF]. func parseExpr(s *scanner.Scanner) (any, bool) { tok := s.Scan() if tok == scanner.EOF { return nil, false } switch tok { case scanner.Int: return parseInt(s), false case scanner.String, scanner.RawString: p, t, ok := appendStringSpec(s, true, StringSpec{ {parseString(s), false}, }) if !ok { panic(TokenError{scanner.String, t}) } return p, true case scanner.Ident: var v Func v.Ident = s.TokenText() if v.Package = v.Ident == "package"; v.Package { scanAs(s, scanner.Ident) v.Ident = s.TokenText() } switch tok = s.Scan(); tok { case '{': for { switch tok = s.Scan(); tok { case '}': return v, false case scanner.Ident: break default: panic(TokenError{scanner.Ident, tok}) } var next bool arg := Arg{K: s.TokenText()} switch tok = s.Scan(); tok { case '=': break case '*': arg.R = true scanAs(s, '=') default: panic(TokenError{'=', tok}) } arg.V, next = parseExpr(s) v.Args = append(v.Args, arg) if !next { scanAs(s, ';') } } case ';': return StringSpec{{v.Ident, true}}, true case '+': p, t, ok := appendStringSpec(s, false, StringSpec{ {v.Ident, true}, }) if !ok { panic(TokenError{scanner.String, t}) } return p, t != scanner.EOF case scanner.EOF: return StringSpec{{v.Ident, true}}, false default: panic(TokenError{'{', tok}) } case '{': var v []KV for { switch tok = s.Scan(); tok { case '}': return v, false case scanner.String: p := KV{K: parseString(s)} switch tok = s.Scan(); tok { case ';': break case ':': var next bool p.V, next = parseExpr(s) if !next { scanAs(s, ';') } break default: panic(ExprError(tok)) } v = append(v, p) default: panic(ExprError(tok)) } } case '[': var v []StringSpec for { p, t, ok := appendStringSpec(s, false, nil) if ok { v = append(v, p) } switch t { case scanner.String, scanner.RawString, scanner.Ident, ',': if !ok { panic(ExprError(']')) } continue case ']': return v, false default: panic(TokenError{']', t}) } } default: panic(ExprError(tok)) } } // ScanError is the error count parsing all expressions. type ScanError int func (ScanError) Error() string { return "aborting due to scanning errors" } // Parse parses expressions from r. func Parse(r io.Reader) (e []any, err error) { var s scanner.Scanner s.Init(r) s.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanStrings | scanner.ScanRawStrings | scanner.ScanComments | scanner.SkipComments s.IsIdentRune = func(ch rune, i int) bool { if i == 0 && ch >= '0' && ch <= '9' { return false } return ch > 0 && ch < rune(len(idents)) && idents[ch] } defer func() { v := recover() if v == nil { return } _err, ok := v.(error) if !ok { panic(v) } if err == nil { err = _err return } err = errors.Join(err, _err) }() for expr, next := parseExpr(&s); expr != nil; expr, next = parseExpr(&s) { if next { return e, ExprError(';') } e = append(e, expr) } if s.ErrorCount != 0 { err = ScanError(s.ErrorCount) } return }