nomsu/core/operators.nom

246 lines
9.0 KiB
Plaintext

#!/usr/bin/env nomsu -V4.12.12.8
#
This file contains definitions of operators like "+" and "and".
use "core/metaprogramming.nom"
use "core/errors.nom"
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
test:
assume (all [1 < 2, 2 > 1, 1 <= 2, 2 >= 1, 1 == 1, 1 != 2])
# Comparison Operators
(%x < %y) compiles to "(\(%x as lua expr) < \(%y as lua expr))"
(%x > %y) compiles to "(\(%x as lua expr) > \(%y as lua expr))"
(%x <= %y) compiles to "(\(%x as lua expr) <= \(%y as lua expr))"
(%x >= %y) compiles to "(\(%x as lua expr) >= \(%y as lua expr))"
[%a is %b, %a == %b] all compile to "(\(%a as lua expr) == \(%b as lua expr))"
[%a isn't %b, %a is not %b, %a not= %b, %a != %b] all compile to "\
..(\(%a as lua expr) ~= \(%b as lua expr))"
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
test:
%x = 10
assume (%x == 10)
[%x, %y] = [10, 20]
assume ((%x == 10) and (%y == 20)) or barf "mutli-assignment failed."
[%x, %y] = [%y, %x]
assume ((%y == 10) and (%x == 20)) or barf "swapping vars failed."
%vals = [4, 5]
[%x, %y] = (unpack %vals)
assume ((%x == 4) and (%y == 5)) or barf "unpacking failed"
# Variable assignment operator
(%var = %value) compiles to:
lua> "\
..
local lua = LuaCode()
if \%var.type == "List" then
for i, \%assignment in ipairs(\%var) do
if i > 1 then lua:append(", ") end
local assignment_lua = \(%assignment as lua expr)
lua:append(assignment_lua)
if \%assignment.type == 'Var' then
lua:add_free_vars({assignment_lua:text()})
end
end
lua:append(' = ')
if \%value.type == "List" then
if #\%value ~= #\%var then
compile_error_at(\%value,
"This assignment has too "..(#\%value > #\%var and "many" or "few").." values.",
"Make sure it has the same number of values on the left and right hand side of the '\
..=' operator.")
end
for i, \%val in ipairs(\%value) do
if i > 1 then lua:append(", ") end
local val_lua = \(%val as lua expr)
lua:append(val_lua)
end
lua:append(";")
else
lua:append(\(%value as lua expr), ';')
end
else
local var_lua = \(%var as lua expr)
lua:append(var_lua)
if \%var.type == 'Var' then
lua:add_free_vars({var_lua:text()})
end
lua:append(' = ', \(%value as lua expr), ';')
end
return lua"
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
test:
[%foozle, %y] = ["outer", "outer"]
externally (set global x local y) means:
external %foozle = "inner"
%y = "inner"
set global x local y
assume ((%foozle == "inner") and (%y == "outer")) or barf "external failed."
(external %var = %value) compiles to "\(%var as lua) = \(%value as lua)"
test:
[%foozle, %y] = ["outer", "outer"]
externally (set global x local y) means:
with external [%foozle]:
%foozle = "inner"
%y = "inner"
set global x local y
assume ((%foozle == "inner") and (%y == "outer")) or barf "\
..'with external' failed."
(with external %externs %body) compiles to:
%body_lua = (%body as lua)
lua> "\%body_lua:remove_free_vars(table.map(\%externs, function(v) return compile(v):text() end))"
return %body_lua
test:
[%x, %y] = [1, 2]
with {%z, %x: 999}:
assume %z == (nil)
%z = 999
assume (%z == 999) or barf "'with' failed."
assume (%x == 999) or barf "'with' assignment failed."
assume (%x == 1) or barf "'with' scoping failed"
assume (%z == (nil)) or barf "'with' scoping failed"
(with %assignments %body) compiles to:
%lua = (%body as lua)
lua> "\
..local lhs, rhs = LuaCode(), LuaCode()
local vars = {}
for i, item in ipairs(\%assignments) do
local \%target, \%value = item[1], item[2]
local target_lua = \(%target as lua)
if not target_lua:text():is_lua_id() then
compile_error_at(\%target, "Invalid target for 'with' scope assignment.",
"This should be either a variable or an action's meaning.")
end
local value_lua = \%value and \(%value as lua) or "nil"
if i > 1 then
lhs:append(", ")
rhs:append(", ")
end
lhs:append(target_lua)
rhs:append(value_lua)
vars[i] = target_lua:text()
end
\%lua:remove_free_vars(vars)
\%lua:prepend("local ", lhs, " = ", rhs, ";\\n")"
return (Lua "do\n \%lua\nend -- 'with' block")
# Math Operators
test:
assume ((5 wrapped around 2) == 1) or barf "mod not working"
[%x wrapped around %y, %x mod %y] all compile to "\
..((\(%x as lua expr)) % (\(%y as lua expr)))"
# 3-part chained comparisons
# (uses a lambda to avoid re-evaluating middle value, while still being an expression)
test:
%calls = 0
(one) means:
external %calls = (%calls + 1)
return 1
assume (0 <= (one) <= 2) or barf "Three-way chained comparison failed."
assume (%calls == 1) or barf "\
..Three-way comparison evaluated middle value multiple times"
(%x < %y < %z) parses as (((%a %b %c) -> ((%a < %b) and (%b < %c))) %x %y %z)
(%x <= %y < %z) parses as (((%a %b %c) -> ((%a <= %b) and (%b < %c))) %x %y %z)
(%x < %y <= %z) parses as (((%a %b %c) -> ((%a < %b) and (%b <= %c))) %x %y %z)
(%x <= %y <= %z) parses as (((%a %b %c) -> ((%a <= %b) and (%b <= %c))) %x %y %z)
(%x > %y > %z) parses as (((%a %b %c) -> ((%a > %b) and (%b > %c))) %x %y %z)
(%x >= %y > %z) parses as (((%a %b %c) -> ((%a >= %b) and (%b > %c))) %x %y %z)
(%x > %y >= %z) parses as (((%a %b %c) -> ((%a > %b) and (%b >= %c))) %x %y %z)
(%x >= %y >= %z) parses as (((%a %b %c) -> ((%a >= %b) and (%b >= %c))) %x %y %z)
# TODO: optimize for common case where x,y,z are all either variables or number literals
# Boolean Operators
test:
(barfer) means (barf "short circuiting failed")
assume (((no) and (barfer)) == (no))
assume ((no) or (yes))
assume ((yes) or (barfer))
(%x and %y) compiles to "(\(%x as lua expr) and \(%y as lua expr))"
(%x or %y) compiles to "(\(%x as lua expr) or \(%y as lua expr))"
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Bitwise Operators
# TODO: implement OR, XOR, AND for multiple operands?
test:
assume ((~ (~ 5)) == 5)
assume ((1 | 4) == 5)
assume ((1 ~ 3) == 2)
assume ((1 & 3) == 1)
assume ((1 << 2) == 4)
assume ((4 >> 2) == 1)
# Lua 5.3 introduced bit operators like | and &. Use them when possible, otherwise
fall back to bit.bor(), bit.band(), etc.
lua> "if \((is jit) or ((Lua version) == "Lua 5.2")) then"
[NOT %, ~ %] all compile to "bit.bnot(\(% as lua expr))"
[%x OR %y, %x | %y] all compile to "\
..bit.bor(\(%x as lua expr), \(%y as lua expr))"
[%x XOR %y, %x ~ %y] all compile to "\
..bit.bxor(\(%x as lua expr), \(%y as lua expr))"
[%x AND %y, %x & %y] all compile to "\
..bit.band(\(%x as lua expr), \(%y as lua expr))"
[%x LSHIFT %shift, %x << %shift] all compile to "\
..bit.lshift(\(%x as lua expr), \(%shift as lua expr))"
[%x RSHIFT %shift, %x >> %shift] all compile to "\
..bit.rshift(\(%x as lua expr), \(%shift as lua expr))"
lua> "else"
[NOT %, ~ %] all compile to "~(\(% as lua expr))"
[%x OR %y, %x | %y] all compile to "(\(%x as lua expr) | \(%y as lua expr))"
[%x XOR %y, %x ~ %y] all compile to "(\(%x as lua expr) ~ \(%y as lua expr))"
[%x AND %y, %x & %y] all compile to "(\(%x as lua expr) & \(%y as lua expr))"
[%x LSHIFT %shift, %x << %shift] all compile to "\
..(\(%x as lua expr) << \(%shift as lua expr))"
[%x RSHIFT %shift, %x >> %shift] all compile to "\
..(\(%x as lua expr) >> \(%shift as lua expr))"
lua> "end"
# Unary operators
test:
assume ((- 5) == -5)
assume ((not (yes)) == (no))
(- %) compiles to "(- \(% as lua expr))"
(not %) compiles to "(not \(% as lua expr))"
test:
assume ((size of [1, 2, 3]) == 3)
(size of %list) compiles to "(#\(%list as lua expr))"
(%list is empty) compiles to "(#\(%list as lua expr) == 0)"
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Update operators
test:
%x = 1
%x += 1
assume (%x == 2) or barf "+= failed"
%x *= 2
assume (%x == 4) or barf "*= failed"
wrap %x around 3
assume (%x == 1) or barf "wrap around failed"
(%var += %) parses as (%var = ((%var or 0) + %))
(%var -= %) parses as (%var = ((%var or 0) - %))
(%var *= %) parses as (%var = ((%var or 1) * %))
(%var /= %) parses as (%var = (%var / %))
(%var ^= %) parses as (%var = (%var ^ %))
(%var and= %) parses as (%var = (%var and %))
(%var or= %) parses as (%var = (%var or %))
(wrap %var around %) parses as (%var = (%var wrapped around %))