function stub syntax, and start of tree transformer

This commit is contained in:
leaf corcoran 2011-10-01 11:46:47 -07:00
parent 5440d0deb9
commit b7af08f15f
12 changed files with 334 additions and 14 deletions

View File

@ -644,6 +644,35 @@ Or...
me = create_person "Leaf", {dad, mother, sister}
## Function Stubs
It is common to pass a function from an object around as a value, for example,
passing an instance method into a function as a callback. If the function
expects the object it is operating on as the first argument then you must
somehow bundle that object with the function so it can be called properly.
The function stub syntax is a shorthand for creating a new closure function
that bundles both the object and function. This new function calls the wrapped
function in the correct context of the object.
Its syntax is the same as calling an instance method with the `\` operator but
with no argument list provided.
my_object = {
value: 1000
print: => print "the value:", @value
}
run_callback (func) ->
print "running callback..."
func!
-- this will not work, the function has to no reference to my_object
run_callback my_object.print
-- function stub syntax lets us bundle the object into a new function
run_callback my_object\print
## The Using Clause; Controlling Destructive Assignment
While lexical scoping can be a great help in reducing the complexity of the

View File

@ -4,6 +4,8 @@ local dump = require("moonscript.dump")
require("moonscript.compile.format")
require("moonscript.compile.line")
require("moonscript.compile.value")
local transform = require("moonscript.transform")
local NameProxy = transform.NameProxy
local Set
do
local _table_0 = require("moonscript.data")
@ -114,9 +116,20 @@ Block_ = (function(_parent_0)
local _item_0 = names
for _index_0 = 1, #_item_0 do
local name = _item_0[_index_0]
if type(name) == "string" and not self:has_name(name) then
local t = util.moon.type(name)
local real_name
if t == NameProxy then
real_name = name:get_name(self)
elseif t == "string" then
real_name = name
end
local _value_0
if real_name and not self:has_name(real_name) then
_value_0 = real_name
end
if _value_0 ~= nil then
_len_0 = _len_0 + 1
_accum_0[_len_0] = name
_accum_0[_len_0] = _value_0
end
end
end
@ -135,6 +148,9 @@ Block_ = (function(_parent_0)
self._name_whitelist = Set(names)
end,
put_name = function(self, name)
if util.moon.type(name) == NameProxy then
name = name:get_name(self)
end
self._names[name] = true
end,
has_name = function(self, name)
@ -318,6 +334,7 @@ Block_ = (function(_parent_0)
if not fn then
error("Failed to compile value: " .. dump.value(node))
end
node = transform.node(node)
return fn(self, node, ...)
end,
values = function(self, values, delim)

View File

@ -7,6 +7,9 @@ require "moonscript.compile.format"
require "moonscript.compile.line"
require "moonscript.compile.value"
transform = require "moonscript.transform"
import NameProxy from transform
import Set from require "moonscript.data"
import ntype from require "moonscript.types"
@ -74,7 +77,14 @@ class Block_
@_state[name]
declare: (names) =>
undeclared = [name for name in *names when type(name) == "string" and not @has_name name]
undeclared = for name in *names
t = util.moon.type(name)
real_name = if t == NameProxy
name\get_name self
elseif t == "string"
name
real_name if real_name and not @has_name real_name
@put_name name for name in *undeclared
undeclared
@ -82,6 +92,7 @@ class Block_
@_name_whitelist = Set names
put_name: (name) =>
name = name\get_name self if util.moon.type(name) == NameProxy
@_names[name] = true
has_name: (name) =>
@ -218,6 +229,7 @@ class Block_
fn = value_compile[action]
error "Failed to compile value: "..dump.value node if not fn
node = transform.node node
fn self, node, ...
values: (values, delim) =>

View File

@ -368,6 +368,9 @@ value_compile = {
minus = function(self, node)
return self:line("-", self:value(node[2]))
end,
temp_name = function(self, node)
return node:get_name(self)
end,
number = function(self, node)
return node[2]
end,

View File

@ -197,6 +197,9 @@ value_compile =
minus: (node) =>
@line "-", @value node[2]
temp_name: (node) =>
node\get_name self
number: (node) =>
node[2]

102
moonscript/transform.lua Normal file
View File

@ -0,0 +1,102 @@
module("moonscript.transform", package.seeall)
local types = require("moonscript.types")
local util = require("moonscript.util")
local data = require("moonscript.data")
local ntype, build = types.ntype, types.build
NameProxy = (function(_parent_0)
local _base_0 = {
get_name = function(self, scope)
if not self.name then
self.name = scope:free_name(self.prefix, true)
end
return self.name
end,
__tostring = function(self)
if self.name then
return ("name<%s>"):format(self.name)
else
return ("name<prefix(%s)>"):format(self.prefix)
end
end
}
_base_0.__index = _base_0
if _parent_0 then
setmetatable(_base_0, getmetatable(_parent_0).__index)
end
local _class_0 = setmetatable({
__init = function(self, prefix)
self.prefix = prefix
self[1] = "temp_name"
end
}, {
__index = _base_0,
__call = function(mt, ...)
local self = setmetatable({}, _base_0)
mt.__init(self, ...)
return self
end
})
_base_0.__class = _class_0
return _class_0
end)()
local transformers = {
chain = function(node)
local stub = node[#node]
if type(stub) == "table" and stub[1] == "colon_stub" then
table.remove(node, #node)
local base_name = NameProxy("base")
local fn_name = NameProxy("fn")
return build.block_exp({
build.assign({
names = {
base_name
},
values = {
node
}
}),
build.assign({
names = {
fn_name
},
values = {
build.chain({
base = base_name,
{
"dot",
stub[2]
}
})
}
}),
build.fndef({
args = {
{
"..."
}
},
body = {
build.chain({
base = fn_name,
{
"call",
{
base_name,
"..."
}
}
})
}
})
})
end
end
}
node = function(n)
local transformer = transformers[ntype(n)]
if transformer then
return transformer(n) or n
else
return n
end
end

68
moonscript/transform.moon Normal file
View File

@ -0,0 +1,68 @@
module "moonscript.transform", package.seeall
types = require "moonscript.types"
util = require "moonscript.util"
data = require "moonscript.data"
import ntype, build from types
export node, NameProxy
class NameProxy
new: (@prefix) =>
self[1] = "temp_name"
get_name: (scope) =>
if not @name
@name = scope\free_name @prefix, true
@name
__tostring: =>
if @name
("name<%s>")\format @name
else
("name<prefix(%s)>")\format @prefix
transformers = {
-- pull out colon chain
chain: (node) ->
stub = node[#node]
if type(stub) == "table" and stub[1] == "colon_stub"
table.remove node, #node
base_name = NameProxy "base"
fn_name = NameProxy "fn"
build.block_exp {
build.assign {
names: {base_name}
values: {node}
}
build.assign {
names: {fn_name}
values: {
build.chain { base: base_name, {"dot", stub[2]} }
}
}
build.fndef {
args: {{"..."}}
body: {
build.chain {
base: fn_name, {"call", {base_name, "..."}}
}
}
}
}
}
node = (n) ->
transformer = transformers[ntype n]
if transformer
transformer(n) or n
else
n

View File

@ -1,6 +1,7 @@
module("moonscript.types", package.seeall)
local util = require("moonscript.util")
local data = require("moonscript.data")
local insert = table.insert
ntype = function(node)
if type(node) ~= "table" then
return "value"
@ -27,6 +28,16 @@ local node_types = {
"body",
t
}
},
assign = {
{
"names",
t
},
{
"values",
t
}
}
}
local build_table
@ -57,11 +68,10 @@ make_builder = function(name)
name
}
for i, arg in ipairs(spec) do
local default_value
name, default_value = unpack(arg)
local key, default_value = unpack(arg)
local val
if props[name] then
val = props[name]
if props[key] then
val = props[key]
else
val = default_value
end
@ -73,7 +83,39 @@ make_builder = function(name)
return node
end
end
build = setmetatable({ }, {
build = nil
build = setmetatable({
block_exp = function(body)
local fn = build.fndef({
body = body
})
return build.chain({
base = {
"parens",
fn
},
{
"call",
{ }
}
})
end,
chain = function(parts)
local base = parts.base or error("expecting base property for chain")
local node = {
"chain",
base
}
do
local _item_0 = parts
for _index_0 = 1, #_item_0 do
local part = _item_0[_index_0]
insert(node, part)
end
end
return node
end
}, {
__index = function(self, name)
self[name] = make_builder(name)
return rawget(self, name)

View File

@ -3,6 +3,8 @@ util = require "moonscript.util"
data = require "moonscript.data"
export ntype, smart_node, build
import insert from table
-- type of node as string
ntype = (node) ->
@ -20,6 +22,10 @@ node_types = {
{"arrow", "slim"}
{"body", t}
}
assign: {
{"names", t}
{"values", t}
}
}
build_table = ->
@ -41,13 +47,24 @@ make_builder = (name) ->
(props={}) ->
node = { name }
for i, arg in ipairs spec
name, default_value = unpack arg
val = if props[name] then props[name] else default_value
key, default_value = unpack arg
val = if props[key] then props[key] else default_value
val = {} if val == t
node[i + 1] = val
node
build = setmetatable {}, {
build = nil
build = setmetatable {
block_exp: (body) ->
fn = build.fndef body: body
build.chain { base: {"parens", fn}, {"call", {}} }
chain: (parts) ->
base = parts.base or error"expecting base property for chain"
node = {"chain", base}
for part in *parts
insert node, part
node
}, {
__index: (name) =>
self[name] = make_builder name
rawget self, name

13
tests/inputs/stub.moon Normal file
View File

@ -0,0 +1,13 @@
x = {
val: 100
hello: =>
print @val
}
fn = x\val
print fn!
print x\val!

15
tests/outputs/stub.lua Normal file
View File

@ -0,0 +1,15 @@
local x = {
val = 100,
hello = function(self)
return print(self.val)
end
}
local fn = (function()
local _base_0 = x
local _fn_0 = _base_0.val
return function(...)
return _fn_0(_base_0, ...)
end
end)()
print(fn())
print(x:val())

5
todo
View File

@ -1,18 +1,17 @@
# TODO
- dump node in mark, see why values are being parsed multiple times
if hello else world
-> if hello then hello else world
- varargs that get put in a nested generated function aren't valid anymore:
- allow a return without an argument
- class expressions, x = class extends Hello do new: => print "hello"
* multiline comments
* table slices (almost)
* add continue keyword (ouch)
* vim syntax file
* combine for and if line decorators