forked from rosa/hakurei
internal/rosa/azalea: ast and parser
This syntax is not final, but acts as a stopgap solution and a proof of concept. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
333
internal/rosa/azalea/azalea.go
Normal file
333
internal/rosa/azalea/azalea.go
Normal file
@@ -0,0 +1,333 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user