Created
August 6, 2016 08:34
-
-
Save phoenixenero/919489f9ff4de5934e4a5d58b9bd3066 to your computer and use it in GitHub Desktop.
Thoughts about Lilia, a language that compiles to Lua
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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