Skip to content

Commit 6d29a9c

Browse files
committed
Enforce typing of records
1 parent e93a7cc commit 6d29a9c

File tree

4 files changed

+81
-37
lines changed

4 files changed

+81
-37
lines changed

eval/eval.go

Lines changed: 56 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -417,27 +417,59 @@ func (c *context) typeRef(x ast.Expr) (ref types.TypeRef, err error) {
417417
return
418418
}
419419

420-
func (c *context) recordExpr(x *ast.RecordExpr) (Record, error) {
421-
record := make(Record)
420+
func (c *context) recordExpr(x *ast.RecordExpr) (r Record, err error) {
421+
// New record.
422+
if x.Rest == nil {
423+
ref := make(types.MapRef, len(x.Entries))
424+
values := make(map[string]Value, len(x.Entries))
425+
426+
for tag, x := range x.Entries {
427+
var val Value
428+
val, err = c.eval(x)
429+
if err != nil {
430+
return
431+
}
422432

423-
if x.Rest != nil {
424-
other, err := c.record(x.Rest)
425-
if err != nil {
426-
return nil, err
433+
ref[tag] = val.Type()
434+
values[tag] = val
427435
}
428-
maps.Copy(record, other)
436+
r = Record{c.reg.Record(ref), values}
437+
return
429438
}
430439

440+
// Record based on another.
441+
var other Record
442+
other, err = c.record(x.Rest)
443+
if err != nil {
444+
return
445+
}
446+
ref := c.reg.GetRecord(other.typ)
447+
values := maps.Clone(other.values)
448+
431449
for tag, x := range x.Entries {
432-
// TODO: ensure types remain the same
433-
val, err := c.eval(x)
450+
var val Value
451+
val, err = c.eval(x)
434452
if err != nil {
435-
return nil, err
453+
return
436454
}
437455

438-
record[tag] = val
456+
typ, ok := ref[tag]
457+
if !ok {
458+
err = c.error(x.Span(),
459+
fmt.Sprintf("cannot set key %s not in the base record", tag))
460+
return
461+
}
462+
if val.Type() != typ {
463+
err = c.error(x.Span(),
464+
fmt.Sprintf("cannot change type of key %s from %s to %s",
465+
tag, c.reg.String(typ), c.reg.String(val.Type())))
466+
return
467+
}
468+
469+
values[tag] = val
439470
}
440-
return record, nil
471+
472+
return Record{other.typ, values}, nil
441473
}
442474

443475
func (c *context) access(x *ast.AccessExpr) (Value, error) {
@@ -446,9 +478,10 @@ func (c *context) access(x *ast.AccessExpr) (Value, error) {
446478
return nil, err
447479
}
448480
key := c.name(&x.Key)
449-
val, ok := r[key]
481+
val, ok := r.values[key]
450482
if !ok {
451-
return nil, c.error(x.Key.Pos, fmt.Sprintf("record is missing property %s", key))
483+
return nil, c.error(x.Key.Pos,
484+
fmt.Sprintf("record %s has no key %s", r, key))
452485
}
453486
return val, nil
454487
}
@@ -537,7 +570,7 @@ func (c *context) createMatchFunc(x ast.MatchFuncExpr) (ScriptFunc, error) {
537570
source: source,
538571
fn: func(a Value) (Value, error) {
539572
for _, alt := range x {
540-
matches, err := Match(c.source, alt.Arg, a)
573+
matches, err := Match(c.source, c.reg, alt.Arg, a)
541574
if err != nil {
542575
if err == ErrNoMatch {
543576
continue
@@ -643,13 +676,15 @@ func (c *context) list(x ast.Node) (l List, err error) {
643676
return
644677
}
645678

646-
func (c *context) record(x ast.Node) (Record, error) {
647-
val, err := c.eval(x)
679+
func (c *context) record(x ast.Node) (r Record, err error) {
680+
var val Value
681+
val, err = c.eval(x)
648682
if err != nil {
649-
return nil, err
683+
return
650684
}
651-
if i, ok := val.(Record); ok {
652-
return i, nil
685+
if r, ok := val.(Record); ok {
686+
return r, nil
653687
}
654-
return nil, c.error(x.Span(), fmt.Sprintf("non-record value %s", val))
688+
err = c.error(x.Span(), fmt.Sprintf("non-record value %s", val))
689+
return
655690
}

eval/eval_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ var failures = []struct {
8989
{`["a"] +< ~be`, `cannot append byte to list text`},
9090
{`1 >+ [~~abcd]`, `cannot prepend int to list bytes`},
9191
{`[1, 1.2]`, `list elements must all be of type int, got float`},
92+
{`{ b = 1 }.a`, `record { b = 1 } has no key a`},
93+
{`{ ..{ a = 2, c = 1 }, a = 1, b = "x"}`, `cannot set key b not in the base record`},
94+
{`{ ..{ a = 2 }, a = "x"}`, `cannot change type of key a from int to text`},
9295
}
9396

9497
func TestEval(t *testing.T) {
@@ -115,7 +118,7 @@ var exp2str = []struct{ source, result string }{
115118
{`rec.a ; rec = { a = 1, b = "x" }`, `1`},
116119
{`{ ..g, a = 2, c = ~FF }
117120
; g = { a = 1, b = "x", c = ~00 }`, `{ a = 2, b = "x", c = ~FF }`},
118-
{`{ ..{ a = 2, c = 1 }, a = 1, b = "x"}`, `{ a = 1, b = "x", c = 1 }`},
121+
{`{ ..{ a = 2, c = 1 }, a = 1 }`, `{ a = 1, c = 1 }`},
119122
{`{ a = 2, b = 3, c = 4 } |>
120123
| { ..x, a = 1, b = 2, c = 3 } -> ()
121124
| { a = 1, b = b, } -> ()

eval/match.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/Victorystick/scrapscript/ast"
99
"github.com/Victorystick/scrapscript/token"
10+
"github.com/Victorystick/scrapscript/types"
1011
)
1112

1213
type bail struct{}
@@ -18,6 +19,7 @@ var (
1819

1920
type matcher struct {
2021
source *token.Source
22+
reg *types.Registry
2123
vars Variables
2224
err error
2325
}
@@ -35,8 +37,8 @@ func (m *matcher) errorf(span token.Span, format string, args ...any) {
3537

3638
// Matches an expression onto val returning new bindings.
3739
// It is a match if err is nil.
38-
func Match(source *token.Source, x ast.Expr, val Value) (vars Variables, err error) {
39-
m := matcher{source, make(Variables), err}
40+
func Match(source *token.Source, reg *types.Registry, x ast.Expr, val Value) (vars Variables, err error) {
41+
m := matcher{source, reg, make(Variables), err}
4042

4143
defer func() {
4244
if pnc := recover(); pnc != nil {
@@ -94,7 +96,7 @@ func (m *matcher) match(x ast.Expr, val Value) {
9496
case *ast.RecordExpr:
9597
if record, ok := val.(Record); ok {
9698
for tag, x := range x.Entries {
97-
val, ok := record[tag]
99+
val, ok := record.values[tag]
98100
if !ok {
99101
// TODO: should point to the key, not the value (x).
100102
m.errorf(x.Span(), "cannot bind to missing key %s", tag)
@@ -105,11 +107,13 @@ func (m *matcher) match(x ast.Expr, val Value) {
105107

106108
// If there's a rest expression; clone the record, clear used keys and recurse.
107109
if x.Rest != nil {
108-
rest := maps.Clone(record)
110+
ref := maps.Clone(m.reg.GetRecord(record.typ))
111+
rest := maps.Clone(record.values)
109112
for tag := range x.Entries {
113+
delete(ref, tag)
110114
delete(rest, tag)
111115
}
112-
m.match(x.Rest, rest)
116+
m.match(x.Rest, Record{m.reg.Record(ref), rest})
113117
}
114118

115119
return

eval/values.go

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ type Bytes []byte
2929
// A named type that may be referenced in e.g. a pick expression.
3030
type Type types.TypeRef
3131

32-
type Record map[string]Value
32+
type Record struct {
33+
typ types.TypeRef
34+
values map[string]Value
35+
}
3336

3437
type List struct {
3538
typ types.TypeRef
@@ -118,11 +121,13 @@ func (t Type) eq(other Value) bool {
118121
}
119122
func (i Record) eq(other Value) bool {
120123
o, ok := other.(Record)
121-
return ok && maps.EqualFunc(i, o, Equals)
124+
return ok && i.typ == o.typ &&
125+
maps.EqualFunc(i.values, o.values, Equals)
122126
}
123127
func (l List) eq(other Value) bool {
124128
o, ok := other.(List)
125-
return ok && slices.EqualFunc(l.elements, o.elements, Equals)
129+
return ok && l.typ == o.typ &&
130+
slices.EqualFunc(l.elements, o.elements, Equals)
126131
}
127132
func (v Variant) eq(other Value) bool {
128133
o, ok := other.(Variant)
@@ -149,10 +154,7 @@ func (t Type) Type() types.TypeRef {
149154
// TODO: Should a type return itself, or a special type?
150155
return types.NeverRef
151156
}
152-
func (i Record) Type() types.TypeRef {
153-
// TODO: implement
154-
return types.NeverRef
155-
}
157+
func (r Record) Type() types.TypeRef { return r.typ }
156158
func (l List) Type() types.TypeRef { return l.typ }
157159
func (v Variant) Type() types.TypeRef { return v.typ }
158160
func (bf BuiltInFunc) Type() types.TypeRef { return bf.typ }
@@ -193,9 +195,9 @@ func (t Type) String() string {
193195
func (r Record) String() string {
194196
var b strings.Builder
195197
b.WriteString("{ ")
196-
comma := len(r) - 1
197-
for _, key := range slices.Sorted(maps.Keys(r)) {
198-
val := r[key]
198+
comma := len(r.values) - 1
199+
for _, key := range slices.Sorted(maps.Keys(r.values)) {
200+
val := r.values[key]
199201
b.WriteString(key)
200202
b.WriteString(" = ")
201203
b.WriteString(val.String())

0 commit comments

Comments
 (0)