From 89f4a83f2651da74704a21937477b879065705f7 Mon Sep 17 00:00:00 2001 From: leaf corcoran Date: Tue, 27 Nov 2012 23:46:32 -0800 Subject: [PATCH] add destructuring assignment --- moonscript/compile.lua | 6 +- moonscript/compile.moon | 2 +- moonscript/parse.lua | 3 +- moonscript/transform.lua | 142 ++-------------- moonscript/transform.moon | 50 ++---- moonscript/transform/destructure.lua | 227 ++++++++++++++++++++++++++ moonscript/transform/destructure.moon | 109 +++++++++++++ moonscript/transform/names.lua | 140 ++++++++++++++++ moonscript/transform/names.moon | 43 +++++ tests/inputs/destructure.moon | 45 +++++ tests/outputs/destructure.lua | 88 ++++++++++ 11 files changed, 681 insertions(+), 174 deletions(-) create mode 100644 moonscript/transform/destructure.lua create mode 100644 moonscript/transform/destructure.moon create mode 100644 moonscript/transform/names.lua create mode 100644 moonscript/transform/names.moon create mode 100644 tests/inputs/destructure.moon create mode 100644 tests/outputs/destructure.lua diff --git a/moonscript/compile.lua b/moonscript/compile.lua index 0e200d8..00dfc4e 100644 --- a/moonscript/compile.lua +++ b/moonscript/compile.lua @@ -5,7 +5,11 @@ require("moonscript.compile.format") require("moonscript.compile.statement") require("moonscript.compile.value") local transform = require("moonscript.transform") -local NameProxy, LocalName = transform.NameProxy, transform.LocalName +local NameProxy, LocalName +do + local _table_0 = require("moonscript.transform.names") + NameProxy, LocalName = _table_0.NameProxy, _table_0.LocalName +end local Set do local _table_0 = require("moonscript.data") diff --git a/moonscript/compile.moon b/moonscript/compile.moon index 411ee9f..e3c3740 100644 --- a/moonscript/compile.moon +++ b/moonscript/compile.moon @@ -9,7 +9,7 @@ require "moonscript.compile.value" transform = require "moonscript.transform" -import NameProxy, LocalName from transform +import NameProxy, LocalName from require "moonscript.transform.names" import Set from require "moonscript.data" import ntype from require "moonscript.types" diff --git a/moonscript/parse.lua b/moonscript/parse.lua index 81b1929..b706104 100644 --- a/moonscript/parse.lua +++ b/moonscript/parse.lua @@ -170,7 +170,8 @@ local _chain_assignable = { index = true, dot = true, slice = true } local function is_assignable(node) local t = ntype(node) return t == "self" or t == "value" or t == "self_class" or - t == "chain" and _chain_assignable[ntype(node[#node])] + t == "chain" and _chain_assignable[ntype(node[#node])] or + t == "table" end local function check_assignable(str, pos, value) diff --git a/moonscript/transform.lua b/moonscript/transform.lua index e2511f6..1039ad9 100644 --- a/moonscript/transform.lua +++ b/moonscript/transform.lua @@ -5,6 +5,12 @@ local data = require("moonscript.data") local reversed = util.reversed local ntype, build, smart_node, is_slice, value_is_singular = types.ntype, types.build, types.smart_node, types.is_slice, types.value_is_singular local insert = table.insert +local NameProxy, LocalName +do + local _table_0 = require("moonscript.transform.names") + NameProxy, LocalName = _table_0.NameProxy, _table_0.LocalName +end +local destructure = require("moonscript.transform.destructure") local mtype do local moon_type = util.moon.type @@ -18,136 +24,6 @@ do end end local implicitly_return -do - local _parent_0 = nil - local _base_0 = { - get_name = function(self) - return self.name - end - } - _base_0.__index = _base_0 - if _parent_0 then - setmetatable(_base_0, _parent_0.__base) - end - local _class_0 = setmetatable({ - __init = function(self, name) - self.name = name - self[1] = "temp_name" - end, - __base = _base_0, - __name = "LocalName", - __parent = _parent_0 - }, { - __index = function(cls, name) - local val = rawget(_base_0, name) - if val == nil and _parent_0 then - return _parent_0[name] - else - return val - end - end, - __call = function(cls, ...) - local _self_0 = setmetatable({}, _base_0) - cls.__init(_self_0, ...) - return _self_0 - end - }) - _base_0.__class = _class_0 - if _parent_0 and _parent_0.__inherited then - _parent_0.__inherited(_parent_0, _class_0) - end - LocalName = _class_0 -end -do - local _parent_0 = nil - 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, - chain = function(self, ...) - local items = { - ... - } - items = (function() - local _accum_0 = { } - local _len_0 = 0 - local _list_0 = items - for _index_0 = 1, #_list_0 do - local i = _list_0[_index_0] - local _value_0 - if type(i) == "string" then - _value_0 = { - "dot", - i - } - else - _value_0 = i - end - if _value_0 ~= nil then - _len_0 = _len_0 + 1 - _accum_0[_len_0] = _value_0 - end - end - return _accum_0 - end)() - return build.chain({ - base = self, - unpack(items) - }) - end, - index = function(self, key) - return build.chain({ - base = self, - { - "index", - key - } - }) - 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, _parent_0.__base) - end - local _class_0 = setmetatable({ - __init = function(self, prefix) - self.prefix = prefix - self[1] = "temp_name" - end, - __base = _base_0, - __name = "NameProxy", - __parent = _parent_0 - }, { - __index = function(cls, name) - local val = rawget(_base_0, name) - if val == nil and _parent_0 then - return _parent_0[name] - else - return val - end - end, - __call = function(cls, ...) - local _self_0 = setmetatable({}, _base_0) - cls.__init(_self_0, ...) - return _self_0 - end - }) - _base_0.__class = _class_0 - if _parent_0 and _parent_0.__inherited then - _parent_0.__inherited(_parent_0, _class_0) - end - NameProxy = _class_0 -end do local _parent_0 = nil local _base_0 = { @@ -497,7 +373,11 @@ Statement = Transformer({ }) end end - return transformed or node + node = transformed or node + if destructure.has_destructure(names) then + return destructure.split_assign(node) + end + return node end, continue = function(self, node) local continue_name = self:send("continue") diff --git a/moonscript/transform.moon b/moonscript/transform.moon index a83713a..c34035d 100644 --- a/moonscript/transform.moon +++ b/moonscript/transform.moon @@ -8,6 +8,9 @@ data = require "moonscript.data" import reversed from util import ntype, build, smart_node, is_slice, value_is_singular from types import insert from table +import NameProxy, LocalName from require "moonscript.transform.names" + +destructure = require "moonscript.transform.destructure" mtype = do moon_type = util.moon.type @@ -19,48 +22,10 @@ mtype = do else t -export Statement, Value, NameProxy, LocalName, Run +export Statement, Value, Run local implicitly_return --- always declares as local -class LocalName - new: (@name) => self[1] = "temp_name" - get_name: => @name - -class NameProxy - new: (@prefix) => - self[1] = "temp_name" - - get_name: (scope) => - if not @name - @name = scope\free_name @prefix, true - @name - - chain: (...) => - items = {...} -- todo: fix ... propagation - items = for i in *items - if type(i) == "string" - {"dot", i} - else - i - - build.chain { - base: self - unpack items - } - - index: (key) => - build.chain { - base: self, {"index", key} - } - - __tostring: => - if @name - ("name<%s>")\format @name - else - ("name")\format @prefix - class Run new: (@fn) => self[1] = "run" @@ -228,7 +193,12 @@ Statement = Transformer { @transform.statement value, ret, node } - transformed or node + node = transformed or node + + if destructure.has_destructure names + return destructure.split_assign node + + node continue: (node) => continue_name = @send "continue" diff --git a/moonscript/transform/destructure.lua b/moonscript/transform/destructure.lua new file mode 100644 index 0000000..fc8ebf1 --- /dev/null +++ b/moonscript/transform/destructure.lua @@ -0,0 +1,227 @@ +local ntype, build +do + local _table_0 = require("moonscript.types") + ntype, build = _table_0.ntype, _table_0.build +end +local NameProxy +do + local _table_0 = require("moonscript.transform.names") + NameProxy = _table_0.NameProxy +end +local insert = table.insert +local user_error +do + local _table_0 = require("moonscript.compile") + user_error = _table_0.user_error +end +local join +join = function(...) + do + local _with_0 = { } + local out = _with_0 + local i = 1 + local _list_0 = { + ... + } + for _index_0 = 1, #_list_0 do + local tbl = _list_0[_index_0] + local _list_1 = tbl + for _index_1 = 1, #_list_1 do + local v = _list_1[_index_1] + out[i] = v + i = i + 1 + end + end + return _with_0 + end +end +local has_destructure +has_destructure = function(names) + local _list_0 = names + for _index_0 = 1, #_list_0 do + local n = _list_0[_index_0] + if ntype(n) == "table" then + return true + end + end + return false +end +local build_assign +build_assign = function(extracted_names, receiver) + local obj = NameProxy("obj") + local names = { } + local values = { } + local _list_0 = extracted_names + for _index_0 = 1, #_list_0 do + local tuple = _list_0[_index_0] + insert(names, tuple[1]) + insert(values, obj:chain(unpack(tuple[2]))) + end + return build.group({ + { + "declare", + names + }, + build["do"]({ + build.assign_one(obj, receiver), + { + "assign", + names, + values + } + }) + }) +end +local extract_assign_names +extract_assign_names = function(name, accum, prefix) + if accum == nil then + accum = { } + end + if prefix == nil then + prefix = { } + end + local i = 1 + local _list_0 = name[2] + for _index_0 = 1, #_list_0 do + local tuple = _list_0[_index_0] + local value, suffix + if #tuple == 1 then + local s = { + "index", + { + "number", + i + } + } + i = i + 1 + value, suffix = tuple[1], s + else + local key = tuple[1] + local s + if ntype(key) == "key_literal" then + s = { + "dot", + key[2] + } + else + s = { + "index", + key + } + end + value, suffix = tuple[2], s + end + suffix = join(prefix, { + suffix + }) + local t = ntype(value) + if t == "value" or t == "chain" or t == "self" then + insert(accum, { + value, + suffix + }) + elseif t == "table" then + extract_assign_names(value, accum, suffix) + else + user_error("Can't destructure value of type: " .. tostring(ntype(value))) + end + end + return accum +end +local split_assign +split_assign = function(assign) + local names, values = unpack(assign, 2) + local g = { } + local total_names = #names + local total_values = #values + local start = 1 + for i, n in ipairs(names) do + if ntype(n) == "table" then + if i > start then + local stop = i - 1 + insert(g, { + "assign", + (function() + local _accum_0 = { } + local _len_0 = 0 + for i = start, stop do + local _value_0 = names[i] + if _value_0 ~= nil then + _len_0 = _len_0 + 1 + _accum_0[_len_0] = _value_0 + end + end + return _accum_0 + end)(), + (function() + local _accum_0 = { } + local _len_0 = 0 + for i = start, stop do + local _value_0 = values[i] + if _value_0 ~= nil then + _len_0 = _len_0 + 1 + _accum_0[_len_0] = _value_0 + end + end + return _accum_0 + end)() + }) + end + local extracted = extract_assign_names(n) + insert(g, build_assign(extracted, values[i])) + start = i + 1 + end + end + if total_names >= start or total_values >= start then + local name_slice + if total_names < start then + name_slice = { + "_" + } + else + name_slice = (function() + local _accum_0 = { } + local _len_0 = 0 + for i = start, total_names do + local _value_0 = names[i] + if _value_0 ~= nil then + _len_0 = _len_0 + 1 + _accum_0[_len_0] = _value_0 + end + end + return _accum_0 + end)() + end + local value_slice + if total_values < start then + value_slice = { + "nil" + } + else + value_slice = (function() + local _accum_0 = { } + local _len_0 = 0 + for i = start, total_values do + local _value_0 = values[i] + if _value_0 ~= nil then + _len_0 = _len_0 + 1 + _accum_0[_len_0] = _value_0 + end + end + return _accum_0 + end)() + end + insert(g, { + "assign", + name_slice, + value_slice + }) + end + return build.group(g) +end +return { + has_destructure = has_destructure, + split_assign = split_assign, + extract_assign_names = extract_assign_names, + build_assign = build_assign +} diff --git a/moonscript/transform/destructure.moon b/moonscript/transform/destructure.moon new file mode 100644 index 0000000..07b615a --- /dev/null +++ b/moonscript/transform/destructure.moon @@ -0,0 +1,109 @@ + +import ntype, build from require "moonscript.types" +import NameProxy from require "moonscript.transform.names" +import insert from table + +import user_error from require "moonscript.compile" + +join = (...) -> + with out = {} + i = 1 + for tbl in *{...} + for v in *tbl + out[i] = v + i += 1 + +has_destructure = (names) -> + for n in *names + return true if ntype(n) == "table" + false + +build_assign = (extracted_names, receiver) -> + obj = NameProxy "obj" + names = {} + values = {} + + for tuple in *extracted_names + insert names, tuple[1] + insert values, obj\chain unpack tuple[2] + + build.group { + {"declare", names} + build.do { + build.assign_one obj, receiver + {"assign", names, values} + } + } + +extract_assign_names = (name, accum={}, prefix={}) -> + i = 1 + for tuple in *name[2] + value, suffix = if #tuple == 1 + s = {"index", {"number", i}} + i += 1 + tuple[1], s + else + key = tuple[1] + s = if ntype(key) == "key_literal" + {"dot", key[2]} + else + {"index", key} + + tuple[2], s + + suffix = join prefix, {suffix} + + t = ntype value + if t == "value" or t == "chain" or t == "self" + insert accum, {value, suffix} + elseif t == "table" + extract_assign_names value, accum, suffix + else + user_error "Can't destructure value of type: #{ntype value}" + + accum + +-- applies to destructuring to a assign node +split_assign = (assign) -> + names, values = unpack assign, 2 + + g = {} + total_names = #names + total_values = #values + + -- We have to break apart the assign into groups of regular + -- assigns, and then the destructuring assignments + start = 1 + for i, n in ipairs names + if ntype(n) == "table" + if i > start + stop = i - 1 + insert g, { + "assign" + for i=start,stop + names[i] + for i=start,stop + values[i] + } + + extracted = extract_assign_names n + insert g, build_assign extracted, values[i] + + start = i + 1 + + if total_names >= start or total_values >= start + name_slice = if total_names < start + {"_"} + else + for i=start,total_names do names[i] + + value_slice = if total_values < start + {"nil"} + else + for i=start,total_values do values[i] + + insert g, {"assign", name_slice, value_slice} + + build.group g + +{ :has_destructure, :split_assign, :extract_assign_names, :build_assign } diff --git a/moonscript/transform/names.lua b/moonscript/transform/names.lua new file mode 100644 index 0000000..aa28dcd --- /dev/null +++ b/moonscript/transform/names.lua @@ -0,0 +1,140 @@ +local build +do + local _table_0 = require("moonscript.types") + build = _table_0.build +end +local LocalName +do + local _parent_0 = nil + local _base_0 = { + get_name = function(self) + return self.name + end + } + _base_0.__index = _base_0 + if _parent_0 then + setmetatable(_base_0, _parent_0.__base) + end + local _class_0 = setmetatable({ + __init = function(self, name) + self.name = name + self[1] = "temp_name" + end, + __base = _base_0, + __name = "LocalName", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil and _parent_0 then + return _parent_0[name] + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + if _parent_0 and _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + LocalName = _class_0 +end +local NameProxy +do + local _parent_0 = nil + 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, + chain = function(self, ...) + local items = (function(...) + local _accum_0 = { } + local _len_0 = 0 + local _list_0 = { + ... + } + for _index_0 = 1, #_list_0 do + local i = _list_0[_index_0] + local _value_0 + if type(i) == "string" then + _value_0 = { + "dot", + i + } + else + _value_0 = i + end + if _value_0 ~= nil then + _len_0 = _len_0 + 1 + _accum_0[_len_0] = _value_0 + end + end + return _accum_0 + end)(...) + return build.chain({ + base = self, + unpack(items) + }) + end, + index = function(self, key) + return build.chain({ + base = self, + { + "index", + key + } + }) + 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, _parent_0.__base) + end + local _class_0 = setmetatable({ + __init = function(self, prefix) + self.prefix = prefix + self[1] = "temp_name" + end, + __base = _base_0, + __name = "NameProxy", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil and _parent_0 then + return _parent_0[name] + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + if _parent_0 and _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + NameProxy = _class_0 +end +return { + NameProxy = NameProxy, + LocalName = LocalName +} diff --git a/moonscript/transform/names.moon b/moonscript/transform/names.moon new file mode 100644 index 0000000..f505997 --- /dev/null +++ b/moonscript/transform/names.moon @@ -0,0 +1,43 @@ + +import build from require "moonscript.types" + +-- always declares as local +class LocalName + new: (@name) => self[1] = "temp_name" + get_name: => @name + +-- creates a unique name when used +class NameProxy + new: (@prefix) => + self[1] = "temp_name" + + get_name: (scope) => + if not @name + @name = scope\free_name @prefix, true + @name + + chain: (...) => + items = for i in *{...} + if type(i) == "string" + {"dot", i} + else + i + + build.chain { + base: self + unpack items + } + + index: (key) => + build.chain { + base: self, {"index", key} + } + + __tostring: => + if @name + ("name<%s>")\format @name + else + ("name")\format @prefix + + +{ :NameProxy, :LocalName } diff --git a/tests/inputs/destructure.moon b/tests/inputs/destructure.moon new file mode 100644 index 0000000..847ae73 --- /dev/null +++ b/tests/inputs/destructure.moon @@ -0,0 +1,45 @@ + +{a, b} = hello + +{{a}, b, {c}} = hello + +{ :hello, :world } = value + +{ yes: no, thing } = world + +{:a,:b,:c,:d} = yeah + +{a} = one, two +{b}, c = one +{d}, e = one, two + +x, {y} = one, two + +xx, yy = 1, 2 +{yy, xx} = {xx, yy} + +{a, :b, c, :d, e, :f, g} = tbl + +--- + +futurists = + sculptor: "Umberto Boccioni" + painter: "Vladimir Burliuk" + poet: + name: "F.T. Marinetti" + address: { + "Via Roma 42R" + "Bellagio, Italy 22021" + } + +{poet: {:name, address: {street, city}}} = futurists + +print name, street, city + +-- + +{ @world } = x +{ a.b, c.y, func!.z } = x + +{ world: @world } = x + diff --git a/tests/outputs/destructure.lua b/tests/outputs/destructure.lua new file mode 100644 index 0000000..9299b72 --- /dev/null +++ b/tests/outputs/destructure.lua @@ -0,0 +1,88 @@ +local a, b +do + local _obj_0 = hello + a, b = _obj_0[1], _obj_0[2] +end +local c +do + local _obj_0 = hello + a, b, c = _obj_0[1][1], _obj_0[2], _obj_0[3][1] +end +local hello, world +do + local _obj_0 = value + hello, world = _obj_0.hello, _obj_0.world +end +local no, thing +do + local _obj_0 = world + no, thing = _obj_0.yes, _obj_0[1] +end +local d +do + local _obj_0 = yeah + a, b, c, d = _obj_0.a, _obj_0.b, _obj_0.c, _obj_0.d +end +do + local _obj_0 = one + a = _obj_0[1] +end +local _ = two +do + local _obj_0 = one + b = _obj_0[1] +end +c = nil +do + local _obj_0 = one + d = _obj_0[1] +end +local e = two +local x = one +local y +do + local _obj_0 = two + y = _obj_0[1] +end +local xx, yy = 1, 2 +do + local _obj_0 = { + xx, + yy + } + yy, xx = _obj_0[1], _obj_0[2] +end +local f, g +do + local _obj_0 = tbl + a, b, c, d, e, f, g = _obj_0[1], _obj_0.b, _obj_0[2], _obj_0.d, _obj_0[3], _obj_0.f, _obj_0[4] +end +local futurists = { + sculptor = "Umberto Boccioni", + painter = "Vladimir Burliuk", + poet = { + name = "F.T. Marinetti", + address = { + "Via Roma 42R", + "Bellagio, Italy 22021" + } + } +} +local name, street, city +do + local _obj_0 = futurists + name, street, city = _obj_0.poet.name, _obj_0.poet.address[1], _obj_0.poet.address[2] +end +print(name, street, city) +do + local _obj_0 = x + self.world = _obj_0[1] +end +do + local _obj_0 = x + a.b, c.y, func().z = _obj_0[1], _obj_0[2], _obj_0[3] +end +do + local _obj_0 = x + self.world = _obj_0.world +end \ No newline at end of file