Skip to content

Commit 3ed0db0

Browse files
authored
Merge pull request #55 from philandstuff/release-6.0.0
Release 6.0.0
2 parents d93babe + 7054919 commit 3ed0db0

File tree

13 files changed

+2353
-2048
lines changed

13 files changed

+2353
-2048
lines changed

CHANGELOG.md

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,53 @@
11
# Changelog
22

33
## [Unreleased]
4-
[Unreleased]: https://github.com/philandstuff/dhall-golang/compare/v5.0.0...HEAD
4+
[Unreleased]: https://github.com/philandstuff/dhall-golang/compare/v6.0.0...HEAD
5+
6+
## [6.0.0] - 2020-10-04
7+
[6.0.0]: https://github.com/philandstuff/dhall-golang/compare/v5.0.0...v6.0.0
8+
9+
This brings dhall-golang up to version 19.0.0 of the Dhall standard.
10+
11+
### Added
12+
13+
* From Dhall 19.0.0: add `Text/replace` builtin
14+
* Add `dhall-golang json` command
15+
16+
The new `dhall-golang json` command takes some Dhall and outputs it as
17+
JSON, similar to dhall-haskell's `dhall json`. One key difference is
18+
that it also supports Prelude.JSON types. For example, the following
19+
command:
20+
21+
dhall-golang json <<EOF
22+
let JSON = https://prelude.dhall-lang.org/JSON/package.dhall
23+
24+
in { x = JSON.natural 4
25+
, y =
26+
JSON.array
27+
[ JSON.string "foo", JSON.natural 4, JSON.null, JSON.bool True ]
28+
}
29+
EOF
30+
31+
will output:
32+
33+
{
34+
"x": 4,
35+
"y": [
36+
"foo",
37+
4,
38+
null,
39+
true
40+
]
41+
}
42+
43+
### Changed
44+
45+
* From Dhall 19.0.0: implement `with` as first-class expression
46+
* Allow unmarshalling Dhall values into `interface{}`; dhall-golang
47+
will select appropriate Go types for Dhall values when doing this.
548

649
## [5.0.0] - 2020-09-20
7-
[5.0.0]: https://github.com/philandstuff/dhall-golang/compare/v4.1.0...v5.1.0
50+
[5.0.0]: https://github.com/philandstuff/dhall-golang/compare/v4.1.0...v5.0.0
851

952
This brings dhall-golang up to version 18.0.0 of the Dhall standard.
1053

binary/cbor.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ var nameToBuiltin = map[string]Term{
4141

4242
"Double/show": DoubleShow,
4343

44-
"Text/show": TextShow,
44+
"Text/show": TextShow,
45+
"Text/replace": TextReplace,
4546

4647
"List/build": ListBuild,
4748
"List/fold": ListFold,

core/ast.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,12 @@ type (
7979
optional struct{}
8080
none struct{}
8181

82-
textShow struct{}
82+
textShow struct{}
83+
textReplace struct {
84+
needle Value
85+
replacement Value
86+
// haystack Value
87+
}
8388

8489
list struct{}
8590
listBuild struct {
@@ -119,7 +124,8 @@ func (doubleShow) isValue() {}
119124
func (optional) isValue() {}
120125
func (none) isValue() {}
121126

122-
func (textShow) isValue() {}
127+
func (textShow) isValue() {}
128+
func (textReplace) isValue() {}
123129

124130
func (list) isValue() {}
125131
func (listBuild) isValue() {}
@@ -334,6 +340,12 @@ type (
334340
FieldNames []string
335341
}
336342

343+
with struct {
344+
Record Value
345+
Path []string
346+
Value Value
347+
}
348+
337349
// no projectType because it cannot be in a normal form so cannot
338350
// be a Value
339351

@@ -396,6 +408,7 @@ func (RecordLit) isValue() {}
396408
func (toMap) isValue() {}
397409
func (field) isValue() {}
398410
func (project) isValue() {}
411+
func (with) isValue() {}
399412
func (UnionType) isValue() {}
400413
func (unionConstructor) isValue() {}
401414
func (unionVal) isValue() {}

core/builtins.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,36 @@ func (textShow) Call(a0 Value) Value {
234234

235235
func (textShow) ArgType() Value { return Text }
236236

237+
func (r textReplace) Call(a Value) Value {
238+
if r.needle == nil {
239+
return textReplace{needle: a}
240+
}
241+
if r.replacement == nil {
242+
return textReplace{needle: r.needle, replacement: a}
243+
}
244+
needle, ok := r.needle.(PlainTextLit)
245+
if !ok {
246+
return nil
247+
}
248+
if needle == "" {
249+
return a
250+
}
251+
haystack, ok := a.(PlainTextLit)
252+
if !ok {
253+
return nil
254+
}
255+
text := &textValBuilder{}
256+
strs := strings.Split(string(haystack), string(needle))
257+
text.appendStr(strs[0])
258+
for _, s := range strs[1:] {
259+
text.appendValue(r.replacement)
260+
text.appendStr(s)
261+
}
262+
return text.value()
263+
}
264+
265+
func (textReplace) ArgType() Value { return Text }
266+
237267
func (list) Call(x Value) Value { return ListOf{x} }
238268
func (list) ArgType() Value { return Type }
239269

@@ -456,7 +486,8 @@ var (
456486
Optional Callable = optional{}
457487
None Callable = none{}
458488

459-
TextShow Callable = textShow{}
489+
TextShow Callable = textShow{}
490+
TextReplace Callable = textReplace{}
460491

461492
List Callable = list{}
462493
ListBuild Callable = listBuild{}

core/equivalence.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ func alphaEquivalentWith(level int, v1 Value, v2 Value) bool {
2121
integerShow, integerClamp, integerNegate, integerToDouble,
2222
doubleShow,
2323
optional, none,
24-
textShow,
24+
textShow, textReplace,
2525
list, listBuild, listFold, listHead, listIndexed,
2626
listLength, listLast, listReverse,
2727
freeVar, localVar, quoteVar,
@@ -197,6 +197,21 @@ func alphaEquivalentWith(level int, v1 Value, v2 Value) bool {
197197
}
198198
}
199199
return alphaEquivalentWith(level, v1.Record, v2.Record)
200+
case with:
201+
v2, ok := v2.(with)
202+
if !ok {
203+
return false
204+
}
205+
if len(v1.Path) != len(v2.Path) {
206+
return false
207+
}
208+
for i := range v1.Path {
209+
if v1.Path[i] != v2.Path[i] {
210+
return false
211+
}
212+
}
213+
return alphaEquivalentWith(level, v1.Record, v2.Record) &&
214+
alphaEquivalentWith(level, v1.Value, v2.Value)
200215
case UnionType:
201216
v2, ok := v2.(UnionType)
202217
if !ok {

core/eval.go

Lines changed: 27 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"errors"
55
"fmt"
66
"sort"
7-
"strings"
87

98
"github.com/philandstuff/dhall-golang/v5/term"
109
)
@@ -64,6 +63,8 @@ func evalWith(t term.Term, e env) Value {
6463
return Text
6564
case term.TextShow:
6665
return TextShow
66+
case term.TextReplace:
67+
return TextReplace
6768
case term.List:
6869
return List
6970
case term.ListBuild:
@@ -135,41 +136,15 @@ func evalWith(t term.Term, e env) Value {
135136
case term.DoubleLit:
136137
return DoubleLit(t)
137138
case term.TextLit:
138-
var str strings.Builder
139-
var newChunks chunks
139+
text := &textValBuilder{}
140140
for _, chk := range t.Chunks {
141-
str.WriteString(chk.Prefix)
141+
text.appendStr(chk.Prefix)
142142
normExpr := evalWith(chk.Expr, e)
143-
if text, ok := normExpr.(PlainTextLit); ok {
144-
str.WriteString(string(text))
145-
} else if text, ok := normExpr.(interpolatedText); ok {
146-
// first chunk gets the rest of str
147-
str.WriteString(text.Chunks[0].Prefix)
148-
newChunks = append(newChunks,
149-
chunk{Prefix: str.String(), Expr: text.Chunks[0].Expr})
150-
newChunks = append(newChunks,
151-
text.Chunks[1:]...)
152-
str.Reset()
153-
str.WriteString(text.Suffix)
154-
} else {
155-
newChunks = append(newChunks, chunk{Prefix: str.String(), Expr: normExpr})
156-
str.Reset()
157-
}
158-
}
159-
str.WriteString(t.Suffix)
160-
newSuffix := str.String()
161-
162-
// Special case: "${<expr>}" → <expr>
163-
if len(newChunks) == 1 && newChunks[0].Prefix == "" && newSuffix == "" {
164-
return newChunks[0].Expr
165-
}
166-
167-
// Special case: no chunks -> PlainTextLit
168-
if len(newChunks) == 0 {
169-
return PlainTextLit(newSuffix)
143+
text.appendValue(normExpr)
170144
}
145+
text.appendStr(t.Suffix)
171146

172-
return interpolatedText{Chunks: newChunks, Suffix: newSuffix}
147+
return text.value()
173148
case term.BoolLit:
174149
return BoolLit(t)
175150
case term.If:
@@ -615,46 +590,33 @@ func evalWith(t term.Term, e env) Value {
615590
case term.With:
616591
record := evalWith(t.Record, e)
617592
value := evalWith(t.Value, e)
618-
output := record
619-
here := record
620-
var recordLit RecordLit
621-
depth := 0
622-
for _, component := range t.Path {
623-
var ok bool
624-
recordLit, ok = here.(RecordLit)
625-
if !ok {
626-
break
627-
}
628-
here = recordLit[component]
629-
depth = depth + 1
630-
}
631-
desugared := desugarWith(here, t.Path[depth:], value)
632-
if depth == 0 {
633-
return desugared
634-
}
635-
recordLit[t.Path[depth-1]] = desugared
636-
return output
593+
594+
return withRule(record, t.Path, value)
637595
default:
638596
panic(fmt.Sprint("unknown term type", t))
639597
}
640598
}
641599

642-
// desugarWith converts a `r with a.b...c = v` term to the equivalent,
643-
// defined by desugar-with() in the Dhall standard. Note that path
644-
// may be of length 0, in which case value is returned.
645-
func desugarWith(abstractRecord Value, path []string, value Value) Value {
646-
if len(path) == 0 {
647-
return value
600+
// withRule implements evaluation of `with` expressions. It was too
601+
// hard to express using for loops, so I finally did actual functional
602+
// style
603+
//
604+
// withRule may modify its parameters, you have been warned
605+
func withRule(record Value, path []string, value Value) Value {
606+
recordLit, ok := record.(RecordLit)
607+
if !ok {
608+
return with{Record: record, Path: path, Value: value}
609+
}
610+
if len(path) == 1 {
611+
recordLit[path[0]] = value
612+
return recordLit
648613
}
649-
return oper{
650-
OpCode: term.RightBiasedRecordMergeOp,
651-
L: abstractRecord,
652-
R: RecordLit{path[0]: desugarWith(
653-
field{abstractRecord, path[0]},
654-
path[1:],
655-
value,
656-
)},
614+
var subrecord Value = RecordLit{}
615+
if recordLit[path[0]] != nil {
616+
subrecord = recordLit[path[0]]
657617
}
618+
recordLit[path[0]] = withRule(subrecord, path[1:], value)
619+
return recordLit
658620
}
659621

660622
func apply(fn Value, args ...Value) Value {

core/quote.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,17 @@ func quoteWith(ctx quoteContext, shouldAlphaNormalize bool, v Value) term.Term {
8282
return term.None
8383
case textShow:
8484
return term.TextShow
85+
case textReplace:
86+
var result term.Term = term.TextReplace
87+
if v.needle == nil {
88+
return result
89+
}
90+
result = term.App{result, quoteWith(ctx, shouldAlphaNormalize, v.needle)}
91+
if v.replacement == nil {
92+
return result
93+
}
94+
result = term.App{result, quoteWith(ctx, shouldAlphaNormalize, v.replacement)}
95+
return result
8596
case list:
8697
return term.List
8798
case listBuild:
@@ -246,6 +257,12 @@ func quoteWith(ctx quoteContext, shouldAlphaNormalize bool, v Value) term.Term {
246257
Record: quoteWith(ctx, shouldAlphaNormalize, v.Record),
247258
FieldNames: v.FieldNames,
248259
}
260+
case with:
261+
return term.With{
262+
Record: quoteWith(ctx, shouldAlphaNormalize, v.Record),
263+
Path: v.Path,
264+
Value: quoteWith(ctx, shouldAlphaNormalize, v.Value),
265+
}
249266
case UnionType:
250267
result := term.UnionType{}
251268
for k, v := range v {

core/text.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package core
2+
3+
type textValBuilder struct {
4+
Chunks chunks
5+
Suffix string
6+
}
7+
8+
func (t *textValBuilder) appendStr(s string) {
9+
t.Suffix += s
10+
}
11+
12+
func (t *textValBuilder) appendValue(v Value) {
13+
if plainText, ok := v.(PlainTextLit); ok {
14+
t.Suffix += string(plainText)
15+
} else if interpolatedText, ok := v.(interpolatedText); ok {
16+
t.Chunks = append(t.Chunks, chunk{
17+
Prefix: t.Suffix + interpolatedText.Chunks[0].Prefix,
18+
Expr: interpolatedText.Chunks[0].Expr,
19+
})
20+
t.Chunks = append(t.Chunks, interpolatedText.Chunks[1:]...)
21+
t.Suffix = interpolatedText.Suffix
22+
} else {
23+
t.Chunks = append(t.Chunks, chunk{Prefix: t.Suffix, Expr: v})
24+
t.Suffix = ""
25+
}
26+
}
27+
28+
func (t *textValBuilder) value() Value {
29+
if len(t.Chunks) == 0 {
30+
return PlainTextLit(t.Suffix)
31+
} else if len(t.Chunks) == 1 && t.Chunks[0].Prefix == "" && t.Suffix == "" {
32+
// special case: "${x}" -> x
33+
return t.Chunks[0].Expr
34+
}
35+
return interpolatedText(*t)
36+
}

0 commit comments

Comments
 (0)