Skip to content

Instantly share code, notes, and snippets.

@phoenixenero
Created August 6, 2016 08:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save phoenixenero/919489f9ff4de5934e4a5d58b9bd3066 to your computer and use it in GitHub Desktop.
Save phoenixenero/919489f9ff4de5934e4a5d58b9bd3066 to your computer and use it in GitHub Desktop.
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