Skip to content

Commit

Permalink
Adding => for short lambda form. Bug fixes with incomplete maps. (#172)
Browse files Browse the repository at this point in the history
* Adding => for short form of single param lambda. Fix bug with incomplete input not reported when using grol -c / string eval. review comment tweaks

* review comment

* Update eval/eval.go

Co-authored-by: ccoVeille <[email protected]>

* Update main_test.txtar

Co-authored-by: ccoVeille <[email protected]>

* Added => form of fib, added missing test file from previous PR

* add comment to lexer

* enforce for now the single ident on left of =>

* Update eval/memo_test.go

* Added support for (a,b,..) => lambdas too

* working with () => lambdas and multi too but format is failing, added failing tests, need extra () when printing

* accept {} body and produce it in format so lambda can be called immediatly

* add more test, make lambda work as map value

* use lambda, remove a bunch of return

* a few more use of =>

* convert anon functions to lambda form

* Update token.go

Co-authored-by: ccoVeille <[email protected]>

* Update object/object.go

---------

Co-authored-by: ccoVeille <[email protected]>
  • Loading branch information
ldemailly and ccoVeille authored Aug 22, 2024
1 parent dfb82b3 commit 7765b86
Show file tree
Hide file tree
Showing 21 changed files with 305 additions and 101 deletions.
24 changes: 23 additions & 1 deletion ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,14 +362,36 @@ func (b Builtin) PrettyPrint(out *PrintState) *PrintState {
}

type FunctionLiteral struct {
Base // The 'func' token
Base // The 'func' or '=>' token
Name *Identifier
Parameters []Node // last one might be `..` for variadic.
Body *Statements
Variadic bool
IsLambda bool
}

func (fl FunctionLiteral) lambdaPrint(out *PrintState) *PrintState {
needParen := len(fl.Parameters) != 1
if needParen {
out.Print("(")
}
out.ComaList(fl.Parameters)
if needParen {
out.Print(")")
}
if out.Compact {
out.Print("=>")
} else {
out.Print(" => ")
}
fl.Body.PrettyPrint(out)
return out
}

func (fl FunctionLiteral) PrettyPrint(out *PrintState) *PrintState {
if fl.IsLambda {
return fl.lambdaPrint(out)
}
out.Print(fl.Literal())
if fl.Name != nil {
out.Print(" ")
Expand Down
5 changes: 5 additions & 0 deletions eval/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,11 @@ func (s *State) evalInternal(node any) object.Object { //nolint:funlen,gocyclo /
Env: s.env,
Body: node.Body,
Variadic: node.Variadic,
Lambda: node.IsLambda,
}
if !fn.Lambda && fn.Name == nil {
log.LogVf("Normalizing non-short lambda form to => lambda")
fn.Lambda = true
}
fn.SetCacheKey() // sets cache key
if name != nil {
Expand Down
2 changes: 1 addition & 1 deletion eval/eval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -949,7 +949,7 @@ func TestSmallMapSorting(t *testing.T) {
s := eval.NewState()
res, err := eval.EvalString(s, inp, false)
if err != nil {
t.Errorf("should not have errored: %v", err)
t.Fatalf("should not have errored: %v", err)
}
resStr := res.Inspect()
if resStr != expected {
Expand Down
30 changes: 30 additions & 0 deletions eval/memo_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package eval_test

import (
"testing"

"grol.io/grol/eval"
"grol.io/grol/object"
)

func TestBigArrayNotHashable(t *testing.T) {
// This test is to make sure that big arrays are showing as not hashable (so no crash when used later).
oSlice := object.MakeObjectSlice(object.MaxSmallArray + 1)
for i := range object.MaxSmallArray + 1 {
oSlice = append(oSlice, object.Integer{Value: int64(i)})
}
a := object.NewArray(oSlice)
if _, ok := a.(object.BigArray); !ok {
t.Fatalf("expected big array")
}
if object.Hashable(a) {
t.Fatalf("expected big array to be not hashable")
}
m := object.NewMap()
m = m.Set(object.Integer{Value: 1}, a)
if object.Hashable(m) {
t.Fatalf("expected map with big array inside to be not hashable")
}
c := eval.NewCache()
c.Get("func(){}", []object.Object{a})
}
2 changes: 1 addition & 1 deletion examples/anon.gr
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Define and immediately call factorial
(IIFE: Immediately Invoked Function Expression)
*/
func(n) {
func(n) { // or n => { ... } for short lambda form
if n <= 1 {
return 1
}
Expand Down
5 changes: 2 additions & 3 deletions examples/apply.gr
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@

a = [ 1, 3, 5, 7]

apply = func(f, a) {
func apply(f, a) {
if (len(a)==0) {
return []
}
return [f(first(a))]+apply(f,rest(a))
}

apply(func(x) {2*x}, a)

apply(x => 2*x, a)
// ^^^ [2, 6, 10, 14]

log("should be reached, and now is")
8 changes: 4 additions & 4 deletions examples/apply2.gr
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@

a = [ 1, 3, 5, 7] // or run `gorepl -shared-state *.gr` and you can comment this out (a defined in apply.gr)

apply = func(f, a) {
func apply(f, a) {
// helper function
h = func(f, a, result) {
h = (f, a, result) => {
if (len(a) == 0) {
return result
}
return h(f, rest(a), result + f(first(a)))
return self(f, rest(a), result + f(first(a)))
}
h(f, a, [])
}

apply(func(x) { 2 * x }, a)
apply(x => 2 * x, a)

// ^^^ [2, 6, 10, 14]
60 changes: 22 additions & 38 deletions examples/boxed_text.gr
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,17 @@ box = {

func apply(f, a) {
if (len(a)==0) {
return []
[]
} else {
[f(first(a))]+apply(f,rest(a))
}
return [f(first(a))]+apply(f,rest(a))
}


func boxTextInternal(s, lenFunc) {
lines = split(s, "\n")
// get max
maxLen = max(apply(func(x) {lenFunc(x)}, lines))
maxLen = max(apply(x => lenFunc(x), lines))
// Create the top line
println(box.topLeft + box.horizontal * maxLen + box.topRight)
// Create the middle lines
Expand Down Expand Up @@ -63,33 +64,23 @@ func str(x) {
}

func matrixWidths(matrix) {
return apply(func(row) {
return apply(func(item) {
return width(str(item))
}, row)
}, matrix)
apply(row => apply(item => width(str(item)), row), matrix)
}

func for(n, f, start) {
l=func(i,f, x) { // internal lambda with the index param and intermediate result
(i,f,x) => { // internal lambda with the index param and intermediate result
r = f(i,x)
if (i>=n) {
return r
}
self(i+1, f,r)
}
l(1,f,start)
}(1,f,start)
}

func maxWidths(widths) {
cols = len(widths[0])
colIndices = 0:cols // Generates [0, 1, 2] for 3 columns

return apply(func(colIndex) {
return max(apply(func(row) {
return row[colIndex]
}, widths))
}, colIndices)
apply(colIndex => max(apply( row => row[colIndex], widths)), colIndices)
}

func createRowArray(row, maxWidths) {
Expand All @@ -103,7 +94,7 @@ func createRowArray(row, maxWidths) {
if colIndex < len(row) - 1 {
cellContent = cellContent + box.vertical
}
return rowContent + cellContent
rowContent + cellContent
}, [])
return [box.vertical + join(rowArr) + box.vertical]
}
Expand All @@ -114,9 +105,8 @@ func boxMatrixA(matrix) {

lines = []
// Top line
topLine = box.topLeft + join(apply(func(w) {
return box.horizontal * w
}, maxes), box.topT) + box.topRight
topLine = box.topLeft + join(apply(w => box.horizontal * w,
maxes), box.topT) + box.topRight
lines = lines + [topLine]

// Middle content (rows and separators)
Expand All @@ -126,20 +116,17 @@ func boxMatrixA(matrix) {
lines = lines + rowArr

if rowIndex < len(matrix) - 1 {
separatorLine = box.leftT + join(apply(func(width) {
return box.horizontal * width
}, maxes), box.middleCross) + box.rightT
separatorLine = box.leftT + join(apply( width => box.horizontal * width,
maxes), box.middleCross) + box.rightT
lines = lines + [separatorLine]
}
return lines
lines
}, lines)

// Bottom line
bottomLine = box.bottomLeft + join(apply(func(width) {
return box.horizontal * width
}, maxes), box.bottomT) + box.bottomRight
lines = lines + [bottomLine]
return lines
bottomLine = box.bottomLeft + join(apply(width => box.horizontal * width,
maxes), box.bottomT) + box.bottomRight
lines + [bottomLine]
}

func boxMatrix(matrix) {
Expand All @@ -150,12 +137,11 @@ func boxMatrix(matrix) {

func joinBoxes(boxes) {
numRows = len(boxes[0]) // Assuming all boxes have the same number of lines
return for(numRows, func(rowIndex, combinedLines) {
for(numRows, func(rowIndex, combinedLines) {
rowIndex--
combinedLine = join(apply(func(box) {
return box[rowIndex]
}, boxes), " ") // Add some spacing between boxes
return combinedLines + [combinedLine]
combinedLine = join(apply(box => box[rowIndex],
boxes), " ") // Add some spacing between boxes
combinedLines + [combinedLine]
}, [])
}

Expand Down Expand Up @@ -201,8 +187,6 @@ game = [
[e, O, X]
]]

combinedOutput = joinBoxes(apply(func(board) {
boxMatrixA(board)
}, game))
combinedOutput = joinBoxes(apply(board=>boxMatrixA(board), game))

println(join(combinedOutput, "\n"))
3 changes: 3 additions & 0 deletions examples/fib_short_lambda.gr
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/* Fibonacci using the lambda short form */
fib = x => if (x <= 1) {x} else {self(x - 1) + self(x - 2)}
log("fib(35) =", fib(35))
1 change: 1 addition & 0 deletions examples/for.gr
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// for loop example, functional style
// executes with 1..n passed to the function parameter.
// there is a more useful version in boxed_text.gr

for=func(n, f) {
l=func(i,f) { // internal lambda with the index param
Expand Down
7 changes: 1 addition & 6 deletions examples/large_fact.gr
Original file line number Diff line number Diff line change
@@ -1,7 +1,2 @@
fact=func(n) {
if (n<=1) {
return 1
}
n*fact(n-1)
}
fact = n => if (n<=1) {1} else {n*fact(n-1)}
fact(50.)
8 changes: 2 additions & 6 deletions examples/namespaced_function.gr
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
// A map is basically defining a namespace thanks to the DOT access syntax.
n1 = {
"f1": func() {
println("f1")
},
"f2": func(x) {
x+1
},
"f1": () => println("f1"),
"f2": x => x+1
}

n1.f1()
Expand Down
4 changes: 3 additions & 1 deletion examples/sample.gr
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ unless = macro(cond, iffalse, iftrue) {

unless(10 > 5, print("BUG: not greater\n"), print("macro test: greater\n"))

fact=func(n) { // first class function objects, can also be written as `func fact(n) {` as shorthand
// first class function objects, can also be written as `func fact(n) {` as shorthand
// or fact = (n => ...) as a lambda short form.
fact=func(n) {
log("called fact ", n) // log (timestamped stderr output)
if (n<=1) {
return 1
Expand Down
4 changes: 4 additions & 0 deletions lexer/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ func (l *Lexer) NextToken() *token.Token {
// so we normalize := like it didn't exist.
return token.ConstantTokenChar2(ch, nextChar)
}
if nextChar == '>' && ch == '=' { // => lambda
l.pos++
return token.ConstantTokenChar2(ch, nextChar)
}
return token.ConstantTokenChar(ch)
case '+', '-':
if nextChar == ch {
Expand Down
Loading

0 comments on commit 7765b86

Please sign in to comment.