Download
235 lines 7 KiBNomsuModified 2019-03-27 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
#!/usr/bin/env nomsu -V7.0.0
###
    This file contains code that supports manipulating and using collections like lists
    and dictionaries.
    
use "core/metaprogramming"
use "core/control_flow"
use "core/operators"

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

### List functionality:
test:
    $list = [1, 2, 3, 4, 5]
    $visited = {}
    for ($i = $x) in $list:
        $visited.$i = (yes)
    assume ($visited == {.1, .2, .3, .4, .5})
    $visited = {}
    for $x in $list:
        $visited.$x = (yes)
    assume ($visited == {.1, .2, .3, .4, .5})
    assume (($list, 2 nd to last) == 4)
    assume (($list, first) == 1)
    assume ($list, has 3)
    assume (($list, index of 3) == 3)
    assume (#$list == 5)
    $list, add 6
    assume (($list, last) == 6)
    $list, pop
    assume (($list, last) == 5)
    $list, remove index 1
    assume (($list, first) == 2)
    assume (([1, 2] + [3, 4]) == [1, 2, 3, 4])

### Dict functionality
test:
    $dict = {.x = 1, .y = 2, .z = 3}
    assume #$dict == 3
    assume [: for ($k = $v) in {.x = 1}: add {.key = $k, .value = $v}] ==
        [{.key = "x", .value = 1}]
    assume ({.x = 1, .y = 1} + {.y = 10, .z = 10}) == {.x = 1, .y = 11, .z = 10}
    assume ({.x = 1, .y = 1} - {.y = 10, .z = 10}) == {.x = 1, .y = -9, .z = -10}
    assume ({.x = 1, .y = 1} | {.y = 10, .z = 10}) == {.x = 1, .y = 1, .z = 10}
    assume ({.x = 1, .y = 1} & {.y = 10, .z = 10}) == {.y = 1}
    assume ({.x = 1, .y = 1} ~ {.y = 10, .z = 10}) == {.x = 1, .z = 10}
    
    ### Set compliments:
    assume (~{.x}).y
    assume ((~{.x}).x == (nil))
    $sc = ~{.x, .y}
    $sc.y = 99
    
    ### For now, whether $sc.y == 99 or $sc.y == (yes) is unspecified,
        (but the actual behavior is (yes))
    assume ($sc.y and (not $sc.x))
    assume ($sc == (~{.x, .y} | {.y = 99}))
    
    ### Both sets:
    assume (({.x, .y} & {.y, .z}) == {.y})
    assume (({.x, .y} | {.y, .z}) == {.x, .y, .z})
    assume (({.x, .y} ~ {.y, .z}) == {.z, .x})
    
    ### Mixed:
    assume (({.x, .y} & ~{.y, .z}) == {.x})
    assume (({.x, .y} | ~{.y, .z}) == ~{.z})
    assume (({.x, .y} ~ ~{.y, .z}) == ~{.x})
    
    ### Mixed reversed:
    assume ((~{.y, .z} & {.x, .y}) == {.x})
    assume ((~{.y, .z} | {.x, .y}) == ~{.z})
    assume ((~{.y, .z} ~ {.x, .y}) == ~{.x})
    
    ### Both set compliments:
    assume ((~{.x, .y} & ~{.y, .z}) == ~{.x, .y, .z})
    assume ((~{.x, .y} | ~{.y, .z}) == ~{.y})
    assume ((~{.x, .y} ~ ~{.y, .z}) == {.x, .z})

test:
    assume ((entries in {.x = 1}) == [{.key = "x", .value = 1}])

(entries in $dict) parses as
    [: for ($k = $v) in $dict: add {.key = $k, .value = $v}]

test:
    assume ((keys in {.x = 1}) == ["x"])

[keys in $dict, keys of $dict] all parse as [: for ($k = $v) in $dict: add $k]
test:
    assume ((values in {.x = 1}) == [1])

[values in $dict, values of $dict] all parse as
    [: for ($k = $v) in $dict: add $v]

### Metatable stuff
test:
    $t = {}
    set $t's metatable to {.__tostring = ($ -> "XXX")}
    assume ("\$t" == "XXX")

(set $dict's metatable to $metatable) compiles to
    "setmetatable(\($dict as lua expr), \($metatable as lua expr));"

[$'s metatable, $'metatable] all compile to "getmetatable(\($ as lua expr))"
test:
    assume (({} with fallback $ -> ($ + 1)).10 == 11)

($dict with fallback $key -> $value) compiles to ("
    (function(d)
        local mt = {}
        for k,v in pairs(getmetatable(d) or {}) do mt[k] = v end
        mt.__index = function(self, \($key as lua expr))
            local value = \($value as lua expr)
            self[\($key as lua expr)] = value
            return value
        end
        return setmetatable(d, mt)
    end)(\($dict as lua expr))
")

### Sorting
test:
    $x = [3, 1, 2]
    sort $x
    assume ($x == [1, 2, 3])
    sort $x by $ = -$
    assume ($x == [3, 2, 1])
    $keys = {.1 = 999, .2 = 0, .3 = 50}
    sort $x by $ = $keys.$
    assume ($x == [2, 3, 1])
(sort $items) compiles to "table.sort(\($items as lua expr));"
[sort $items by $item = $key_expr, sort $items by $item -> $key_expr]
..all parse as
    do:
        $keys = ({} with fallback $item -> $key_expr)
        lua> "table.sort(\$items, function(x,y) return \$keys[x] < \$keys[y] end)"

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

external:
    test:
        assume ((sorted [3, 1, 2]) == [1, 2, 3])
    
    [$items sorted, sorted $items] all mean:
        $copy = [: for $ in $items: add $]
        sort $copy
        return $copy
    
    [$items sorted by $item = $key, $items sorted by $item -> $key] all parse as
        result of:
            $copy = [: for $ in $items: add $]
            sort $copy by $item = $key
            return $copy
    
    test:
        assume ((unique [1, 2, 1, 3, 2, 3]) == [1, 2, 3])
    
    (unique $items) means:
        $unique = []
        $seen = {}
        for $ in $items:
            unless $seen.$:
                $unique, add $
                $seen.$ = (yes)
        
        return $unique

### Ranges:
test:
    $r = (3 to 5)
    assume ($r.2 == 4)
    assume ($r is "a Range")
    assume ((1 to 10, backwards) == (10 to 1 by -1))
    assume (#(1 to 10 by 2) == 5)
    $visited = []
    for $ in (1 to 10 by 2):
        $visited, add $
    assume ($visited == [1, 3, 5, 7, 9])
    $r = (1 to 10 by 2)
    $visited = []
    for $ in $r:
        $visited, add $
    assume ($visited == [1, 3, 5, 7, 9])

$range_mt = {
    .__type = "a Range"
    .__index =
        for ($self's $key):
            if ($key is "a Number"):
                if (($key % 1) != 0):
                    return (nil)
                $i = ($self.first + ($key - 1) * $self.step)
                if ($self.step > 0):
                    if ($i > $self.last):
                        return (nil)
                ..else:
                    if ($i < $self.last):
                        return (nil)
                
                return $i
            return $range_mt.$key
    
    .__len =
        for $self:
            $len = (($self.last - $self.first) / $self.step + 1)
            if ($len < 0):
                $len = 0
            return ($len, rounded down)
    
    .__eq =
        for ($self == $other)
            (($self's metatable) == ($other's metatable)) and
                ($self.first == $other.first) and
                    ($self.last == $other.last) and ($self.step == $other.step)
    
    .backwards = (for $self ($self.last to $self.first by -$self.step))
    .__inext = $(inext), .__next = $(inext)
    .as_text =
        for $self:
            if ($self.step == 1):
                return "(\($self.first) to \($self.last))"
            ..else:
                return "(\($self.first) to \($self.last) by \($self.step))"
}

$range_mt.reversed = $range_mt.backwards
$range_mt.__unm = $range_mt.backwards
$range_mt.__tostring = $range_mt.as_text
$range_mt.as_nomsu = $range_mt.as_text
set $range_mt's metatable to (nil)
external:
    ($first to $last by $step) means
        setmetatable {.first = $first, .last = $last, .step = $step} $range_mt
    
    ($first to $last) means
        setmetatable {.first = $first, .last = $last, .step = 1} $range_mt