Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Thoughts about Lilia, a language that compiles to Lua
-- These are random scattered thoughts about this hypothetical language. I did
-- my best to make it "Lua-y". Ideally most existing Lua code should compile to
-- near-equivalent code.
-- Wrap print function so that it also returns what was passed
local _pr
do
old_print = print
function _pr(...) do
old_print(...)
return ...
end
end
print := _pr
-- Imports
do
import cdef from ffi
-- equivalent to
{ \cdef } := require 'ffi'
-- or
local cdef
do
_obj_0 := require 'ffi'
cdef = _obj_0.cdef
end
end
-- Tables shorthand syntax
Point := {
:get_x() = @x,
:set_x(x) @x = x,
:__tostring() do
-- Pipe result of previous expression to function. This makes logging
-- incredibly convenient. Also, embedded expressions in strings only works
-- for double-quoted strings.
return "(${@x}, ${@y})" |> print
-- compare
-- return print("(${@x}, ${@y})")
end,
}
-- Macros
do
-- Edit the AST during compilation
macro_rules! clamp {
($min:expr, $max:expr, $x:tt) => {
math.max(math.min($x, $min), $max)
}
}
-- To include macros, you'll have to use the preprocessor.
--
-- Unfortunately, this means macros can't use a different namespace and may
-- conflict with other code.
!!include(macros.lyh)
num := 100
clamped := clamp!(3, 50, num)
-- Using for HTML
macro_rules! write_html {
($w:expr, ) => ()
($w:expr, $e:tt) => ($w ..= "${$e}")
($w:expr, $tag:ident [ $($inner:tt)* ] $($rest:tt)*) => {{
-- To simplify parsing, each statement **must** have a semicolon.
-- I'm gonna call this "strict Lua"
$w ..= "<${$tag}>";
write_html!($w, $($inner)*);
$w ..= "</${$tag}>";
write_html!($w, $($rest)*);
}}
}
-- random thought: using a Lisp variant of Lua for macros can greatly
-- simplify eval and parsing. Then again it's because Lisp is
-- basically an AST.
out := ""
write_html!(out,
html[
head[title["Macros guide"]]
body[h1["Macros are the best!"]]
])
-- should expand to
out ..= "<html>"
out ..= "<head>"
out ..= "<title>"
out ..= "Macros guide"
out ..= "</title>"
out ..= "</head>"
out ..= "<body>"
out ..= "<h1>"
out ..= "Macros are the best!"
out ..= "</h1>"
out ..= "</body>"
out ..= "</html>"
-- the result should be
assert(out == "<html><head><title>Macros guide</title></head><body><h1>Macros are the best!</h1></body></html>")
end
-- Functions
do
-- Nonlocal function declaration
function global_disaster()
print "IT'S THE END OF THE WORLD!!!11!1"
end
-- Local function declaration shorthand
local sayHello()
print "Hello World"
end
-- This still works of course
-- local function sayHello()
-- print "Hello World"
-- end
-- Lambda-style syntax.
square := |x| x * x
cube := |x| x * x * x
sayHello = || do
print "Hello World"
end
end
-- Functional Programming
do
-- Higher-Order Functions
-- ---
local filter(xs, pred)
result, len := [], #xs
for i, len do
if pred(xs[i]) then
table.insert(result, xs[i])
end
end
return result
end
-- Function that returns another function
is := |type| |x| type(x) == type
filter(is('number'), [0, '1', 2, nil]) |> print -- [0, 2]
-- Currying
-- ---
local res
sum := |a, b| a + b
curried_sum := |a| |b| a + b
res = curried_sum(40)(2) |> print -- 42.
add2 := curriedSum(2) -- |b| 2 + b
res = add2(10) |> print -- 12.
-- Function Composition
-- ---
compose := |f, g| |a| f(g(a))
floor_and_tostring := compose(|v| tostring(v), math.floor)
floor_and_tostring(121.212_121) -- underscores in numbers for readability.
-- shamelessly stolen from Rust.
-- Mapping
-- ---
local map(val, xs)
result = {}
for k, v in pairs(val) do
result = xs(v, k)
end
return result
end
foo := {6, 3, 7, 0, 2, 4, 1, 8, 3, 6, 1}
res = map(val, |x| x * x) |> print -- i don't have time for this
-- Ruby-style passing closure to last parameter of function
res = map(val) do |x|
return x * x
end
-- equivalent to
res = map(val, |x| do
return x * x
end)
-- but with one less comma
end
-- misc. syntactic sugar
do
-- PHP-style table append
test := {1, 2}
test[] = 3
test[#test+1] = 4
print(test) -- { 1, 2, 3, 4 }
-- the ? operator
yveltal := { level = 42 }
print(yveltal.level, yveltal.sex?)
-- turns to
if yveltal.sex ~= nil then
print(yveltal.level, yveltal.sex)
end
-- the ? operator applies to the *whole* statement. this is to prevent leaky
-- abstractions and nasty side effects.
-- shorthand multiple value in tables
zeroes := {0; 35}
-- good for preallocating stuff
map := {{nil; 16}; 16}
end
-- Match and With statement
do
x := 1
match x with
1 => do_this(),
2 => do
do_that()
end,
_ => finally_do_this(),
end
mob := Entity.Creeper()
block := Block.Stone()
match mob with
-- backslash is defined as the return value of the expression
\.is_entity => print(\.name)
end
with block do
\.get_pos() |> print
\.get_name() |> print
\.set_invisible(true)
\.set_pos(mob.get_pos() |> Block.entity_to_block_pos)
end
end
-- Classes
class Person(name)
-- Class body as constructor
@name = name
-- Class method declaration
::speak()
print "Hi, I am ${@name}"
end
-- Shorthand class method declaration
::__tostring() = @name
-- Equivalent to this
::__tostring = || @name
-- or this
::__tostring = function() return @name end
-- Move code outside the class constructor
::escape
-- Dynamic method generation
for method in *{ 'get', 'post', 'delete', 'put' } do
upper_meth := meth:upper()
@__base[meth] = |route_name, path, hadler| do
if handler == nil then
handler = path
path = route_name
route_name = nil
end
@responders or= {}
existing := @responders[route_name or path]
tbl := { [upper_meth]: handler }
if existing then
setmetatable(tbl, {
__index: |key| do
if key:match '%u' then
return existing
end
end
} )
end
responder = respond_to(tbl)
@responders[route_name or path] = responder
return @match(route_name, path, responder)
end
end
end
end
class AgedPerson(name, age) < Person(name)
-- Class variables i.e. static variables
@@ADULT_AGE = 18
@age = age
::speak()
super()
message := if @age < @@ADULT_AGE then
-- accessing a class variable from an instance method
"I am underaged."
else
"I am an adult."
end
message |> print
end
end
p1 := AgedPerson('Billy the Kid', 13)
p2 := AgedPerson('Luke Skywalker', 21)
p1:speak()
p2:speak()
-- Metamethods
class Vector2D(x, y)
@x, @y = x, y
::add(b) = Vector2D(@x + b.x, @y + b.y)
::sub(b) = Vector2D(@x - b.x, @y - b.y)
::dot(b) = Vector2D(@x * b.x, @y * b.y)
::__add(b) = @:add(b)
::__sub(b) = @:sub(b)
::__tostring() = "[${@x}, ${@y}]"
::__index(key)
mt := getmetatable @
old_index := mt.__index
mt.__index = function (key)
return old_index[key]?
if key == 'xy' then
return Vector2D(@x, @y)
end
end
end
end
class Vector3D(x, y, z)
@@_X = string.byte('x')
@@_Y = string.byte('y')
@@_Z = string.byte('z')
@x, @y, @z = x, y, z
::add(b) = Vector3D(@x + b.x, @y + b.y, @z + b.z)
::sub(b) = Vector3D(@x - b.x, @y - b.y, @z - b.z)
::dot(b) = Vector3D(@x * b.x, @y * b.y, @z * b.z)
::__add(b) = @:add(b)
::__sub(b) = @:sub(b)
::__tostring() = "[${@x}, ${@y}, ${@z}]"
-- overriding the index metamethod
::__index(key)
mt := getmetatable(@)
old_index := mt.__index
mt.__index = |key| do
old := old_index[key]
if old ~= nil then old end
coords := {nil; 3}
len := math.max(#str, 3)
for i = 1, len do
match str:byte(i) with
@@_X => coords[i] = @x,
@@_Y => coords[i] = @y,
@@_Z => coords[i] = @z,
end
end
return if coords[3] == nil then
coords |> unpack |> Vector2D
elseif coords[2] == nil then
coords[1]
else
coords |> unpack |> Vector3D
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.