aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/rosa
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2026-10-30 23:37:34 +0900
committerOphestra <cat@gensokyo.uk>2026-10-30 23:38:34 +0900
commit3c0505619920d8656b332ef0a23295b0d13f2253 (patch)
tree206cfca2f4aa4596bbc34eeab2fed7be6e7b2354 /internal/rosa
parentbe0ef63ad467a7efbbecb92d40d1f73af82a0d84 (diff)
internal/rosa/azalea: record position in func exprHEADmaster
This is included in the evaluation error message. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/rosa')
-rw-r--r--internal/rosa/azalea/azalea.go29
-rw-r--r--internal/rosa/azalea/azalea_test.go61
-rw-r--r--internal/rosa/azalea/evaluate.go22
-rw-r--r--internal/rosa/azalea/evaluate_test.go66
-rw-r--r--internal/rosa/package/hakurei.az2
-rw-r--r--internal/rosa/state.go9
6 files changed, 162 insertions, 27 deletions
diff --git a/internal/rosa/azalea/azalea.go b/internal/rosa/azalea/azalea.go
index 94e54585..d97b6f7d 100644
--- a/internal/rosa/azalea/azalea.go
+++ b/internal/rosa/azalea/azalea.go
@@ -126,10 +126,12 @@ func (v Val) String() string {
// parseVal parses until the end of the [Val].
func (p *parser) parseVal() (v Val) {
- v = append(v, p.parseExpr())
+ expr, _ := p.parseExpr()
+ v = append(v, expr)
for p.tok == '+' {
p.scan()
- v = append(v, p.parseExpr())
+ expr, _ = p.parseExpr()
+ v = append(v, expr)
}
return
}
@@ -194,6 +196,8 @@ type Func struct {
Package bool
// Key-value arguments.
Args []Arg
+ // Position of the character immediately after the function identifier.
+ Pos scanner.Position
}
// fullFuncString controls whether [Func.String] includes the function body.
@@ -233,17 +237,17 @@ func (v Func) String() string {
}
// parseExpr parses the current expression.
-func (p *parser) parseExpr() any {
+func (p *parser) parseExpr() (any, scanner.Position) {
switch p.tok {
case scanner.Int:
v := p.parseInt()
p.scan()
- return v
+ return v, p.s.Pos()
case scanner.String, scanner.RawString:
v := p.parseString()
p.scan()
- return v
+ return v, p.s.Pos()
case scanner.Ident:
var v Func
@@ -252,6 +256,7 @@ func (p *parser) parseExpr() any {
p.scanAs(scanner.Ident)
v.Ident = Ident(p.s.TokenText())
}
+ v.Pos = p.s.Pos()
p.scan()
switch p.tok {
@@ -261,7 +266,7 @@ func (p *parser) parseExpr() any {
switch p.tok {
case '}':
p.scan()
- return v
+ return v, p.s.Pos()
case scanner.Ident:
break
@@ -312,7 +317,7 @@ func (p *parser) parseExpr() any {
}
default:
- return v.Ident
+ return v.Ident, p.s.Pos()
}
case '{':
@@ -322,7 +327,7 @@ func (p *parser) parseExpr() any {
switch p.tok {
case '}':
p.scan()
- return v
+ return v, p.s.Pos()
default:
pair := KV{K: p.parseVal()}
@@ -360,7 +365,7 @@ func (p *parser) parseExpr() any {
panic(ExprError(','))
case ']':
p.scan()
- return v
+ return v, p.s.Pos()
case scanner.EOF:
panic(ExprError(scanner.EOF))
default:
@@ -386,9 +391,10 @@ func (ScanError) Error() string {
}
// Parse parses expressions from r.
-func Parse(r io.Reader) (e []any, err error) {
+func Parse(r io.Reader, name string) (e []any, err error) {
var p parser
p.s.Init(r)
+ p.s.Filename = name
p.s.Mode = scanner.ScanIdents |
scanner.ScanInts |
@@ -423,7 +429,8 @@ func Parse(r io.Reader) (e []any, err error) {
p.scan()
for p.tok != scanner.EOF {
- e = append(e, p.parseExpr())
+ expr, _ := p.parseExpr()
+ e = append(e, expr)
}
if p.s.ErrorCount != 0 {
diff --git a/internal/rosa/azalea/azalea_test.go b/internal/rosa/azalea/azalea_test.go
index 6e184737..7cae6de5 100644
--- a/internal/rosa/azalea/azalea_test.go
+++ b/internal/rosa/azalea/azalea_test.go
@@ -35,9 +35,21 @@ func TestParse(t *testing.T) {
Args: []Arg{{K: []Ident{"v"}, V: Val{
Ident("v"),
String("\xfd"),
- Func{Ident: "p"},
+ Func{Ident: "p", Pos: scanner.Position{
+ Filename: "concat.az",
+ Offset: 18,
+ Line: 1,
+ Column: 19,
+ }},
Int(9),
}}},
+
+ Pos: scanner.Position{
+ Filename: "concat.az",
+ Offset: 1,
+ Line: 1,
+ Column: 2,
+ },
}}, nil},
{"truncated string concat", `f { v = v+; }`, nil,
ExprError(';')},
@@ -92,6 +104,13 @@ func TestParse(t *testing.T) {
{K: []Ident{Ident("checksum")}, V: Val{String("4ASoWbxaA2FW7PAB0zzHDPC5XnNhyaAyjtDPpGzceSLeYnEIXsNYZR3PA_Zu5P0K")}},
{K: []Ident{Ident("compress")}, V: Val{Ident("gzip")}},
},
+
+ Pos: scanner.Position{
+ Filename: "gcc.az",
+ Offset: 165,
+ Line: 7,
+ Column: 20,
+ },
}}},
{K: []Ident{Ident("patches")}, V: Val{Array{
{String("musl-off64_t-loff_t.patch")},
@@ -120,12 +139,29 @@ func TestParse(t *testing.T) {
{K: []Ident{Ident("amd64"), Ident("arm64")}, V: Val{String("''")}},
{K: []Ident{Ident("default")}, V: Val{Ident("unset")}},
},
+
+ Pos: scanner.Position{
+ Filename: "gcc.az",
+ Offset: 890,
+ Line: 32,
+ Column: 30,
+ },
}}},
}}},
{K: []Ident{Ident("make")}, V: Val{Array{
{String("BOOT_CFLAGS='-O2 -g'")},
{
- Func{Ident: Ident("noop"), Args: []Arg{{K: []Ident{Ident("key")}, V: Val{Ident("value")}}}},
+ Func{
+ Ident: Ident("noop"),
+ Args: []Arg{{K: []Ident{Ident("key")}, V: Val{Ident("value")}}},
+
+ Pos: scanner.Position{
+ Filename: "gcc.az",
+ Offset: 995,
+ Line: 39,
+ Column: 8,
+ },
+ },
String("\x00"),
},
{String("bootstrap")},
@@ -133,6 +169,13 @@ func TestParse(t *testing.T) {
{K: []Ident{Ident("skip-check")}, V: Val{Ident("true")}},
},
+
+ Pos: scanner.Position{
+ Filename: "gcc.az",
+ Offset: 627,
+ Line: 22,
+ Column: 13,
+ },
}}},
{K: []Ident{Ident("inputs")}, V: Val{Array{
@@ -143,13 +186,23 @@ func TestParse(t *testing.T) {
{Ident("kernel-headers")},
}}},
},
+
+ Pos: scanner.Position{
+ Filename: "gcc.az",
+ Offset: 11,
+ Line: 1,
+ Column: 12,
+ },
}}, nil},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
- p, err := Parse(strings.NewReader(tc.data))
+ p, err := Parse(
+ strings.NewReader(tc.data),
+ strings.ReplaceAll(tc.name, " ", "_")+".az",
+ )
if !reflect.DeepEqual(p, tc.want) {
t.Errorf("Parse: %#v, want %#v", p, tc.want)
}
@@ -163,7 +216,7 @@ func TestParse(t *testing.T) {
func BenchmarkParse(b *testing.B) {
r := strings.NewReader(sample)
for b.Loop() {
- if _, err := Parse(r); err != nil {
+ if _, err := Parse(r, "./testdata/gcc.az"); err != nil {
b.Fatal(err)
}
diff --git a/internal/rosa/azalea/evaluate.go b/internal/rosa/azalea/evaluate.go
index 9b119100..85e20583 100644
--- a/internal/rosa/azalea/evaluate.go
+++ b/internal/rosa/azalea/evaluate.go
@@ -211,6 +211,18 @@ func exprTextIndent(expr any) string {
return strings.ReplaceAll(ExprText(expr), "\n", "\n\t")
}
+// posString returns the string representation of the position of expr, if
+// available. Otherwise, it returns a hardcoded string.
+func posString(expr any) string {
+ switch v := expr.(type) {
+ case Func:
+ return v.Pos.String()
+
+ default:
+ return "<unknown>"
+ }
+}
+
// EvaluationError is an error and the expression it occurred in.
type EvaluationError struct {
Expr any
@@ -225,10 +237,16 @@ func (e EvaluationError) Unwrap() error { return e.Err }
func (e EvaluationError) Error() string {
v, ok := errors.AsType[EvaluationError](e.Err)
if ok {
- return fmt.Sprintf("%v\n%s", v, ExprText(e.Expr))
+ return fmt.Sprintf(
+ "%v\n%s\n\t%s",
+ v, ExprText(e.Expr), posString(e.Expr),
+ )
}
- return fmt.Sprintf("%v\n\n%s", e.Err, ExprText(e.Expr))
+ return fmt.Sprintf(
+ "%v\n\n%s\n\t%s",
+ e.Err, ExprText(e.Expr), posString(e.Expr),
+ )
}
var (
diff --git a/internal/rosa/azalea/evaluate_test.go b/internal/rosa/azalea/evaluate_test.go
index 049fcb52..edd72432 100644
--- a/internal/rosa/azalea/evaluate_test.go
+++ b/internal/rosa/azalea/evaluate_test.go
@@ -6,6 +6,7 @@ import (
"reflect"
"strings"
"testing"
+ "text/scanner"
"unique"
. "hakurei.app/internal/rosa/azalea"
@@ -81,6 +82,12 @@ func TestEvaluate(t *testing.T) {
Args: []Arg{
{K: []Ident{"v"}, V: Val{Int(9)}},
},
+ Pos: scanner.Position{
+ Filename: "apply_undefined.az",
+ Offset: 1,
+ Line: 1,
+ Column: 2,
+ },
},
Err: UndefinedError("v"),
}},
@@ -96,8 +103,13 @@ func TestEvaluate(t *testing.T) {
}), "\x00", nil},
{"undefined function", `f {}`, nil, "", EvaluationError{
- Expr: Func{Ident: "f"},
- Err: UndefinedError("f"),
+ Expr: Func{Ident: "f", Pos: scanner.Position{
+ Filename: "undefined_function.az",
+ Offset: 1,
+ Line: 1,
+ Column: 2,
+ }},
+ Err: UndefinedError("f"),
}},
{"error wrap deep", `f { v = nonexistent; }`, makeStackCheck(func(
@@ -128,6 +140,12 @@ func TestEvaluate(t *testing.T) {
Args: []Arg{
{K: []Ident{"v"}, V: Val{Ident("nil")}},
},
+ Pos: scanner.Position{
+ Filename: "apply_nil_string.az",
+ Offset: 1,
+ Line: 1,
+ Column: 2,
+ },
},
Err: NilError{Type: reflect.TypeFor[string]()},
}},
@@ -184,6 +202,13 @@ func TestEvaluate(t *testing.T) {
"v",
}, V: Val{Array(nil)}},
},
+
+ Pos: scanner.Position{
+ Filename: "common_inputs.az",
+ Offset: 12,
+ Line: 1,
+ Column: 13,
+ },
},
Err: ErrInvalidSpecial,
}},
@@ -196,6 +221,13 @@ func TestEvaluate(t *testing.T) {
Args: []Arg{
{K: []Ident{"inputs"}, V: Val{Array(nil)}, R: true},
},
+
+ Pos: scanner.Position{
+ Filename: "bound_inputs.az",
+ Offset: 12,
+ Line: 1,
+ Column: 13,
+ },
},
Err: ErrInvalidSpecial,
}},
@@ -208,6 +240,13 @@ func TestEvaluate(t *testing.T) {
Args: []Arg{
{K: []Ident{"runtime"}, V: Val{Array(nil)}, R: true},
},
+
+ Pos: scanner.Position{
+ Filename: "bound_runtime.az",
+ Offset: 12,
+ Line: 1,
+ Column: 13,
+ },
},
Err: ErrInvalidSpecial,
}},
@@ -220,6 +259,13 @@ func TestEvaluate(t *testing.T) {
Args: []Arg{
{K: []Ident{"inputs"}, V: Val{String(""), String("")}},
},
+
+ Pos: scanner.Position{
+ Filename: "concat_inputs.az",
+ Offset: 12,
+ Line: 1,
+ Column: 13,
+ },
},
Err: ErrInvalidSpecial,
}},
@@ -232,6 +278,13 @@ func TestEvaluate(t *testing.T) {
Args: []Arg{
{K: []Ident{"source"}, V: Val{String(""), String("")}},
},
+
+ Pos: scanner.Position{
+ Filename: "concat_source.az",
+ Offset: 12,
+ Line: 1,
+ Column: 13,
+ },
},
Err: ErrInvalidSpecial,
}},
@@ -250,7 +303,10 @@ func TestEvaluate(t *testing.T) {
t.Parallel()
var expr Func
- if e, err := Parse(strings.NewReader(tc.data)); err != nil {
+ if e, err := Parse(
+ strings.NewReader(tc.data),
+ strings.ReplaceAll(tc.name, " ", "_")+".az",
+ ); err != nil {
t.Fatal(err)
} else if len(e) != 1 {
t.Fatalf("got expression %#v", e)
@@ -294,7 +350,7 @@ func TestEvaluateGCC(t *testing.T) {
t.Parallel()
var gcc Func
- if e, err := Parse(strings.NewReader(sample)); err != nil {
+ if e, err := Parse(strings.NewReader(sample), "./testdata/gcc.az"); err != nil {
t.Fatal(err)
} else {
gcc = e[0].(Func)
@@ -421,7 +477,7 @@ func TestEvaluateGCC(t *testing.T) {
func BenchmarkEvaluate(b *testing.B) {
var gcc Func
- if e, err := Parse(strings.NewReader(sample)); err != nil {
+ if e, err := Parse(strings.NewReader(sample), "testdata/gcc.az"); err != nil {
b.Fatal(err)
} else {
gcc = e[0].(Func)
diff --git a/internal/rosa/package/hakurei.az b/internal/rosa/package/hakurei.az
index a9283d96..14faef32 100644
--- a/internal/rosa/package/hakurei.az
+++ b/internal/rosa/package/hakurei.az
@@ -112,7 +112,7 @@ export HAKUREI_ARCH='`+arch+`'
export HAKUREI_SYSROOT='`+prefix+`/`+arch+`'
export PKG_CONFIG_SYSROOT_DIR='`+prefix+`/`+arch+`'
-export PKG_CONFIG_LIBDIR='`+prefix+`/`+arch+``+prefix+`/lib/pkgconfig:`+prefix+`/`+arch+``+prefix+`/share/pkgconfig'
+export PKG_CONFIG_LIBDIR='`+prefix+`/`+arch+prefix+`/lib/pkgconfig:`+prefix+`/`+arch+``+prefix+`/share/pkgconfig'
`;
build = `
NAME=make`;
diff --git a/internal/rosa/state.go b/internal/rosa/state.go
index febde19f..d937d070 100644
--- a/internal/rosa/state.go
+++ b/internal/rosa/state.go
@@ -1309,9 +1309,9 @@ var (
// RegisterAzalea registers all package declarations from r. The backing
// filesystem is directly exposed to azalea pathnames.
-func (s *S) RegisterAzalea(r io.Reader, b fs.FS) error {
+func (s *S) RegisterAzalea(r io.Reader, name string, b fs.FS) error {
var pending []*azalea.Func
- if expressions, err := azalea.Parse(r); err != nil {
+ if expressions, err := azalea.Parse(r, name); err != nil {
return err
} else {
pending = make([]*azalea.Func, len(expressions))
@@ -1354,6 +1354,7 @@ func (s *S) RegisterFS(fsys fs.FS) error {
return err
}
+ const prefix = "internal/rosa/package/"
var r fs.File
for _, dent := range dents {
if dent.IsDir() {
@@ -1371,7 +1372,7 @@ func (s *S) RegisterFS(fsys fs.FS) error {
return err
}
- err = s.RegisterAzalea(r, sub)
+ err = s.RegisterAzalea(r, prefix+dent.Name()+"/package.az", sub)
if _err := r.Close(); err == nil {
err = _err
}
@@ -1391,7 +1392,7 @@ func (s *S) RegisterFS(fsys fs.FS) error {
return err
}
- err = s.RegisterAzalea(r, fsys)
+ err = s.RegisterAzalea(r, prefix+dent.Name(), fsys)
if _err := r.Close(); err == nil {
err = _err
}