From 3159a45de9e691ad758dcbc933446f61b7ae1940 Mon Sep 17 00:00:00 2001 From: Li Jin Date: Thu, 14 Jul 2022 02:48:49 +0800 Subject: fix table matching issue and update doc. --- doc/docs/doc/README.md | 119 +++++++++++++++++++++++++++++++++++++++-- spec/inputs/switch.yue | 6 +-- spec/outputs/switch.lua | 28 +++++----- src/yuescript/yue_compiler.cpp | 72 ++++++++++++++++--------- 4 files changed, 176 insertions(+), 49 deletions(-) diff --git a/doc/docs/doc/README.md b/doc/docs/doc/README.md index 5d856bb..ef00c23 100755 --- a/doc/docs/doc/README.md +++ b/doc/docs/doc/README.md @@ -311,7 +311,7 @@ end ### Export Macro -Macro functions can be exported from a module and get imported in another module. It is recommanded to export macro functions in a single file to speed up compilation. +Macro functions can be exported from a module and get imported in another module. You have to put export macro functions in a single file to be used, and only macro definition, macro importing and macro expansion in place can be put into the macro exporting module. ```moonscript -- file: utils.yue export macro map = (items, action)-> "[#{action} for _ in *#{items}]" @@ -345,6 +345,20 @@ import "utils" as { +### Builtin Macro + +There are some builtin macros but you can override them by declaring macros with the same names. +```moonscript +print $FILE -- get string of current module name +print $LINE -- get number 2 +``` + +
+print $FILE -- get string of current module name
+print $LINE -- get number 2
+
+
+ ## Operator All of Lua's binary and unary operators are available. Additionally **!=** is as an alias for **~=**, and either **\\** or **::** can be used to write a chaining function call like `tb\func!` or `tb::func!`. And Yuescipt offers some other special operators to write more expressive codes. @@ -1237,10 +1251,6 @@ close _ = close#: -> print "Out of scope." -::: warning NOTICE -The rest of the document is describing mostly the same syntax taken from Moonscript. So you may as well refer to the [Moonscript Reference](http://moonscript.org/reference) to get the same explanation. -::: - ## Literals All of the primitive literals in Lua can be used. This applies to numbers, strings, booleans, and **nil**. @@ -2295,6 +2305,60 @@ msg = switch math.random(1, 5) It is worth noting the order of the case comparison expression. The case’s expression is on the left hand side. This can be useful if the case’s expression wants to overwrite how the comparison is done by defining an eq metamethod. +### Table Matching + +You can do table matching in a switch when clause, if the table can be destructured by a specific structure and get non-nil values. + +```moonscript +items = + * x: 100 + y: 200 + * width: 300 + height: 400 + +for item in *items + switch item + when :x, :y + print "Vec2 #{x}, #{y}" + when :width, :height + print "size #{width}, #{height}" +``` + +
+items =
+  * x: 100
+    y: 200
+  * width: 300
+    height: 400
+
+for item in *items
+  switch item
+    when :x, :y
+      print "Vec2 #{x}, #{y}"
+    when :width, :height
+      print "size #{width}, #{height}"
+
+
+ +You can use default values to optionally destructure the table for some fields. + +```moonscript +item = x: 100 + +switch item + when {:x, :y = 200} + print "Vec2 #{x}, #{y}" -- table matching will pass +``` + +
+item = x: 100
+
+switch item
+  when {:x, :y = 200}
+    print "Vec2 #{x}, #{y}" -- table matching will pass
+
+
+ ## Object Oriented Programming In these examples, the generated Lua code may appear overwhelming. It is best to focus on the meaning of the Yuescript code at first, then look into the Lua code if you wish to know the implementation details. @@ -2718,6 +2782,51 @@ x = class +### Class Mixing + +You can do mixing with keyword `using` to copy functions from either a plain table or a predefined class object into your new class. When doing mixing with a plain table, you can override the class indexing function (metamethod `__index`) to your customized implementation. When doing mixing with an existing class object, the class object's metamethods won't be copied. + +```moonscript +MyIndex = __index: var: 1 + +class X using MyIndex + func: => + print 123 + +x = X! +print x.var + +class Y using X + +y = Y! +y\func! + +assert y.__class.__parent ~= X -- X is not parent of Y +``` + +
+MyIndex = __index: var: 1
+
+class X using MyIndex
+  func: =>
+    print 123
+
+x = X!
+print x.var
+
+class Y using X
+
+y = Y!
+y\func!
+
+assert y.__class.__parent ~= X -- X is not parent of Y
+
+
+ +::: warning NOTICE +The rest of the document is describing mostly the same syntax taken from Moonscript. So you may as well refer to the [Moonscript Reference](http://moonscript.org/reference) to get the same explanation. +::: + ## With Statement A common pattern involving the creation of an object is calling a series of functions and setting a series of properties immediately after creating it. diff --git a/spec/inputs/switch.yue b/spec/inputs/switch.yue index 36f9be6..04bb02e 100644 --- a/spec/inputs/switch.yue +++ b/spec/inputs/switch.yue @@ -139,10 +139,10 @@ do "#{a + b}" when 1, 2, 3, 4, 5 "number 1 - 5" - when {:alwaysMatch = "fallback"} - alwaysMatch + when {:matchAnyTable = "fallback"} + matchAnyTable else - "should not reach here" + "should not reach here unless it is not a table" nil diff --git a/spec/outputs/switch.lua b/spec/outputs/switch.lua index 03a0d37..7b413f8 100644 --- a/spec/outputs/switch.lua +++ b/spec/outputs/switch.lua @@ -182,8 +182,8 @@ do local x = item.x local y = item.y if x ~= nil and y ~= nil then - print("Vec2 " .. tostring(x) .. ", " .. tostring(y)) _match_0 = true + print("Vec2 " .. tostring(x) .. ", " .. tostring(y)) end end if not _match_0 then @@ -192,8 +192,8 @@ do local width = item.width local height = item.height if width ~= nil and height ~= nil then - print("Size " .. tostring(width) .. ", " .. tostring(height)) _match_1 = true + print("Size " .. tostring(width) .. ", " .. tostring(height)) end end if not _match_1 then @@ -204,12 +204,12 @@ do if _tab_0 then local cls = item.__class if cls ~= nil then + _match_2 = true if ClassA == cls then print("Object A") elseif ClassB == cls then print("Object B") end - _match_2 = true end end if not _match_2 then @@ -241,9 +241,7 @@ do if b == nil then b = 2 end - if a ~= nil and b ~= nil then - print(a, b) - end + print(a, b) end end end @@ -258,8 +256,8 @@ do local x = tb.x local y = tb.y if x ~= nil and y ~= nil then - print("x: " .. tostring(x) .. " with y: " .. tostring(y)) _match_0 = true + print("x: " .. tostring(x) .. " with y: " .. tostring(y)) end end if not _match_0 then @@ -284,8 +282,8 @@ do if _tab_0 then local x = _exp_0.x if x ~= nil then - matched = x _match_0 = true + matched = x end end if not _match_0 then @@ -310,8 +308,8 @@ do local a = _exp_0.a local b = _exp_0.b if a ~= nil and b ~= nil then - return tostring(a + b) _match_0 = true + return tostring(a + b) end end if not _match_0 then @@ -319,15 +317,13 @@ do return "number 1 - 5" else if _tab_0 then - local alwaysMatch = _exp_0.alwaysMatch - if alwaysMatch == nil then - alwaysMatch = "fallback" - end - if alwaysMatch ~= nil then - return alwaysMatch + local matchAnyTable = _exp_0.matchAnyTable + if matchAnyTable == nil then + matchAnyTable = "fallback" end + return matchAnyTable else - return "should not reach here" + return "should not reach here unless it is not a table" end end end diff --git a/src/yuescript/yue_compiler.cpp b/src/yuescript/yue_compiler.cpp index c68746c..2565878 100755 --- a/src/yuescript/yue_compiler.cpp +++ b/src/yuescript/yue_compiler.cpp @@ -56,7 +56,7 @@ using namespace parserlib; typedef std::list str_list; -const std::string_view version = "0.13.3"sv; +const std::string_view version = "0.13.4"sv; const std::string_view extension = "yue"sv; class YueCompilerImpl { @@ -1582,12 +1582,22 @@ private: added--; } if (pair.defVal) { - auto stmt = toAst(pair.targetVar + "=nil if "s + pair.targetVar + "==nil", pair.defVal); - auto defAssign = stmt->content.as(); - auto assign = defAssign->action.as(); - assign->values.clear(); - assign->values.push_back(pair.defVal); - transformStatement(stmt, temp); + bool isNil = false; + if (auto v1 = singleValueFrom(pair.defVal)) { + if (auto v2 = v1->item.as()) { + if (auto v3 = v2->value.as()) { + isNil = _parser.toString(v3) == "nil"sv; + } + } + } + if (!isNil) { + auto stmt = toAst(pair.targetVar + "=nil if "s + pair.targetVar + "==nil", pair.defVal); + auto defAssign = stmt->content.as(); + auto assign = defAssign->action.as(); + assign->values.clear(); + assign->values.push_back(pair.defVal); + transformStatement(stmt, temp); + } } continue; } @@ -1709,12 +1719,22 @@ private: } for (const auto& item : destruct.items) { if (item.defVal) { - auto stmt = toAst(item.targetVar + "=nil if "s + item.targetVar + "==nil", item.defVal); - auto defAssign = stmt->content.as(); - auto assign = defAssign->action.as(); - assign->values.clear(); - assign->values.push_back(item.defVal); - transformStatement(stmt, temp); + bool isNil = false; + if (auto v1 = singleValueFrom(item.defVal)) { + if (auto v2 = v1->item.as()) { + if (auto v3 = v2->value.as()) { + isNil = _parser.toString(v3) == "nil"sv; + } + } + } + if (!isNil) { + auto stmt = toAst(item.targetVar + "=nil if "s + item.targetVar + "==nil", item.defVal); + auto defAssign = stmt->content.as(); + auto assign = defAssign->action.as(); + assign->values.clear(); + assign->values.push_back(item.defVal); + transformStatement(stmt, temp); + } } } for (const auto& item : leftPairs) { @@ -7160,32 +7180,34 @@ private: auto assignment = assignmentFrom(static_cast(branch->valueList->exprs.front()), toAst(objVar, branch), branch); auto info = extractDestructureInfo(assignment, true, false); transformAssignment(assignment, temp, true); - temp.push_back(indent() + "if"s); - bool firstItem = true; + str_list conds; for (const auto& destruct : info.first) { for (const auto& item : destruct.items) { - str_list tmp; - transformExp(item.target, tmp, ExpUsage::Closure); - temp.back().append((firstItem ? " " : " and "s) + tmp.back() + " ~= nil"s); - if (firstItem) firstItem = false; + if (!item.defVal) { + transformExp(item.target, conds, ExpUsage::Closure); + conds.back().append(" ~= nil"s); + } } } - temp.back().append(" then"s + nll(branch)); - pushScope(); - transform_plain_body(branch->body, temp, usage, assignList); + if (!conds.empty()) { + temp.push_back(indent() + "if "s + join(conds, " and "sv) + " then"s + nll(branch)); + pushScope(); + } if (!lastBranch) { temp.push_back(indent() + matchVar + " = true"s + nll(branch)); } - popScope(); - if (!lastBranch) { + transform_plain_body(branch->body, temp, usage, assignList); + if (!conds.empty()) { + popScope(); temp.push_back(indent() + "end"s + nll(branch)); + } + if (!lastBranch) { popScope(); temp.push_back(indent() + "end"s + nll(branch)); temp.push_back(indent() + "if not "s + matchVar + " then"s + nll(branch)); pushScope(); addScope++; } else { - temp.push_back(indent() + "end"s + nll(branch)); popScope(); } firstBranch = true; -- cgit v1.2.3-55-g6feb