diff --git a/docs/reference.md b/docs/reference.md index c8f7f14..35cd4fa 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -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 diff --git a/moonscript/compile.lua b/moonscript/compile.lua index 502d303..06c2183 100644 --- a/moonscript/compile.lua +++ b/moonscript/compile.lua @@ -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) diff --git a/moonscript/compile.moon b/moonscript/compile.moon index e1fe92d..035e625 100644 --- a/moonscript/compile.moon +++ b/moonscript/compile.moon @@ -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) => diff --git a/moonscript/compile/value.lua b/moonscript/compile/value.lua index 934ab5f..2ae27f5 100644 --- a/moonscript/compile/value.lua +++ b/moonscript/compile/value.lua @@ -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, diff --git a/moonscript/compile/value.moon b/moonscript/compile/value.moon index 62048f1..60e76cd 100644 --- a/moonscript/compile/value.moon +++ b/moonscript/compile/value.moon @@ -197,6 +197,9 @@ value_compile = minus: (node) => @line "-", @value node[2] + temp_name: (node) => + node\get_name self + number: (node) => node[2] diff --git a/moonscript/transform.lua b/moonscript/transform.lua new file mode 100644 index 0000000..d790248 --- /dev/null +++ b/moonscript/transform.lua @@ -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"):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 diff --git a/moonscript/transform.moon b/moonscript/transform.moon new file mode 100644 index 0000000..5ed1256 --- /dev/null +++ b/moonscript/transform.moon @@ -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")\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 + + diff --git a/moonscript/types.lua b/moonscript/types.lua index 1cfe58b..0436263 100644 --- a/moonscript/types.lua +++ b/moonscript/types.lua @@ -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) diff --git a/moonscript/types.moon b/moonscript/types.moon index 84df9f1..e817ff0 100644 --- a/moonscript/types.moon +++ b/moonscript/types.moon @@ -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 diff --git a/tests/inputs/stub.moon b/tests/inputs/stub.moon new file mode 100644 index 0000000..19ce047 --- /dev/null +++ b/tests/inputs/stub.moon @@ -0,0 +1,13 @@ + + +x = { + val: 100 + hello: => + print @val +} + +fn = x\val +print fn! +print x\val! + + diff --git a/tests/outputs/stub.lua b/tests/outputs/stub.lua new file mode 100644 index 0000000..6ab73cc --- /dev/null +++ b/tests/outputs/stub.lua @@ -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()) \ No newline at end of file diff --git a/todo b/todo index abe8440..b29fb1c 100644 --- a/todo +++ b/todo @@ -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