aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authors-ol <s-ol@users.noreply.github.com>2020-08-21 11:30:16 +0000
committers-ol <s+removethis@s-ol.nu>2025-03-02 14:24:49 +0000
commit950dd72215f375fe37d8dc5d78152ccf452d5e46 (patch)
treea17591a435eac84f85a0b5933ca14336322a5e1e
parentdirty (remove) implementation (diff)
downloadalive-950dd72215f375fe37d8dc5d78152ccf452d5e46.tar.gz
alive-950dd72215f375fe37d8dc5d78152ccf452d5e46.zip
move array ops into library, implement concat and size
-rw-r--r--alv-lib/array.moon243
-rw-r--r--spec/lang/array_spec.moon72
-rw-r--r--spec/test_setup.moon6
3 files changed, 292 insertions, 29 deletions
diff --git a/alv-lib/array.moon b/alv-lib/array.moon
new file mode 100644
index 0000000..94499bc
--- /dev/null
+++ b/alv-lib/array.moon
@@ -0,0 +1,243 @@
+import Array, Op, PureOp, Constant, Error, const, sig, evt from require 'alv.base'
+
+get = Constant.meta
+ meta:
+ name: 'get'
+ summary: "Index into Arrays."
+ examples: { '(get array i)' }
+ description: "Get the value at index `i` (starting at 0).
+
+`i` has to be a constant expression."
+
+ value: class extends PureOp
+ pattern: (sig! / evt!) + const.num
+ type: (inputs) =>
+ { array, i } = inputs
+ array\type!\get i.result!
+
+ tick: =>
+ { array, i } = @unwrap_all!
+ @out\set array[i + 1]
+
+set = Constant.meta
+ meta:
+ name: 'set'
+ summary: "Update values in Arrays."
+ examples: { '(set array i val)' }
+ description: "Set the value for `i` to `val`.
+
+`i` has to be a constant expression. This is a pure op, so at most one of
+`array` and `val` may be a !-stream."
+
+ value: class extends PureOp
+ pattern: (sig! / evt!) + const.num + (sig! / evt!)
+ type: (inputs) =>
+ { array, i, val } = inputs
+ type = array\type!
+ expected = type\get i.result!
+
+ if expected ~= val\type!
+ msg = string.format "expected value of type %s, not %s",
+ expected, val\type!
+ error Error 'argument', msg
+
+ type
+
+ tick: =>
+ { array, key, val } = @unwrap_all!
+
+ array = [v for v in *array]
+ array[key + 1] = val
+
+ @out\set array
+
+head = Constant.meta
+ meta:
+ name: 'head'
+ summary: "Get the first element from an array."
+ examples: { '(head array)' }
+
+ value: class extends PureOp
+ pattern: (sig! / evt!)*1
+ type: (inputs) =>
+ type = inputs[1]\type!
+
+ assert type.__class == Array, Error 'argument', "expected an Array"
+ assert type.size > 0, Error 'argument', "cannot get head of empty Array"
+
+ type.type
+
+ tick: =>
+ { array } = @unwrap_all!
+ @out\set array[1]
+
+tail = Constant.meta
+ meta:
+ name: 'tail'
+ summary: "Get everything except the first element from an array."
+ examples: { '(tail array)' }
+
+ value: class extends PureOp
+ pattern: (sig! / evt!)*1
+ type: (inputs) =>
+ type = inputs[1]\type!
+
+ assert type.__class == Array, Error 'argument', "expected an Array"
+ assert type.size > 0, Error 'argument', "cannot get tail of empty Array"
+
+ Array type.size - 1, type.type
+
+ tick: =>
+ { array } = @unwrap_all!
+ @out\set [v for v in *array[2,]]
+
+prepend = Constant.meta
+ meta:
+ name: 'prepend'
+ summary: "Prepend a new value at the start of an Array."
+ examples: { '(prepend array val)' }
+ description: "Prepend `val` to `array` at index `0`, moving other values back.
+
+This is a pure op, so at most one of `array` and `val` may be a !-stream."
+
+ value: class extends PureOp
+ pattern: (sig! / evt!) + (sig! / evt!)
+ type: (inputs) =>
+ { array, val } = inputs
+ type = array\type!
+
+ if val\type! ~= type.type
+ msg = string.format "expected value of type %s, not %s",
+ type.type, val\type!
+ error Error 'argument', msg
+
+ Array type.size + 1, type.type
+
+ tick: =>
+ { array, val } = @unwrap_all!
+
+ array = [v for v in *array]
+ table.insert array, 1, val
+
+ @out\set array
+
+insert = Constant.meta
+ meta:
+ name: 'insert'
+ summary: "Insert new values into Arrays."
+ examples: { '(insert array i val)' }
+ description: "Insert `val` into `array` at `i`, moving other values back if
+necessary.
+
+`i` has to be a constant expression. This is a pure op, so at most one of
+`array` and `val` may be a !-stream."
+
+ value: class extends PureOp
+ pattern: (sig! / evt!) + const.num + (sig! / evt!)
+ type: (inputs) =>
+ { array, i, val } = inputs
+ type = array\type!
+ i = i.result!
+
+ if i > type.size or i < 0
+ error Error 'argument', "index '#{i}' out of range!"
+ if val\type! ~= type.type
+ msg = string.format "expected value of type %s, not %s",
+ type.type, val\type!
+ error Error 'argument', msg
+
+ Array type.size + 1, type.type
+
+ tick: =>
+ { array, i, val } = @unwrap_all!
+
+ array = [v for v in *array]
+ table.insert array, i + 1, val
+
+ @out\set array
+
+remove = Constant.meta
+ meta:
+ name: 'remove'
+ summary: "Remove values from Arrays."
+ examples: { '(remove array i)' }
+ description: "Removes the value at index `i` from `array`.
+
+`i` has to be a constant expression."
+
+ value: class extends PureOp
+ pattern: (sig! / evt!) + const.num
+ type: (inputs) =>
+ { array, i } = inputs
+ type = array\type!
+
+ -- check index range
+ type\get i.result!
+
+ Array type.size - 1, type.type
+
+ tick: =>
+ { array, i, val } = @unwrap_all!
+
+ array = [v for v in *array]
+ table.remove array, i + 1
+
+ @out\set array
+
+size = Constant.meta
+ meta:
+ name: 'size'
+ summary: "Get Array size"
+ examples: { '(size array)' }
+
+ value: class extends Op
+ setup: (inputs) =>
+ super {}
+
+ assert #inputs == 1, Error 'argument', "expected exactly one argument"
+ type = inputs[1]\type!
+ assert type.__class == Array, Error 'argument', "expected an Array"
+
+ @out = Constant.num type.size
+
+concat = Constant.meta
+ meta:
+ name: 'concat'
+ summary: "Concatenate Arrays"
+ examples: { '(concat arr1 arr2 [arr3…])' }
+
+ value: class extends PureOp
+ pattern: (sig! / evt!)\rep 2
+ type: (inputs) =>
+ size = 0
+ type = inputs[1]\type!.type
+
+ for input in *inputs
+ array = input\type!
+
+ if array.type ~= type
+ msg = string.format "Cannot concatenate different arrays %s, %s",
+ inputs[1]\type!, array
+ error Error 'argument', msg
+
+ size += array.size
+
+ Array size, type
+
+ tick: =>
+ arrays = @unwrap_all!
+ out = {}
+
+ for array in *arrays
+ for val in *array
+ table.insert out, val
+
+ @out\set out
+
+{
+ :get, :set
+ :head, :tail, :prepend
+ :insert, :remove
+
+ :size, :concat
+}
diff --git a/spec/lang/array_spec.moon b/spec/lang/array_spec.moon
index 2dd2894..fc7ce43 100644
--- a/spec/lang/array_spec.moon
+++ b/spec/lang/array_spec.moon
@@ -2,7 +2,7 @@ import TestPilot from require 'spec.test_setup'
import T, Array, Constant from require 'alv'
describe "array", ->
- test = TestPilot ''
+ test = TestPilot '', '(import* array)\n'
svec3 = Array 3, T.str
@@ -16,15 +16,6 @@ describe "array", ->
err = assert.has.error -> COPILOT\eval_once '(array 1 false)'
assert.matches "argument error: couldn't match arguments", err
- it "length can be read using (len)", ->
- rt = COPILOT\eval_once '(len (array 1))'
- assert.is.true rt\is_const!
- assert.is.equal (Constant.num 1), rt.result
-
- rt = COPILOT\eval_once '(len (array 1 2 3))'
- assert.is.true rt\is_const!
- assert.is.equal (Constant.num 3), rt.result
-
describe "(set)", ->
it "can swap values", ->
rt = COPILOT\eval_once '(set (array "f" "b" "c") 0 "a")'
@@ -33,7 +24,7 @@ describe "array", ->
it "checks value type", ->
err = assert.has.error -> COPILOT\eval_once '(set (array 1) 0 "a")'
- assert.matches "expected value for key '0' to be num, not str", err
+ assert.matches "expected value of type num, not str", err
it "checks index range", ->
err = assert.has.error -> COPILOT\eval_once '(set (array 1 2) -1 0)'
@@ -47,18 +38,13 @@ describe "array", ->
assert.matches "index '2' out of range!", err
describe "(get)", ->
- it "can peek a value", ->
- rt = COPILOT\eval_once '(get (array 1 2))'
- assert.is.true rt\is_const!
- assert.is.equal (Constant.num 2), rt.result
-
it "can get a value", ->
rt = COPILOT\eval_once '(get (array 1 2) 0)'
assert.is.true rt\is_const!
assert.is.equal (Constant.num 1), rt.result
it "checks index range", ->
- err = assert.has.error -> COPILOT\eval_once '(get (array 1 2) -1 0)'
+ err = assert.has.error -> COPILOT\eval_once '(get (array 1 2) -1)'
assert.matches "index '%-1' out of range!", err
COPILOT\eval_once '(get (array 1 2) 0)'
@@ -68,12 +54,42 @@ describe "array", ->
err = assert.has.error -> COPILOT\eval_once '(get (array 1 2) 2)'
assert.matches "index '2' out of range!", err
- describe "(insert)", ->
- it "can append a value", ->
- rt = COPILOT\eval_once '(insert (array "a" "b") "c")'
+ describe '(head)', ->
+ it "can peek a value", ->
+ rt = COPILOT\eval_once '(head (array 1 2))'
assert.is.true rt\is_const!
- assert.is.equal svec3\mk_const({ 'a', 'b', 'c' }), rt.result
+ assert.is.equal (Constant.num 1), rt.result
+
+ describe '(tail)', ->
+ it "gets rest of an array", ->
+ rt = COPILOT\eval_once '(tail (array 1))'
+ assert.is.true rt\is_const!
+ assert.is.same (Array 0, T.num), rt.result.type
+ assert.is.same {}, rt.result!
+
+ rt = COPILOT\eval_once '(tail (array 1 2))'
+ assert.is.true rt\is_const!
+ assert.is.same (Array 1, T.num), rt.result.type
+ assert.is.same { 2 }, rt.result!
+ rt = COPILOT\eval_once '(tail (array 1 2 3 4))'
+ assert.is.true rt\is_const!
+ assert.is.same (Array 3, T.num), rt.result.type
+ assert.is.same { 2, 3, 4 }, rt.result!
+
+ describe '(prepend)', ->
+ it "prepends to array", ->
+ rt = COPILOT\eval_once '(prepend (array 2) 1)'
+ assert.is.true rt\is_const!
+ assert.is.same (Array 2, T.num), rt.result.type
+ assert.is.same { 1, 2 }, rt.result!
+
+ rt = COPILOT\eval_once '(prepend (array 2 3 4) 1)'
+ assert.is.true rt\is_const!
+ assert.is.same (Array 4, T.num), rt.result.type
+ assert.is.same { 1, 2, 3, 4 }, rt.result!
+
+ describe "(insert)", ->
it "can insert a value", ->
rt = COPILOT\eval_once '(insert (array "b" "c") 0 "a")'
assert.is.true rt\is_const!
@@ -101,11 +117,6 @@ describe "array", ->
assert.matches "index '3' out of range!", err
describe "(remove)", ->
- it "can pop a value", ->
- rt = COPILOT\eval_once '(remove (array "a" "b" "c" "d"))'
- assert.is.true rt\is_const!
- assert.is.equal svec3\mk_const({ 'a', 'b', 'c' }), rt.result
-
it "can remove a value", ->
rt = COPILOT\eval_once '(remove (array "d" "a" "b" "c") 0)'
assert.is.true rt\is_const!
@@ -126,3 +137,12 @@ describe "array", ->
rt = COPILOT\eval_once '(concat (array "a" "b") (array "c"))'
assert.is.true rt\is_const!
assert.is.equal svec3\mk_const({ 'a', 'b', 'c' }), rt.result
+
+ it "size can be read using (size)", ->
+ rt = COPILOT\eval_once '(size (array 1))'
+ assert.is.true rt\is_const!
+ assert.is.equal (Constant.num 1), rt.result
+
+ rt = COPILOT\eval_once '(size (array 1 2 3))'
+ assert.is.true rt\is_const!
+ assert.is.equal (Constant.num 3), rt.result
diff --git a/spec/test_setup.moon b/spec/test_setup.moon
index 2bcd9df..92756a8 100644
--- a/spec/test_setup.moon
+++ b/spec/test_setup.moon
@@ -15,13 +15,13 @@ os.time = do
export COPILOT
class TestPilot extends Copilot
- new: (code) =>
+ new: (code, @preamble) =>
super!
COPILOT = @
if code
- @active_module = StringModule 'main', code
+ @active_module = StringModule 'main', @preamble .. code
@last_modules.__root = @active_module
@tick!
else
@@ -33,7 +33,7 @@ class TestPilot extends Copilot
next_tick: => @T += 1
eval_once: (code) =>
- @active_module\spit code
+ @active_module\spit @preamble .. code
@tick!
@active_module.root