This commit is contained in:
Umur Ozkul 2022-05-19 20:25:18 +02:00
parent 57c2fba791
commit a51e4be528
9 changed files with 231 additions and 81 deletions

View File

@ -16,7 +16,11 @@ testMacro([], exampleExpression, "Ok(1)")
describe("bindStatement", () => {
// A statement is bound by the bindings created by the previous statement
testMacro([], eBindStatement(eBindings([]), exampleStatementY), "Ok((:$setBindings {} :y 1) context: {})")
testMacro(
[],
eBindStatement(eBindings([]), exampleStatementY),
"Ok((:$setBindings {} :y 1) context: {})",
)
// Then it answers the bindings for the next statement when reduced
testMacroEval([], eBindStatement(eBindings([]), exampleStatementY), "Ok({y: 1})")
// Now let's feed a binding to see what happens
@ -31,13 +35,17 @@ describe("bindStatement", () => {
testMacro(
[("z", EvNumber(99.))],
eBindStatementDefault(exampleStatementY),
"Ok((:$setBindings {z: 99} :y 1) context: {z: 99})",
"Ok((:$setBindings {z: 99} :y 1) context: {z: 99})",
)
})
describe("bindExpression", () => {
// x is simply bound in the expression
testMacro([], eBindExpression(eBindings([("x", EvNumber(2.))]), eSymbol("x")), "Ok(2 context: {x: 2})")
testMacro(
[],
eBindExpression(eBindings([("x", EvNumber(2.))]), eSymbol("x")),
"Ok(2 context: {x: 2})",
)
// When an let statement is the end expression then bindings are returned
testMacro(
[],

View File

@ -60,7 +60,10 @@ describe("Peggy parse", () => {
testParse("1 * 2 - 3 / 4", "{(::subtract (::multiply 1 2) (::divide 3 4))}")
testParse("1 * 2 - 3 ./ 4", "{(::subtract (::multiply 1 2) (::dotDivide 3 4))}")
testParse("1 * 2 - 3 * 4^5", "{(::subtract (::multiply 1 2) (::multiply 3 (::pow 4 5)))}")
testParse("1 * 2 - 3 * 4^5^6", "{(::subtract (::multiply 1 2) (::multiply 3 (::pow (::pow 4 5) 6)))}")
testParse(
"1 * 2 - 3 * 4^5^6",
"{(::subtract (::multiply 1 2) (::multiply 3 (::pow (::pow 4 5) 6)))}",
)
testParse("1 * -a[-2]", "{(::multiply 1 (::unaryMinus (::$atIndex :a (::unaryMinus 2))))}")
})
@ -93,7 +96,7 @@ describe("Peggy parse", () => {
testParse("record.property", "{(::$atIndex :record 'property')}")
})
describe("post operators", ()=>{
describe("post operators", () => {
//function call, array and record access are post operators with higher priority than unary operators
testParse("a==!b(1)", "{(::equal :a (::not (::b 1)))}")
testParse("a==!b[1]", "{(::equal :a (::not (::$atIndex :b 1)))}")
@ -105,11 +108,14 @@ describe("Peggy parse", () => {
testParse("1 // This is a line comment", "{1}")
testParse("1 /* This is a multi line comment */", "{1}")
testParse("/* This is a multi line comment */ 1", "{1}")
testParse(`
testParse(
`
/* This is
a multi line
comment */
1`, "{1}")
1`,
"{1}",
)
})
describe("ternary operator", () => {
@ -126,7 +132,7 @@ describe("Peggy parse", () => {
) //nested if
})
describe("logical", ()=> {
describe("logical", () => {
testParse("true || false", "{(::or true false)}")
testParse("true && false", "{(::and true false)}")
testParse("a && b || c", "{(::and :a (::or :b :c))}")
@ -144,9 +150,18 @@ describe("Peggy parse", () => {
testParse("a && b<c.i || d", "{(::and :a (::or (::smaller :b (::$atIndex :c 'i')) :d))}")
testParse("a && b<c(i) || d", "{(::and :a (::or (::smaller :b (::c :i)) :d))}")
testParse("a && b<1+2 || d", "{(::and :a (::or (::smaller :b (::add 1 2)) :d))}")
testParse("a && b<1+2*3 || d", "{(::and :a (::or (::smaller :b (::add 1 (::multiply 2 3))) :d))}")
testParse("a && b<1+2*-3+4 || d", "{(::and :a (::or (::smaller :b (::add (::add 1 (::multiply 2 (::unaryMinus 3))) 4)) :d))}")
testParse("a && b<1+2*3 || d ? true : false", "{(::$$ternary (::and :a (::or (::smaller :b (::add 1 (::multiply 2 3))) :d)) true false)}")
testParse(
"a && b<1+2*3 || d",
"{(::and :a (::or (::smaller :b (::add 1 (::multiply 2 3))) :d))}",
)
testParse(
"a && b<1+2*-3+4 || d",
"{(::and :a (::or (::smaller :b (::add (::add 1 (::multiply 2 (::unaryMinus 3))) 4)) :d))}",
)
testParse(
"a && b<1+2*3 || d ? true : false",
"{(::$$ternary (::and :a (::or (::smaller :b (::add 1 (::multiply 2 3))) :d)) true false)}",
)
})
describe("pipe", () => {
@ -194,67 +209,111 @@ describe("Peggy parse", () => {
})
describe("Using lambda as value", () => {
testParse("myadd(x,y)=x+y; z=myadd; z", "{:myadd = {|:x,:y| {(::add :x :y)}}; :z = {:myadd}; :z}")
testParse("myadd(x,y)=x+y; z=[myadd]; z", "{:myadd = {|:x,:y| {(::add :x :y)}}; :z = {(::$constructArray (:myadd))}; :z}")
testParse("myaddd(x,y)=x+y; z={x: myaddd}; z", "{:myaddd = {|:x,:y| {(::add :x :y)}}; :z = {(::$constructRecord ('x': :myaddd))}; :z}")
testParse(
"myadd(x,y)=x+y; z=myadd; z",
"{:myadd = {|:x,:y| {(::add :x :y)}}; :z = {:myadd}; :z}",
)
testParse(
"myadd(x,y)=x+y; z=[myadd]; z",
"{:myadd = {|:x,:y| {(::add :x :y)}}; :z = {(::$constructArray (:myadd))}; :z}",
)
testParse(
"myaddd(x,y)=x+y; z={x: myaddd}; z",
"{:myaddd = {|:x,:y| {(::add :x :y)}}; :z = {(::$constructRecord ('x': :myaddd))}; :z}",
)
testParse("f({|x| x+1})", "{(::f {|:x| {(::add :x 1)}})}")
testParse("map(arr, {|x| x+1})", "{(::map :arr {|:x| {(::add :x 1)}})}")
testParse("map([1,2,3], {|x| x+1})", "{(::map (::$constructArray (1 2 3)) {|:x| {(::add :x 1)}})}")
testParse("[1,2,3]->map({|x| x+1})", "{(::map (::$constructArray (1 2 3)) {|:x| {(::add :x 1)}})}")
testParse(
"map([1,2,3], {|x| x+1})",
"{(::map (::$constructArray (1 2 3)) {|:x| {(::add :x 1)}})}",
)
testParse(
"[1,2,3]->map({|x| x+1})",
"{(::map (::$constructArray (1 2 3)) {|:x| {(::add :x 1)}})}",
)
})
})
describe("parsing new line", ()=>{
testParse(`
describe("parsing new line", () => {
testParse(
`
a +
b`, "{(::add :a :b)}")
testParse(`
b`,
"{(::add :a :b)}",
)
testParse(
`
x=
1`, "{:x = {1}}")
testParse(`
1`,
"{:x = {1}}",
)
testParse(
`
x=1
y=2`, "{:x = {1}; :y = {2}}")
testParse(`
y=2`,
"{:x = {1}; :y = {2}}",
)
testParse(
`
x={
y=2;
y }
x`, "{:x = {:y = {2}; :y}; :x}")
testParse(`
x`,
"{:x = {:y = {2}; :y}; :x}",
)
testParse(
`
x={
y=2
y }
x`, "{:x = {:y = {2}; :y}; :x}")
testParse(`
x`,
"{:x = {:y = {2}; :y}; :x}",
)
testParse(
`
x={
y=2
y
}
x`, "{:x = {:y = {2}; :y}; :x}")
testParse(`
x`,
"{:x = {:y = {2}; :y}; :x}",
)
testParse(
`
x=1
y=2
z=3
`, "{:x = {1}; :y = {2}; :z = {3}}")
testParse(`
`,
"{:x = {1}; :y = {2}; :z = {3}}",
)
testParse(
`
f={
x=1
y=2
z=3
x+y+z
}
`, "{:f = {:x = {1}; :y = {2}; :z = {3}; (::add (::add :x :y) :z)}}")
testParse(`
`,
"{:f = {:x = {1}; :y = {2}; :z = {3}; (::add (::add :x :y) :z)}}",
)
testParse(
`
a |>
b |>
c |>
d
`, "{(::d (::c (::b :a)))}")
testParse(`
`,
"{(::d (::c (::b :a)))}",
)
testParse(
`
a |>
b |>
c |>
d +
e
`, "{(::add (::d (::c (::b :a))) :e)}")
`,
"{(::add (::d (::c (::b :a))) :e)}",
)
})

View File

@ -9,27 +9,32 @@ open Jest
open Expect
let expectToExpressionToBe = (expr, answer, ~v="_", ()) => {
let rExpr = Parse.parse(expr)
->Result.map(ToExpression.fromNode)
let rExpr = Parse.parse(expr)->Result.map(ToExpression.fromNode)
let a1 = rExpr->ExpressionT.toStringResultOkless
if (v=="_") {
if v == "_" {
a1->expect->toBe(answer)
} else {
let a2 = rExpr->Result.flatMap(
expr => Expression.reduceExpression(expr, Belt.Map.String.empty, ExpressionValue.defaultEnvironment)
)->ExpressionValue.toStringResultOkless
let a2 =
rExpr
->Result.flatMap(expr =>
Expression.reduceExpression(expr, Belt.Map.String.empty, ExpressionValue.defaultEnvironment)
)
->ExpressionValue.toStringResultOkless
(a1, a2)->expect->toEqual((answer, v))
}
}
let testToExpression = (expr, answer, ~v="_", ()) => test(expr, () => expectToExpressionToBe(expr, answer, ~v=v, ()))
let testToExpression = (expr, answer, ~v="_", ()) =>
test(expr, () => expectToExpressionToBe(expr, answer, ~v, ()))
module MySkip = {
let testToExpression = (expr, answer, ~v="_", ()) => Skip.test(expr, () => expectToExpressionToBe(expr, answer, ~v=v, ()))
let testToExpression = (expr, answer, ~v="_", ()) =>
Skip.test(expr, () => expectToExpressionToBe(expr, answer, ~v, ()))
}
module MyOnly = {
let testToExpression = (expr, answer, ~v="_", ()) => Only.test(expr, () => expectToExpressionToBe(expr, answer, ~v=v, ()))
let testToExpression = (expr, answer, ~v="_", ()) =>
Only.test(expr, () => expectToExpressionToBe(expr, answer, ~v, ()))
}
describe("Peggy to Expression", () => {
@ -53,7 +58,12 @@ describe("Peggy to Expression", () => {
describe("multi-line", () => {
testToExpression("x=1; 2", "(:$$block (:$let :x (:$$block 1)) 2)", ~v="2", ())
testToExpression("x=1; y=2", "(:$$block (:$let :x (:$$block 1)) (:$let :y (:$$block 2)))", ~v="{x: 1,y: 2}", ())
testToExpression(
"x=1; y=2",
"(:$$block (:$let :x (:$$block 1)) (:$let :y (:$$block 2)))",
~v="{x: 1,y: 2}",
(),
)
})
describe("variables", () => {
@ -63,22 +73,51 @@ describe("Peggy to Expression", () => {
})
describe("functions", () => {
testToExpression("identity(x) = x", "(:$$block (:$let :identity (:$$lambda [x] (:$$block :x))))", ~v="{identity: lambda(x=>internal code)}", ()) // Function definitions become lambda assignments
testToExpression(
"identity(x) = x",
"(:$$block (:$let :identity (:$$lambda [x] (:$$block :x))))",
~v="{identity: lambda(x=>internal code)}",
(),
) // Function definitions become lambda assignments
testToExpression("identity(x)", "(:$$block (:identity :x))", ()) // Note value returns error properly
})
describe("arrays", () => {
testToExpression("[]", "(:$$block (:$constructArray ()))", ~v="[]", ())
testToExpression("[0, 1, 2]", "(:$$block (:$constructArray (0 1 2)))", ~v="[0,1,2]", ())
testToExpression("['hello', 'world']", "(:$$block (:$constructArray ('hello' 'world')))", ~v="['hello','world']", ())
testToExpression("([0,1,2])[1]", "(:$$block (:$atIndex (:$constructArray (0 1 2)) 1))", ~v="1", ())
testToExpression(
"['hello', 'world']",
"(:$$block (:$constructArray ('hello' 'world')))",
~v="['hello','world']",
(),
)
testToExpression(
"([0,1,2])[1]",
"(:$$block (:$atIndex (:$constructArray (0 1 2)) 1))",
~v="1",
(),
)
})
describe("records", () => {
testToExpression("{a: 1, b: 2}", "(:$$block (:$constructRecord (('a' 1) ('b' 2))))", ~v="{a: 1,b: 2}", ())
testToExpression("{1+0: 1, 2+0: 2}", "(:$$block (:$constructRecord (((:add 1 0) 1) ((:add 2 0) 2))))", ()) // key can be any expression
testToExpression(
"{a: 1, b: 2}",
"(:$$block (:$constructRecord (('a' 1) ('b' 2))))",
~v="{a: 1,b: 2}",
(),
)
testToExpression(
"{1+0: 1, 2+0: 2}",
"(:$$block (:$constructRecord (((:add 1 0) 1) ((:add 2 0) 2))))",
(),
) // key can be any expression
testToExpression("record.property", "(:$$block (:$atIndex :record 'property'))", ())
testToExpression("record={property: 1}; record.property", "(:$$block (:$let :record (:$$block (:$constructRecord (('property' 1))))) (:$atIndex :record 'property'))", ~v="1", ())
testToExpression(
"record={property: 1}; record.property",
"(:$$block (:$let :record (:$$block (:$constructRecord (('property' 1))))) (:$atIndex :record 'property'))",
~v="1",
(),
)
})
describe("comments", () => {
@ -91,16 +130,35 @@ describe("Peggy to Expression", () => {
describe("ternary operator", () => {
testToExpression("true ? 1 : 0", "(:$$block (:$$ternary true 1 0))", ~v="1", ())
testToExpression("false ? 1 : 0", "(:$$block (:$$ternary false 1 0))", ~v="0", ())
testToExpression("true ? 1 : false ? 2 : 0", "(:$$block (:$$ternary true 1 (:$$ternary false 2 0)))", ~v="1", ()) // nested ternary
testToExpression("false ? 1 : false ? 2 : 0", "(:$$block (:$$ternary false 1 (:$$ternary false 2 0)))", ~v="0", ()) // nested ternary
testToExpression(
"true ? 1 : false ? 2 : 0",
"(:$$block (:$$ternary true 1 (:$$ternary false 2 0)))",
~v="1",
(),
) // nested ternary
testToExpression(
"false ? 1 : false ? 2 : 0",
"(:$$block (:$$ternary false 1 (:$$ternary false 2 0)))",
~v="0",
(),
) // nested ternary
})
describe("if then else", () => {
testToExpression("if true then 2 else 3", "(:$$block (:$$ternary true (:$$block 2) (:$$block 3)))", ())
testToExpression("if true then {2} else {3}", "(:$$block (:$$ternary true (:$$block 2) (:$$block 3)))", ())
testToExpression(
"if true then 2 else 3",
"(:$$block (:$$ternary true (:$$block 2) (:$$block 3)))",
(),
)
testToExpression(
"if true then {2} else {3}",
"(:$$block (:$$ternary true (:$$block 2) (:$$block 3)))",
(),
)
testToExpression(
"if false then {2} else if false then {4} else {5}",
"(:$$block (:$$ternary false (:$$block 2) (:$$ternary false (:$$block 4) (:$$block 5))))", ()
"(:$$block (:$$ternary false (:$$block 2) (:$$ternary false (:$$block 4) (:$$block 5))))",
(),
) //nested if
})
@ -119,15 +177,38 @@ describe("Peggy to Expression", () => {
describe("inner block", () => {
// inner blocks are 0 argument lambdas. They can be used whenever a value is required.
// Like lambdas they have a local scope.
testToExpression("y=99; x={y=1; y}", "(:$$block (:$let :y (:$$block 99)) (:$let :x (:$$block (:$let :y (:$$block 1)) :y)))", ~v="{x: 1,y: 99}", ())
testToExpression(
"y=99; x={y=1; y}",
"(:$$block (:$let :y (:$$block 99)) (:$let :x (:$$block (:$let :y (:$$block 1)) :y)))",
~v="{x: 1,y: 99}",
(),
)
})
describe("lambda", () => {
testToExpression("{|x| x}", "(:$$block (:$$lambda [x] (:$$block :x)))", ~v="lambda(x=>internal code)", ())
testToExpression("f={|x| x}", "(:$$block (:$let :f (:$$block (:$$lambda [x] (:$$block :x)))))", ~v="{f: lambda(x=>internal code)}", ())
testToExpression("f(x)=x","(:$$block (:$let :f (:$$lambda [x] (:$$block :x))))", ~v="{f: lambda(x=>internal code)}", ()) // Function definitions are lambda assignments
testToExpression("f(x)=x ? 1 : 0", "(:$$block (:$let :f (:$$lambda [x] (:$$block (:$$ternary :x 1 0)))))", ~v="{f: lambda(x=>internal code)}", ())
testToExpression(
"{|x| x}",
"(:$$block (:$$lambda [x] (:$$block :x)))",
~v="lambda(x=>internal code)",
(),
)
testToExpression(
"f={|x| x}",
"(:$$block (:$let :f (:$$block (:$$lambda [x] (:$$block :x)))))",
~v="{f: lambda(x=>internal code)}",
(),
)
testToExpression(
"f(x)=x",
"(:$$block (:$let :f (:$$lambda [x] (:$$block :x))))",
~v="{f: lambda(x=>internal code)}",
(),
) // Function definitions are lambda assignments
testToExpression(
"f(x)=x ? 1 : 0",
"(:$$block (:$let :f (:$$lambda [x] (:$$block (:$$ternary :x 1 0)))))",
~v="{f: lambda(x=>internal code)}",
(),
)
})
})

View File

@ -9,4 +9,3 @@ describe("Eval with Bindings", () => {
testEvalBindingsToBe("y = x+1; y", list{("x", ExpressionValue.EvNumber(1.))}, "Ok(2)")
testEvalBindingsToBe("y = x+1", list{("x", ExpressionValue.EvNumber(1.))}, "Ok({x: 1,y: 2})")
})

View File

@ -68,21 +68,23 @@ describe("function tricks", () => {
testEvalToBe("y=2;g(x)=inspect(y)+1", "Ok({g: lambda(x=>internal code),y: 2})")
MySkip.testEvalToBe("f(x) = x(x); f(f)", "????") // TODO: Infinite loop. Any solution? Catching proper exception or timeout?
MySkip.testEvalToBe("f(x, x)=x+x; f(1,2)", "????") // TODO: Duplicate parameters
testEvalToBe("myadd(x,y)=x+y; z=myadd; z", "Ok(lambda(x,y=>internal code))")
testEvalToBe("myadd(x,y)=x+y; z=myadd; z(1, 1)", "Ok(2)")
testEvalToBe("myadd(x,y)=x+y; z=myadd; z", "Ok(lambda(x,y=>internal code))")
testEvalToBe("myadd(x,y)=x+y; z=myadd; z(1, 1)", "Ok(2)")
})
describe("lambda in structures", () => {
testEvalToBe("myadd(x,y)=x+y; z=[myadd]", "Ok({myadd: lambda(x,y=>internal code),z: [lambda(x,y=>internal code)]})")
testEvalToBe("myadd(x,y)=x+y; z=[myadd]; z[0]", "Ok(lambda(x,y=>internal code))")
testEvalToBe("myadd(x,y)=x+y; z=[myadd]; z[0](3,2)", "Ok(5)")
testEvalToBe("myaddd(x,y)=x+y; z={x: myaddd}; z", "Ok({x: lambda(x,y=>internal code)})")
testEvalToBe("myaddd(x,y)=x+y; z={x: myaddd}; z.x", "Ok(lambda(x,y=>internal code))")
testEvalToBe("myaddd(x,y)=x+y; z={x: myaddd}; z.x(3,2)", "Ok(5)")
testEvalToBe(
"myadd(x,y)=x+y; z=[myadd]",
"Ok({myadd: lambda(x,y=>internal code),z: [lambda(x,y=>internal code)]})",
)
testEvalToBe("myadd(x,y)=x+y; z=[myadd]; z[0]", "Ok(lambda(x,y=>internal code))")
testEvalToBe("myadd(x,y)=x+y; z=[myadd]; z[0](3,2)", "Ok(5)")
testEvalToBe("myaddd(x,y)=x+y; z={x: myaddd}; z", "Ok({x: lambda(x,y=>internal code)})")
testEvalToBe("myaddd(x,y)=x+y; z={x: myaddd}; z.x", "Ok(lambda(x,y=>internal code))")
testEvalToBe("myaddd(x,y)=x+y; z={x: myaddd}; z.x(3,2)", "Ok(5)")
})
describe("ternary and bindings", () => {
testEvalToBe("f(x)=x ? 1 : 0; f(true)", "Ok(1)")
testEvalToBe("f(x)=x>2 ? 1 : 0; f(3)", "Ok(1)")
})

View File

@ -26,7 +26,7 @@ describe("eval", () => {
test("define", () => expectEvalToBe("{a: 1, b: 2}", "Ok({a: 1,b: 2})"))
test("index", () => expectEvalToBe("r = {a: 1}; r.a", "Ok(1)"))
test("index", () => expectEvalToBe("r = {a: 1}; r.b", "Error(Record property not found: b)"))
testEvalError("{a: 1}.b") // invalid syntax
testEvalError("{a: 1}.b") // invalid syntax
})
describe("multi-line", () => {

View File

@ -21,7 +21,9 @@ type t = expression
Converts a Squigle code to expression
*/
let parse = (peggyCode: string): result<t, errorValue> =>
peggyCode->Reducer_Peggy_Parse.parse->Result.map(node => Reducer_Peggy_ToExpression.fromNode(node))
peggyCode
->Reducer_Peggy_Parse.parse
->Result.map(node => Reducer_Peggy_ToExpression.fromNode(node))
/*
Recursively evaluate/reduce the expression (Lisp AST)
@ -72,7 +74,7 @@ and reduceExpressionList = (
and reduceValueList = (valueList: list<expressionValue>, environment): result<
expressionValue,
'e,
> =>
> =>
switch valueList {
| list{EvCall(fName), ...args} =>
(fName, args->Belt.List.toArray)->BuiltIn.dispatch(environment, reduceExpression)

View File

@ -5,7 +5,6 @@ module Parse = Reducer_Peggy_Parse
type expression = ExpressionT.expression
let rec fromNode = (node: Parse.node): expression => {
let caseBlock = nodeBlock =>
ExpressionBuilder.eBlock(nodeBlock["statements"]->Js.Array2.map(fromNode)->Belt.List.fromArray)

View File

@ -90,7 +90,7 @@ let toStringResult = x =>
let toStringResultOkless = codeResult =>
switch codeResult {
| Ok(a) => toString(a)
| Error(m) => `Error(${ErrorValue.errorToString(m)})`
| Error(m) => `Error(${ErrorValue.errorToString(m)})`
}
let toStringResultRecord = x =>