From eb5fd951efb47f4dc4f26f7eb20c67b7319589fb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 28 Apr 2021 17:28:43 +0000 Subject: [PATCH 1/6] Merge docs for 'Carpet Mod 1.4.34 for Minecraft 1.16.5 and Minecraft 21w17a' --- docs/scarpet/Full.md | 73 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 66 insertions(+), 7 deletions(-) diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md index e4084313..c7b9209e 100644 --- a/docs/scarpet/Full.md +++ b/docs/scarpet/Full.md @@ -356,11 +356,12 @@ Here is the complete list of operators in `scarpet` including control flow opera are not technically operators, but part of the language, even if they look like them: * Match, Get `~ :` -* Unary `+ - !` +* Unary `+ - ! ...` * Exponent `^` * Multiplication `* / %` * Addition `+ -` -* Comparison `== != > >= <= <` +* Comparison `> >= <= <` +* Equality `== !=` * Logical And`&&` * Logical Or `||` * Assignment `= += <>` @@ -561,6 +562,45 @@ flips boolean condition of the expression. Equivalent of `bool(expr)==false` !l() => true !l(null) => false + +### `Unpacking Operator ...` + +Unpacks elements of a list of an iterator into a sequence of arguments in a function making so that `fun(...[1, 2, 3])` is +identical with `fun(1, 2, 3)`. For maps, it unpacks them to a list of key-value pairs. + +In function signatures it identifies a vararg parameter. + +
+fun(a, b, ... rest) -> [a, b, rest]; fun(1, 2, 3, 4)    => [1, 2, [3, 4]]
+
+ +Effects of `...` can be surprisingly lasting. It is kept through the use of variables and function calls. + +
+fun(a, b, ... rest) -> [a, b, ... rest]; fun(1, 2, 3, 4)    => [1, 2, 3, 4]
+args() -> ... [1, 2, 3]; sum(a, b, c) -> a+b+c; sum(args())   => 6
+a = ... [1, 2, 3]; sum(a, b, c) -> a+b+c; sum(a)   => 6
+
+ +Unpacking mechanics can be used for list and map constriction, not just for function calls. + +
+[...range(5), pi, ...range(5,-1,-1)]   => [0, 1, 2, 3, 4, 3.14159265359, 5, 4, 3, 2, 1, 0]
+{ ... map(range(5),  _  -> _*_ )}   => {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
+{...{1 -> 2, 3 -> 4}, ...{5 -> 6, 7 -> 8}}   => {1: 2, 3: 4, 5: 6, 7: 8}
+
+ +Fine print: unpacking of argument lists happens just before functions are evaluated. +This means that in some situations, for instance +when an expression is expected (`map(list, expr)`), or a function should not evaluate some (most!) of its arguments (`if(...)`), +unpacking cannot be used, and will be ignored, leaving `... list` identical to `list`. +Functions that don't honor unpacking mechanics, should have no use for it at the first place + (i.e. have one, or very well-defined, and very specific parameters), +so some caution (prior testing) is advised. Some of these multi-argument built-in functions are + `if`, `try`, `sort_key`, `system_variable_get`, `synchronize`, `sleep`, `in_dimension`, +all container functions (`get`, `has`, `put`, `delete`), +and all loop functions (`while`, `loop`, `map`, `filter`, `first`, `all`, `c_for`, `for` and`reduce`). + # Arithmetic operations ## Basic Arithmetic Functions @@ -772,7 +812,7 @@ are not thread safe and require the execution to park on the server thread, wher the off-tick time in between ticks that didn't take all 50ms. There are however benefits of running things in parallel, like fine time control not relying on the tick clock, or running things independent on each other. You can still run your actions on tick-by-tick basis, either taking control of the execution using `game_tick()` API function -(nasty solution), or scheduling tick using `schedule()` function (preffered solution), but threading gives much more control +(nasty solution), or scheduling tick using `schedule()` function (preferred solution), but threading gives much more control on the timings without impacting the main game and is the only solution to solve problems in parallel (see [scarpet camera](/src/main/resources/assets/carpet/scripts/camera.sc)). @@ -786,7 +826,10 @@ effectively parallelized. If the app is shutting down, creating new tasks via `task` will not succeed. Instead the new task will do nothing and return `null`, so most threaded application should handle closing apps naturally. Keep in mind in case you rely on task return values, -that they will return `null` regardless of anything in these situations. +that they will return `null` regardless of anything in these situations. When app handles `__on_close()` event, new tasks cannot +be submitted at this point, but current tasks are not terminated. Apps can use that opportunity to gracefully shutdown their tasks. +Regardless if the app handles `__on_close()` event, or does anything with their tasks in it, all tasks will be terminated exceptionally +within the next 1.5 seconds. ### `task(function, ... args)`, `task_thread(executor, function, ... args)` @@ -1030,6 +1073,15 @@ This will give you a date: It is currently `hrs`:`mins` and `secs` seconds on the `date`th of `month`, `year` +### `encode_b64(string)`, `decode_b64(string)` + +Encode or decode a string from b64, throwing a `b64_error` exception if it's invalid + +### `encode_json(value)`, `decode_json(string)` + +Encodes a value as a json string, and decodes a json string as a valid value, throwing a `json_error` exception if it +doesn't parse properly + ### `profile_expr(expression)` Returns number of times given expression can be run in 50ms time. Useful to profile and optimize your code. @@ -1247,6 +1299,7 @@ it will be used from now on as a new value for the accumulator. reduce([1,2,3,4],_a+_,0) => 10 reduce([1,2,3,4],_a*_,1) => 24 + # User-defined functions and program control flow ## Writing programs with more than 1 line @@ -1516,6 +1569,7 @@ but like everywhere else, doing that sounds like a bad idea. - `io_exception`: This is the parent for any exception that occurs due to an error handling external data. - `nbt_error`: Incorrect input/output NBT file. - `json_error`: Incorrect input/output JSON data. + - `b64_error`: Incorrect input/output b64 (base 64) string - `user_exception`: Exception thrown by default with `throw` function. Synopsis: @@ -3448,6 +3502,10 @@ Numbers related to player's xp. `xp` is the overall xp player has, `xp_level` is Number indicating remaining entity health, or `null` if not applicable. +### `query(e, 'language')` + +Returns `null` for any non-player entity, if not returns the player's language as a string. + ### `query(e, 'holds', slot?)` Returns triple of short name, stack count, and NBT of item held in `slot`, or `null` if nothing or not applicable. Available options for `slot` are: @@ -4723,7 +4781,8 @@ per shape and last whatever they typically last in the game. Shapes can be send one by one, using either of the first three invocations, or batched as a list of shape descriptors. Batching has this benefit that they will be send possibly as one packet, limiting network overhead of -sending many small packets to draw several shapes at once. +sending many small packets to draw several shapes at once. The drawback of sending shapes is batches is that they need to address +the same list of players, i.e. if multiple players from the list target different players, all shapes will be sent to all of them. Shapes will fail to draw and raise a runtime error if not all its required parameters are specified and all available shapes have some parameters that are required, so make sure to have them in place: @@ -4736,8 +4795,8 @@ or simply refresh the shapes periodically in more dynamic applications. Optional shared shape attributes: * `color` - integer value indicating the main color of the shape in the form of red, green, blue and alpha components in the form of `0xRRGGBBAA`, with the default of `-1`, so white opaque, or `0xFFFFFFFF`. - * `player` - name or player entity to send the shape to. If specified, the shapes will appear only for the specified - player, otherwise it will be send to all players in the dimension. + * `player` - name or player entity to send the shape to, or a list of players. If specified, the shapes will appear only for the specified + players (regardless where they are), otherwise it will be send to all players in the current dimension. * `line` - (Deprecated) line thickness, defaults to 2.0pt. Not supported in 1.17's 3.2 core GL renderer. * `fill` - color for the faces, defaults to no fill. Use `color` attribute format * `follow` - entity, or player name. Shape will follow an entity instead of being static. From 6eee47ec1469fc19b5360efbe69594bccb7b3879 Mon Sep 17 00:00:00 2001 From: whats2000 <60466660+whats2000@users.noreply.github.com> Date: Sun, 2 May 2021 23:22:44 +0800 Subject: [PATCH 2/6] Update zh_tw.json Add new translation to support the latest version --- .../resources/assets/carpet/lang/zh_tw.json | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/main/resources/assets/carpet/lang/zh_tw.json b/src/main/resources/assets/carpet/lang/zh_tw.json index f33a8d40..a9884942 100644 --- a/src/main/resources/assets/carpet/lang/zh_tw.json +++ b/src/main/resources/assets/carpet/lang/zh_tw.json @@ -240,8 +240,56 @@ "rule.spawnChunksSize.name": "出生點永加載區塊半徑", "rule.spawnChunksSize.desc": "更改出生點永加載區塊半徑", "rule.spawnChunksSize.extra.0": "定義新半徑\n設為0以禁用出生點區塊加載", + + "rule.chainStone.name": "強化鎖鏈", + "rule.chainStone.desc": "長條鎖鏈在末端間將會相互連接", + "rule.chainStone.extra.0": "並且也會與直接連接的方塊連接\n當啟用 stick_to_all : 將無視視覺上連接狀態,使全部連接", + "rule.piglinsSpawningInBastions.name": "豬布林堡壘生成", + "rule.piglinsSpawningInBastions.desc": "豬布林將在堡壘遺跡生成", + "rule.piglinsSpawningInBastions.extra.0": "包括 : 豬布林,蠻兵,及一些豬布獸", + "rule.renewableBlackstone.name": "可再生黑石", + "rule.renewableBlackstone.desc": "地獄玄武岩生成機,但在下方沒有靈魂沙", + "rule.renewableBlackstone.extra.0": "..將會轉換成黑石", + + "rule.lightEngineMaxBatchSize.name": "光照引擎最大批次大小", + "rule.lightEngineMaxBatchSize.desc": "更改光照引擎最大批次大小", + "rule.lightEngineMaxBatchSize.extra.0": "允許更高光照抑制容限", + "rule.lightEngineMaxBatchSize.extra.1": "設置成5-遊戲預設容限", + + "rule.cleanLogs.name": "清除數據追蹤器", + "rule.cleanLogs.desc": "從數據追蹤器中刪除令人討厭的信息", + "rule.cleanLogs.extra.0": "在'達到最大聲音對象池247'時將不再顯示", + "rule.cleanLogs.extra.1": "在一般農場與裝置是正常顯示", + + "rule.structureBlockOutlineDistance.name": "結構方塊外框顯示距離", + "rule.structureBlockOutlineDistance.desc": "自訂義結構方塊外框顯示距離", + "rule.structureBlockOutlineDistance.extra.0": "需要客戶端支持才能正常工作", + + "rule.superSecretSetting.name": "最高機密設置", + + "rule.lightningKillsDropsFix.name": "閃電破壞掉落物修正", + "rule.lightningKillsDropsFix.desc": "閃電會破壞自閃電擊殺實體的掉落物", + "rule.lightningKillsDropsFix.extra.0": "設置成true來防止掉落物被閃電破壞", + "rule.lightningKillsDropsFix.extra.1": "修正[MC-206922](https://bugs.mojang.com/browse/MC-206922)", + + "rule.pingPlayerListLimit.name": "玩家ping列表數量限制", + "rule.pingPlayerListLimit.desc": "自訂義伺服器ping列表(多人遊戲菜單)玩家列表樣本限制", + + "rule.structureBlockIgnored.name": "結構方塊變更無視方塊", + "rule.structureBlockIgnored.desc": "變更被結構方塊無視的方塊", + + "rule.structureBlockLimit.name": "結構方塊上限更改", + "rule.structureBlockLimit.desc": "自訂義結構方塊每個方位距離上限", + "rule.structureBlockLimit.extra.0": "警告 : 必須是永久的才能正確加載", + "rule.structureBlockLimit.extra.1": "建議設置'結構方塊變更無視方塊'成空氣(air)", + "rule.structureBlockLimit.extra.2": "當儲存大量結構時", + "rule.structureBlockLimit.extra.3": "需要客戶端玩家編輯結構方塊", + "rule.structureBlockLimit.extra.4": "當渲染很長的結構時,\n'結構方塊外框顯示距離'將會是必要開啟的", + + + "category.bugfix": "BUG修復", "category.survival": "生存用", "category.creative": "創造用", From c465976609b3a6fefe46f201e89013ce8e16c9ba Mon Sep 17 00:00:00 2001 From: whats2000 <60466660+whats2000@users.noreply.github.com> Date: Sun, 2 May 2021 23:35:23 +0800 Subject: [PATCH 3/6] Update zh_tw.json small fix --- src/main/resources/assets/carpet/lang/zh_tw.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/assets/carpet/lang/zh_tw.json b/src/main/resources/assets/carpet/lang/zh_tw.json index a9884942..95993dc3 100644 --- a/src/main/resources/assets/carpet/lang/zh_tw.json +++ b/src/main/resources/assets/carpet/lang/zh_tw.json @@ -241,7 +241,7 @@ "rule.spawnChunksSize.desc": "更改出生點永加載區塊半徑", "rule.spawnChunksSize.extra.0": "定義新半徑\n設為0以禁用出生點區塊加載", - "rule.chainStone.name": "強化鎖鏈", + "rule.chainStone.name": "強化鎖鏈", "rule.chainStone.desc": "長條鎖鏈在末端間將會相互連接", "rule.chainStone.extra.0": "並且也會與直接連接的方塊連接\n當啟用 stick_to_all : 將無視視覺上連接狀態,使全部連接", From 659ad2408eceac4c64883e935c74ee52f391dcc4 Mon Sep 17 00:00:00 2001 From: replaceitem Date: Tue, 4 May 2021 20:26:20 +0200 Subject: [PATCH 4/6] Fixed boolean value of FormattedText being incorrect --- src/main/java/carpet/script/value/FormattedTextValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/carpet/script/value/FormattedTextValue.java b/src/main/java/carpet/script/value/FormattedTextValue.java index e6225e1d..c237d7ec 100644 --- a/src/main/java/carpet/script/value/FormattedTextValue.java +++ b/src/main/java/carpet/script/value/FormattedTextValue.java @@ -54,7 +54,7 @@ public class FormattedTextValue extends StringValue @Override public boolean getBoolean() { - return !text.asString().isEmpty(); + return !text.getString().isEmpty(); } @Override From 8d97ab26a99fb86898b513cbcb3d8c1529457631 Mon Sep 17 00:00:00 2001 From: gnembon Date: Wed, 5 May 2021 00:25:31 -0300 Subject: [PATCH 5/6] script compile time optimizations. --- docs/scarpet/api/Overview.md | 14 +- .../language/FunctionsAndControlFlow.md | 9 +- docs/scarpet/language/Operators.md | 26 +- docs/scarpet/resources/editors/idea/1.txt | 13 + .../scarpet/resources/editors/npp/scarpet.xml | 1 + src/main/java/carpet/CarpetSettings.java | 13 + src/main/java/carpet/script/Context.java | 26 +- src/main/java/carpet/script/Expression.java | 650 ++++++++++++++++-- src/main/java/carpet/script/Fluff.java | 83 ++- src/main/java/carpet/script/LazyValue.java | 49 +- src/main/java/carpet/script/ScriptHost.java | 5 - src/main/java/carpet/script/Tokenizer.java | 24 +- .../java/carpet/script/api/Auxiliary.java | 2 +- .../java/carpet/script/api/Threading.java | 2 +- .../carpet/script/language/Arithmetic.java | 3 +- .../carpet/script/language/ControlFlow.java | 15 +- .../script/language/DataStructures.java | 2 +- .../carpet/script/language/Functions.java | 94 ++- .../java/carpet/script/language/Loops.java | 4 +- .../carpet/script/language/Operators.java | 182 ++++- src/main/java/carpet/script/language/Sys.java | 2 +- .../carpet/script/language/Threading.java | 4 +- .../script/value/AbstractListValue.java | 5 + .../carpet/script/value/FunctionValue.java | 19 +- .../carpet/script/value/LazyListValue.java | 5 + .../script/value/NBTSerializableValue.java | 5 + src/main/java/carpet/script/value/Value.java | 7 + 27 files changed, 1071 insertions(+), 193 deletions(-) diff --git a/docs/scarpet/api/Overview.md b/docs/scarpet/api/Overview.md index 3f62e3d5..88bacaac 100644 --- a/docs/scarpet/api/Overview.md +++ b/docs/scarpet/api/Overview.md @@ -1,7 +1,19 @@ # Minecraft specific API and `scarpet` language add-ons and commands -Here is the gist of the Minecraft related functions. Otherwise the CarpetScript could live without Minecraft. +Here is the gist of the Minecraft related functions. Otherwise the scarpet could live without Minecraft. +## Global scarpet options + +These options affect directly how scarpet functions and can be triggered via `/carpet` command. + - `commandScript`: disables `/script` command making it impossible to control apps in game. Apps will still load and run + when loaded with the world (i.e. present in the world/scripts folder) + - `scriptsAutoload`: when set to `false` will prevent apps loaded with the world to load automatically. You can still + load them on demand via `/script load` command + - `commandScriptACE`: command permission level that is used to trigger commands from scarpet scripts (regardless who triggeres + the code that calls the command). Defaults to `ops`, could be customized to any level via a numerical value (0, 1, 2, 3 or 4) + - `scriptsOptimization`: when disabled, disables default app compile time optimizations. If your app behaves differently with + and without optimizations, please file a bug report on the bug tracker and disable code optimizations + - `scriptsDebugging`: Puts detailed information about apps loading, performance and runtime in system log. ## App structure diff --git a/docs/scarpet/language/FunctionsAndControlFlow.md b/docs/scarpet/language/FunctionsAndControlFlow.md index f60aa07e..988a825c 100644 --- a/docs/scarpet/language/FunctionsAndControlFlow.md +++ b/docs/scarpet/language/FunctionsAndControlFlow.md @@ -2,7 +2,7 @@ ## Writing programs with more than 1 line -### `Operator ;` +### Operator `;`, `then(...)` To effectively write programs that have more than one line, a programmer needs way to specify a sequence of commands that execute one after another. In `scarpet` this can be achieved with `;`. Its an operator, and by separating @@ -22,7 +22,7 @@ in `scarpet`, and not barely an instruction delimiter, terminating the code with be valid. Having said that, since many programming languages don't care about the number of op terminators programmers use, carpet preprocessor will remove all unnecessary semicolons from scripts when compiled. -In general `expr; expr; expr; expr` is equivalent to `(((expr ; expr) ; expr) ; expr)`. +In general `expr; expr; expr; expr` is equivalent to `(((expr ; expr) ; expr) ; expr)` or `then(expr, expr, expr, expr)`. Result of the evaluated expression is the same as the result of the second expression, but first expression is also evaluated for side-effects @@ -132,15 +132,16 @@ foo(... x) -> ... # all arguments for foo are included in the list -### `import(module_name, symbols ...)` +### `import(module_name, ? symbols ...)` Imports symbols from other apps and libraries into the current one: global variables or functions, allowing to use them in the current app. This includes other symbols imported by these modules. Scarpet supports circular dependencies, but if symbols are used directly in the module body rather than functions, it may not be able to retrieve them. + Returns full list of available symbols that could be imported from this module, which can be used to debug import issues, and list contents of libraries. -### `call(function, args.....)` +### `call(function, ? args ...)` calls a user defined function with specified arguments. It is equivalent to calling `function(args...)` directly except you can use it with function value, or name instead. This means you can pass functions to other user defined diff --git a/docs/scarpet/language/Operators.md b/docs/scarpet/language/Operators.md index cd6860d7..b403d4d7 100644 --- a/docs/scarpet/language/Operators.md +++ b/docs/scarpet/language/Operators.md @@ -6,6 +6,12 @@ as `2+(2*2)`, not `(2+2)*2`, otherwise they are applied from left to right, i.e. as `(2+4)-3`, which in case of numbers doesn't matter, but since `scarpet` allows for mixing all value types the associativity would matter, and may lead to unintended effects: +Operators can be unary - with one argument prefixed by the operator (like `-`, `!`, `...`), "practically binary" (that +clearly have left and right operands, like assignment `=` operator), and "technically binary" (all binary operators have left and +right hand, but can be frequently chained together, like `1+2+3`). All "technically binary" operators (where chaining makes sense) +have their functional counterparts, e.g. `1+2+3` is equivalent to `sum(1, 2, 3)`. Functional and operatoral forms are directly +equivalent - they actually will result in the same code as scarpet will optimize long operator chains into their optimized functional forms. + Important operator is function definition `->` operator. It will be covered in [User Defined Functions and Program Control Flow](docs/scarpet/language/FunctionsAndControlFlow.md) @@ -118,7 +124,7 @@ $); /script run global_get_enchantment(player(), 'sharpness') -### `Basic Arithmetic Operators + - * /` +### Basic Arithmetic Operators `+`, `sum(...)`, `-`, `difference(...)`, `*`, `product(...)`, `/`, `quotient(...)` Allows to add the results of two expressions. If the operands resolve to numbers, the result is arithmetic operation. In case of strings, adding or subtracting from a string results in string concatenation and removal of substrings @@ -134,6 +140,9 @@ Addition with maps (`{}` or `m()`) results in a new map with keys from both maps adding elements of the right argument to the keys, of left map, or just adding the right value as a new key in the output map. +Functional forms of `-` and `/` have less intuitive multi-nary interpretation, but they might be useful in some situations. +`x-y-z` resolves to `difference(x, y, z)`. + Examples:
@@ -149,7 +158,7 @@ b = [100,63,100]; b+[10,0,10]  => [110,63,110]
 {'a' -> 1} + {'b' -> 2} => {'a' -> 1, 'b' -> 2}
 
-### `Just Operators % ^` +### Just Operators `%`, `^` The modulo and exponent (power) operators work only if both operands are numbers @@ -160,11 +169,11 @@ The modulo and exponent (power) operators work only if both operands are numbers -3 ^ pi => // Error -### `Comparison Operators == != < > <= >=` +### Comparison Operators `==`, `equal()`, `!=`, `unique()`, `<`, `increasing()`, `>`, `decreasing()`, `<=`, `nondecreasing()`, `>=`, `nonincreasing()` -Allows to compare the results of two expressions. For numbers it is considers arithmetic order of numbers, for +Allows to compare the results of two expressions. For numbers, it considers arithmetic order of numbers, for strings - lexicographical, nulls are always 'less' than everything else, and lists check their elements - -if the sizes are different, the size matters, otherwise, pairwise comparisons for each elements are performed. +if the sizes are different, the size matters, otherwise, pairwise comparisons for each element are performed. The same order rules than with all these operators are used with the default sortographical order as used by `sort` function. All of these are true: @@ -180,7 +189,12 @@ null < -1000 3 == 3.0 -### `Logical Operators && ||` +Functional variants of these operators allow to assert certain paradigms on multiple arguments at once. This means that +due to formal equivalence `x < y < z` is equivalent to `x < y & y < z` because of direct mapping to `increasing(x, y, z)`. This translates through +the parentheses, so `((x < y) < z)` is the same as `increasing(x, y, z)`. To achieve the same effect as you would see in other + languages (not python), you would need to cast the first pair to boolean value, i.e. `bool(x < y) < z`. + +### Logical Operators `&&`, `and(...)`, `||`, `or(...)` These operator compute respective boolean operation on the operands. What it important is that if calculating of the second operand is not necessary, it won't be evaluated, which means one can use them as conditional statements. In diff --git a/docs/scarpet/resources/editors/idea/1.txt b/docs/scarpet/resources/editors/idea/1.txt index e83e03da..bae7a5b3 100644 --- a/docs/scarpet/resources/editors/idea/1.txt +++ b/docs/scarpet/resources/editors/idea/1.txt @@ -3,6 +3,7 @@ acos acosh acot all +and asin asinh atan @@ -21,8 +22,11 @@ cot coth csc csch +decreasing deg delete +difference +equal exit fact filter @@ -34,6 +38,7 @@ has hash_code if import +increasing join keys l @@ -48,14 +53,19 @@ mandelbrot map max min +nondecreasing +nonincreasing not number +or outer pairs perlin print +product profile_expr put +quotient rad rand range @@ -75,6 +85,7 @@ sleep slice sort sort_key +sum synchronize system_variable_get system_variable_set @@ -90,12 +101,14 @@ task_dock task_join task_thread task_value +then throw time title try type undef +unique upper values var diff --git a/docs/scarpet/resources/editors/npp/scarpet.xml b/docs/scarpet/resources/editors/npp/scarpet.xml index 93992ec5..7a8f00c4 100644 --- a/docs/scarpet/resources/editors/npp/scarpet.xml +++ b/docs/scarpet/resources/editors/npp/scarpet.xml @@ -31,6 +31,7 @@ log log10 log1p sqrt max min abs round floor ceil + then sum difference product quotient equal unique increasing decreasing nonincreasing nondecreasing or and mandelbrot relu perlin simplex diff --git a/src/main/java/carpet/CarpetSettings.java b/src/main/java/carpet/CarpetSettings.java index b665f8ce..5316456b 100644 --- a/src/main/java/carpet/CarpetSettings.java +++ b/src/main/java/carpet/CarpetSettings.java @@ -443,6 +443,19 @@ public class CarpetSettings ) public static boolean scriptsAutoload = true; + @Rule( + desc = "Enables scripts debugging messages in system log", + category = SCARPET + ) + public static boolean scriptsDebugging = false; + + @Rule( + desc = "Enables scripts optimization", + category = SCARPET + ) + public static boolean scriptsOptimization = true; + + @Rule(desc = "Enables /player command to control/spawn players", category = COMMAND) public static String commandPlayer = "ops"; diff --git a/src/main/java/carpet/script/Context.java b/src/main/java/carpet/script/Context.java index fb9b583e..17bcd2c6 100644 --- a/src/main/java/carpet/script/Context.java +++ b/src/main/java/carpet/script/Context.java @@ -8,17 +8,21 @@ import java.util.Set; public class Context { - public static final int NONE = 0; - public static final int VOID = 1; - public static final int BOOLEAN = 2; - public static final int NUMBER = 3; - public static final int STRING = 4; - public static final int LIST = 5; - public static final int ITERATOR = 6; - public static final int SIGNATURE = 7; - public static final int LOCALIZATION = 8; - public static final int LVALUE = 9; - public static final int MAPDEF = 10; + public enum Type + { + NONE, VOID, BOOLEAN, NUMBER, STRING, LIST, ITERATOR, SIGNATURE, LOCALIZATION, LVALUE, MAPDEF + } + public static final Type NONE = Type.NONE; + public static final Type VOID = Type.VOID; + public static final Type BOOLEAN = Type.BOOLEAN; + public static final Type NUMBER = Type.NUMBER; + public static final Type STRING = Type.STRING; + public static final Type LIST = Type.LIST; + public static final Type ITERATOR = Type.ITERATOR; + public static final Type SIGNATURE = Type.SIGNATURE; + public static final Type LOCALIZATION = Type.LOCALIZATION; + public static final Type LVALUE = Type.LVALUE; + public static final Type MAPDEF = Type.MAPDEF; public Map variables = new HashMap<>(); diff --git a/src/main/java/carpet/script/Expression.java b/src/main/java/carpet/script/Expression.java index d0ccf197..9b2c1424 100644 --- a/src/main/java/carpet/script/Expression.java +++ b/src/main/java/carpet/script/Expression.java @@ -1,5 +1,6 @@ package carpet.script; +import carpet.CarpetSettings; import carpet.script.Fluff.AbstractFunction; import carpet.script.Fluff.AbstractLazyFunction; import carpet.script.Fluff.AbstractLazyOperator; @@ -32,6 +33,8 @@ import carpet.script.value.FunctionValue; import carpet.script.value.NumericValue; import carpet.script.value.StringValue; import carpet.script.value.Value; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.math.BigInteger; @@ -45,6 +48,7 @@ import java.util.Stack; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; +import java.util.stream.Collectors; import static java.lang.Math.max; import static java.lang.Math.min; @@ -86,6 +90,27 @@ public class Expression private final Map functions = new Object2ObjectOpenHashMap<>(); public Set getFunctionNames() {return functions.keySet();} + private final Map functionalEquivalence = new Object2ObjectOpenHashMap<>(); + public void addFunctionalEquivalence(String operator, String function) + { + assert operators.containsKey(operator); + assert functions.containsKey(function); + functionalEquivalence.put(operator, function); + } + + private final Map constants = ImmutableMap.of( + "euler", Arithmetic.euler, + "pi", Arithmetic.PI, + "null", Value.NULL, + "true", Value.TRUE, + "false", Value.FALSE + ); + + protected Value getConstantFor(String surface) + { + return constants.get(surface); + } + public List getExpressionSnippet(Tokenizer.Token token) { String code = this.getCodeString(); @@ -139,13 +164,28 @@ public class Expression } - public void addLazyUnaryOperator(String surface, int precedence, boolean leftAssoc, - TriFunction lazyfun) + public void addLazyUnaryOperator(String surface, int precedence, boolean leftAssoc, boolean pure, Function staticTyper, + TriFunction lazyfun) { operators.put(surface+"u", new AbstractLazyOperator(precedence, leftAssoc) { @Override - public LazyValue lazyEval(Context c, Integer t, Expression e, Tokenizer.Token token, LazyValue v, LazyValue v2) + public boolean pure() { + return pure; + } + + @Override + public boolean transitive() { + return false; + } + + @Override + public Context.Type staticType(Context.Type outerType) { + return staticTyper.apply(outerType); + } + + @Override + public LazyValue lazyEval(Context c, Context.Type t, Expression e, Tokenizer.Token token, LazyValue v, LazyValue v2) { try { @@ -160,13 +200,23 @@ public class Expression } - public void addLazyBinaryOperatorWithDelegation(String surface, int precedence, boolean leftAssoc, - SexFunction lazyfun) + public void addLazyBinaryOperatorWithDelegation(String surface, int precedence, boolean leftAssoc, boolean pure, + SexFunction lazyfun) { operators.put(surface, new AbstractLazyOperator(precedence, leftAssoc) { @Override - public LazyValue lazyEval(Context c, Integer type, Expression e, Tokenizer.Token t, LazyValue v1, LazyValue v2) + public boolean pure() { + return pure; + } + + @Override + public boolean transitive() { + return false; + } + + @Override + public LazyValue lazyEval(Context c, Context.Type type, Expression e, Tokenizer.Token t, LazyValue v1, LazyValue v2) { try { @@ -180,13 +230,28 @@ public class Expression }); } - public void addLazyFunctionWithDelegation(String name, int numpar, - QuinnFunction, LazyValue> lazyfun) + public void addCustomFunction(String name, ILazyFunction fun) + { + functions.put(name, fun); + } + + public void addLazyFunctionWithDelegation(String name, int numpar, boolean pure, boolean transitive, + QuinnFunction, LazyValue> lazyfun) { functions.put(name, new AbstractLazyFunction(numpar, name) { @Override - public LazyValue lazyEval(Context c, Integer type, Expression e, Tokenizer.Token t, List lv) + public boolean pure() { + return pure; + } + + @Override + public boolean transitive() { + return transitive; + } + + @Override + public LazyValue lazyEval(Context c, Context.Type type, Expression e, Tokenizer.Token t, List lv) { ILazyFunction.checkInterrupts(); try @@ -201,13 +266,23 @@ public class Expression }); } - public void addFunctionWithDelegation(String name, int numpar, - QuinnFunction, Value> fun) + public void addFunctionWithDelegation(String name, int numpar, boolean pure, boolean transitive, + QuinnFunction, Value> fun) { functions.put(name, new AbstractLazyFunction(numpar, name) { @Override - public LazyValue lazyEval(Context c, Integer type, Expression e, Tokenizer.Token t, List lv) + public boolean pure() { + return pure; + } + + @Override + public boolean transitive() { + return transitive; + } + + @Override + public LazyValue lazyEval(Context c, Context.Type type, Expression e, Tokenizer.Token t, List lv) { try { @@ -222,13 +297,30 @@ public class Expression }); } - public void addLazyBinaryOperator(String surface, int precedence, boolean leftAssoc, - QuadFunction lazyfun) + public void addLazyBinaryOperator(String surface, int precedence, boolean leftAssoc, boolean pure, Function typer, + QuadFunction lazyfun) { operators.put(surface, new AbstractLazyOperator(precedence, leftAssoc) { + @Override - public LazyValue lazyEval(Context c, Integer t, Expression e, Tokenizer.Token token, LazyValue v1, LazyValue v2) + public boolean pure() { + return pure; + } + + @Override + public boolean transitive() { + return false; + } + + @Override + public Context.Type staticType(Context.Type outerType) + { + return typer.apply(outerType); + } + + @Override + public LazyValue lazyEval(Context c, Context.Type t, Expression e, Tokenizer.Token token, LazyValue v1, LazyValue v2) { ILazyFunction.checkInterrupts(); try @@ -243,13 +335,23 @@ public class Expression }); } - public void addBinaryContextOperator(String surface, int precedence, boolean leftAssoc, - QuadFunction fun) + public void addBinaryContextOperator(String surface, int precedence, boolean leftAssoc, boolean pure, boolean transitive, + QuadFunction fun) { operators.put(surface, new AbstractLazyOperator(precedence, leftAssoc) { @Override - public LazyValue lazyEval(Context c, Integer t, Expression e, Tokenizer.Token token, LazyValue v1, LazyValue v2) + public boolean pure() { + return pure; + } + + @Override + public boolean transitive() { + return transitive; + } + + @Override + public LazyValue lazyEval(Context c, Context.Type t, Expression e, Tokenizer.Token token, LazyValue v1, LazyValue v2) { try { @@ -312,6 +414,20 @@ public class Expression }); } + public void addImpureUnaryFunction(String name, Function fun) + { + functions.put(name, new AbstractFunction(1, name) + { + @Override + public boolean pure() { + return false; + } + + @Override + public Value eval(List parameters) { return fun.apply(parameters.get(0)); } + }); + } + public void addBinaryFunction(String name, BiFunction fun) { functions.put(name, new AbstractFunction(2, name) @@ -330,6 +446,20 @@ public class Expression }); } + public void addImpureFunction(String name, Function, Value> fun) + { + functions.put(name, new AbstractFunction(-1, name) + { + @Override + public boolean pure() { + return false; + } + + @Override + public Value eval(List parameters) { return fun.apply(parameters); } + }); + } + public void addMathematicalUnaryFunction(String name, Function fun) { addUnaryFunction(name, (v) -> new NumericValue(fun.apply(NumericValue.asNumber(v).getDouble()))); @@ -347,12 +477,22 @@ public class Expression } - public void addLazyFunction(String name, int num_params, TriFunction, LazyValue> fun) + public void addLazyFunction(String name, int num_params, TriFunction, LazyValue> fun) { functions.put(name, new AbstractLazyFunction(num_params, name) { @Override - public LazyValue lazyEval(Context c, Integer i, Expression e, Tokenizer.Token t, List lazyParams) + public boolean pure() { + return false; + } + + @Override + public boolean transitive() { + return false; + } + + @Override + public LazyValue lazyEval(Context c, Context.Type i, Expression e, Tokenizer.Token t, List lazyParams) { ILazyFunction.checkInterrupts(); try @@ -367,12 +507,58 @@ public class Expression }); } - public void addContextFunction(String name, int num_params, TriFunction, Value> fun) + public void addPureLazyFunction(String name, int num_params, Function typer, TriFunction, LazyValue> fun) { functions.put(name, new AbstractLazyFunction(num_params, name) { @Override - public LazyValue lazyEval(Context c, Integer i, Expression e, Tokenizer.Token t, List lazyParams) + public boolean pure() { + return true; + } + + @Override + public boolean transitive() { + return false; + } + + @Override + public Context.Type staticType(Context.Type outerType) + { + return typer.apply(outerType); + } + + @Override + public LazyValue lazyEval(Context c, Context.Type i, Expression e, Tokenizer.Token t, List lazyParams) + { + ILazyFunction.checkInterrupts(); + try + { + return fun.apply(c, i, lazyParams); + } + catch (RuntimeException exc) + { + throw handleCodeException(c, exc, e, t); + } + } + }); + } + + public void addContextFunction(String name, int num_params, TriFunction, Value> fun) + { + functions.put(name, new AbstractLazyFunction(num_params, name) + { + @Override + public boolean pure() { + return false; + } + + @Override + public boolean transitive() { + return false; + } + + @Override + public LazyValue lazyEval(Context c, Context.Type i, Expression e, Tokenizer.Token t, List lazyParams) { ILazyFunction.checkInterrupts(); try @@ -388,12 +574,27 @@ public class Expression }); } - public void addTypedContextFunction(String name, int num_params, int reqType, TriFunction, Value> fun) + public void addTypedContextFunction(String name, int num_params, Context.Type reqType, TriFunction, Value> fun) { functions.put(name, new AbstractLazyFunction(num_params, name) { @Override - public LazyValue lazyEval(Context c, Integer i, Expression e, Tokenizer.Token t, List lazyParams) + public boolean pure() { + return true; + } + + @Override + public boolean transitive() { + return false; + } + + @Override + public Context.Type staticType(Context.Type outerType) { + return reqType; + } + + @Override + public LazyValue lazyEval(Context c, Context.Type i, Expression e, Tokenizer.Token t, List lazyParams) { try { @@ -451,7 +652,22 @@ public class Expression } @Override - public LazyValue lazyEval(Context c, Integer type, Expression expr, Tokenizer.Token token, List lazyParams) + public boolean pure() { + return originalFunction.pure(); + } + + @Override + public boolean transitive() { + return originalFunction.transitive(); + } + + @Override + public Context.Type staticType(Context.Type outerType) { + return originalFunction.staticType(outerType); + } + + @Override + public LazyValue lazyEval(Context c, Context.Type type, Expression expr, Tokenizer.Token token, List lazyParams) { c.host.issueDeprecation(copy+"(...)"); return originalFunction.lazyEval(c, type, expr, token, lazyParams); @@ -474,7 +690,7 @@ public class Expression public LazyValue getOrSetAnyVariable(Context c, String name) { - LazyValue var = null; + LazyValue var; if (!name.startsWith("global_")) { var = c.getVariable(name); @@ -599,16 +815,16 @@ public class Expression case OPEN_PAREN: if (previousToken != null) { - if (previousToken.type == Tokenizer.Token.TokenType.LITERAL || previousToken.type == Tokenizer.Token.TokenType.CLOSE_PAREN - || previousToken.type == Tokenizer.Token.TokenType.VARIABLE - || previousToken.type == Tokenizer.Token.TokenType.HEX_LITERAL) - { + //if (previousToken.type == Tokenizer.Token.TokenType.LITERAL || previousToken.type == Tokenizer.Token.TokenType.CLOSE_PAREN + // || previousToken.type == Tokenizer.Token.TokenType.VARIABLE + // || previousToken.type == Tokenizer.Token.TokenType.HEX_LITERAL) + //{ // Implicit multiplication, e.g. 23(a+b) or (a+b)(a-b) - Tokenizer.Token multiplication = new Tokenizer.Token(); - multiplication.append("*"); - multiplication.type = Tokenizer.Token.TokenType.OPERATOR; - stack.push(multiplication); - } + // Tokenizer.Token multiplication = new Tokenizer.Token(); + // multiplication.append("*"); + // multiplication.type = Tokenizer.Token.TokenType.OPERATOR; + // stack.push(multiplication); + //} // if the ( is preceded by a valid function, then it // denotes the start of a parameter list if (previousToken.type == Tokenizer.Token.TokenType.FUNCTION) @@ -675,19 +891,15 @@ public class Expression } public Value eval(Context c) - { - return eval(c, Context.NONE); - } - public Value eval(Context c, Integer expectedType) { if (ast == null) { ast = getAST(c); } - return evalValue(() -> ast, c, expectedType); + return evalValue(() -> ast, c, Context.Type.NONE); } - public Value evalValue(Supplier exprProvider, Context c, Integer expectedType) + public Value evalValue(Supplier exprProvider, Context c, Context.Type expectedType) { try { @@ -715,35 +927,67 @@ public class Expression } } - private LazyValue getAST(Context context) + public static class ExpressionNode { - Stack stack = new Stack<>(); - List rpn = shuntingYard(context); - validate(context, rpn); - for (final Tokenizer.Token token : rpn) + public LazyValue op; + public List args; + public Tokenizer.Token token; + public List range; + /** + * The Value representation of the left parenthesis, used for parsing + * varying numbers of function parameters. + */ + public static final ExpressionNode PARAMS_START = new ExpressionNode(null, null, Tokenizer.Token.NONE); + public ExpressionNode(LazyValue op, List args, Tokenizer.Token token) + { + this.op = op; + this.args = args; + this.token = token; + range = new ArrayList<>(); + range.add(token); + } + public static ExpressionNode ofConstant(Value val, Tokenizer.Token token) + { + return new ExpressionNode(new LazyValue.Constant(val), Collections.emptyList(), token); + } + } + + + private ExpressionNode RPNToParseTree(List tokens, Context context) + { + Stack nodeStack = new Stack<>(); + for (final Tokenizer.Token token : tokens) { switch (token.type) { case UNARY_OPERATOR: { - final LazyValue value = stack.pop(); - LazyValue result = (c, t) -> operators.get(token.surface).lazyEval(c, t, this, token, value, null).evalValue(c, t); - stack.push(result); + final ExpressionNode node = nodeStack.pop(); + LazyValue result = (c, t) -> operators.get(token.surface).lazyEval(c, t, this, token, node.op, null).evalValue(c, t); + nodeStack.push(new ExpressionNode(result, Collections.singletonList(node), token)); break; } case OPERATOR: - final LazyValue v1 = stack.pop(); - final LazyValue v2 = stack.pop(); - LazyValue result = (c,t) -> operators.get(token.surface).lazyEval(c, t,this, token, v2, v1).evalValue(c, t); - stack.push(result); + final ExpressionNode v1 = nodeStack.pop(); + final ExpressionNode v2 = nodeStack.pop(); + LazyValue result = (c,t) -> operators.get(token.surface).lazyEval(c, t,this, token, v2.op, v1.op).evalValue(c, t); + nodeStack.push(new ExpressionNode(result, ImmutableList.of(v2, v1), token )); break; case VARIABLE: - stack.push((c, t) -> getOrSetAnyVariable(c, token.surface).evalValue(c, t)); + Value constant = getConstantFor(token.surface); + if (constant != null) + { + token.morph(Tokenizer.Token.TokenType.CONSTANT, token.surface); + nodeStack.push(new ExpressionNode(LazyValue.ofConstant(constant), Collections.emptyList(), token)); + } + else { + nodeStack.push(new ExpressionNode(((c, t) -> getOrSetAnyVariable(c, token.surface).evalValue(c, t)), Collections.emptyList(), token)); + } break; case FUNCTION: String name = token.surface; ILazyFunction f; - ArrayList p; + ArrayList p; boolean isKnown = functions.containsKey(name); // globals will be evaluated lazily, not at compile time via . if (isKnown) { @@ -757,47 +1001,301 @@ public class Expression } // pop parameters off the stack until we hit the start of // this function's parameter list - while (!stack.isEmpty() && stack.peek() != LazyValue.PARAMS_START) + while (!nodeStack.isEmpty() && nodeStack.peek() != ExpressionNode.PARAMS_START) { - p.add(stack.pop()); + p.add(nodeStack.pop()); + } + if (!isKnown) + { + p.add(ExpressionNode.ofConstant(new StringValue(name), token.morphedInto(Tokenizer.Token.TokenType.STRINGPARAM, token.surface))); + token.morph(Tokenizer.Token.TokenType.FUNCTION, "call"); } - if (!isKnown) p.add( (c, t) -> new StringValue(name)); Collections.reverse(p); - if (stack.peek() == LazyValue.PARAMS_START) + if (nodeStack.peek() == ExpressionNode.PARAMS_START) { - stack.pop(); - } + nodeStack.pop(); + }; + List params = p.stream().map(n -> n.op).collect(Collectors.toList()); - stack.push((c, t) -> f.lazyEval(c, t, this, token, p).evalValue(c, t)); + nodeStack.push(new ExpressionNode( + (c, t) -> f.lazyEval(c, t, this, token, params).evalValue(c, t), + p,token + )); break; case OPEN_PAREN: - stack.push(LazyValue.PARAMS_START); + nodeStack.push(ExpressionNode.PARAMS_START); break; case LITERAL: - stack.push((c, t) -> + Value number; + try { - try - { - return new NumericValue(token.surface); - } - catch (NumberFormatException exception) - { - throw new ExpressionException(c, this, token, "Not a number"); - } - }); + number = new NumericValue(token.surface); + } + catch (NumberFormatException exception) + { + throw new ExpressionException(context, this, token, "Not a number"); + } + token.morph(Tokenizer.Token.TokenType.CONSTANT, token.surface); + nodeStack.push(ExpressionNode.ofConstant(number, token)); break; case STRINGPARAM: - stack.push((c, t) -> new StringValue(token.surface) ); // was originally null + token.morph(Tokenizer.Token.TokenType.CONSTANT, token.surface); + nodeStack.push(ExpressionNode.ofConstant(new StringValue(token.surface), token)); break; case HEX_LITERAL: - stack.push((c, t) -> new NumericValue(new BigInteger(token.surface.substring(2), 16).doubleValue())); + Value hexNumber; + try + { + hexNumber = new NumericValue(new BigInteger(token.surface.substring(2), 16).longValue()); + } + catch (NumberFormatException exception) + { + throw new ExpressionException(context, this, token, "Not a number"); + } + token.morph(Tokenizer.Token.TokenType.CONSTANT, token.surface); + nodeStack.push(ExpressionNode.ofConstant(hexNumber, token)); break; default: throw new ExpressionException(context, this, token, "Unexpected token '" + token.surface + "'"); } } - return stack.pop(); + return nodeStack.pop(); + } + + private LazyValue getAST(Context context) + { + //Stack stack = new Stack<>(); + List rpn = shuntingYard(context); + validate(context, rpn); + ExpressionNode root = RPNToParseTree(rpn, context); + if (!CarpetSettings.scriptsOptimization) + return root.op; + + CarpetScriptServer.LOG.info("Input code size: " + treeSize(root) + " nodes, " + treeDepth(root) + " deep"); + + boolean changed = true; + while(changed) { + changed = false; + while (true) { + int tree_size = treeSize(root); + int tree_depth = treeDepth(root); + boolean optimized = compactTree(root, Context.Type.NONE, 0); + if (!optimized) break; + changed = true; + CarpetScriptServer.LOG.info("Compacted from " + tree_size + " nodes, " + tree_depth + " code depth to " + treeSize(root) + " nodes, " + treeDepth(root) + " code depth"); + } + while (true) { + int tree_size = treeSize(root); + int tree_depth = treeDepth(root); + boolean optimized = optimizeTree(context, root, Context.Type.NONE, 0); + if (!optimized) break; + changed = true; + CarpetScriptServer.LOG.info("Optimized from " + tree_size + " nodes, " + tree_depth + " code depth to " + treeSize(root) + " nodes, " + treeDepth(root) + " code depth"); + } + } + return extractOp(context, root, Context.Type.NONE); + } + + private int treeSize(ExpressionNode node) + { + if (node.op instanceof LazyValue.ContextFreeLazyValue) return 1; + return node.args.stream().mapToInt(this::treeSize).sum()+1; + } + private int treeDepth(ExpressionNode node) + { + if (node.op instanceof LazyValue.ContextFreeLazyValue) return 1; + return node.args.stream().mapToInt(this::treeDepth).max().orElse(0)+1; + } + + + private boolean compactTree(ExpressionNode node, Context.Type expectedType, int indent) { + // ctx is just to report errors, not values evaluation + boolean optimized = false; + Tokenizer.Token.TokenType token = node.token.type; + if (!token.isFunctional()) return false; + // input special cases here, like function signature + if (node.op instanceof LazyValue.Constant) return false; // optimized already + // function or operator + String symbol = node.token.surface; + Fluff.EvalNode operation = ((token == Tokenizer.Token.TokenType.FUNCTION) ? functions : operators).get(symbol); + Context.Type requestedType = operation.staticType(expectedType); + for (ExpressionNode arg : node.args) { + if (compactTree(arg, requestedType, indent+1)) optimized = true; + } + + if (expectedType != Context.Type.MAPDEF && symbol.equals("->") && node.args.size() == 2) { + String rop = node.args.get(1).token.surface; + ExpressionNode returnNode = null; + if ((rop.equals(";") || rop.equals("then"))) { + List thenArgs = node.args.get(1).args; + if (thenArgs.size() > 1 && thenArgs.get(thenArgs.size() - 1).token.surface.equals("return")) { + returnNode = thenArgs.get(thenArgs.size() - 1); + } + } else if (rop.equals("return")) { + returnNode = node.args.get(1); + } + if (returnNode != null) // tail return + { + if (returnNode.args.size() > 0) { + returnNode.op = returnNode.args.get(0).op; + returnNode.token = returnNode.args.get(0).token; + returnNode.range = returnNode.args.get(0).range; + returnNode.args = returnNode.args.get(0).args; + if (CarpetSettings.scriptsDebugging) + CarpetScriptServer.LOG.info(" - Removed unnecessary tail return of " + returnNode.token.surface + " from function body at line " + (returnNode.token.lineno + 1) + ", node depth " + indent); + + } else { + returnNode.op = LazyValue.ofConstant(Value.NULL); + returnNode.token.morph(Tokenizer.Token.TokenType.CONSTANT, ""); + returnNode.args = Collections.emptyList(); + if (CarpetSettings.scriptsDebugging) + CarpetScriptServer.LOG.info(" - Removed unnecessary tail return from function body at line " + (returnNode.token.lineno + 1) + ", node depth " + indent); + + } + + } + } + for (Map.Entry pair : functionalEquivalence.entrySet()) { + String operator = pair.getKey(); + String function = pair.getValue(); + if (symbol.equals(operator) || symbol.equals(function)) { + List newargs = new ArrayList<>(); + boolean contracted = false; + for (ExpressionNode arg : node.args) { + String argsymbol = arg.token.surface; // child is unoptimized and also same class + if ((argsymbol.equals(operator) || argsymbol.equals(function)) && (!(arg.op instanceof LazyValue.ContextFreeLazyValue))) { + newargs.addAll(arg.args); + contracted = true; + } else { + newargs.add(arg); + } + } + if (contracted) { + optimized = true; + if (CarpetSettings.scriptsDebugging) + CarpetScriptServer.LOG.info(" - " + symbol + "(" + node.args.size() + ") => " + function + "(" + newargs.size() + ") at line " + (node.token.lineno + 1) + ", node depth " + indent); + node.token.morph(Tokenizer.Token.TokenType.FUNCTION, function); + node.args = newargs; + } + } + } + return optimized; + } + + private boolean optimizeTree(Context ctx, ExpressionNode node, Context.Type expectedType, int indent) { + // ctx is just to report errors, not values evaluation + boolean optimized = false; + Tokenizer.Token.TokenType token = node.token.type; + if (!token.isFunctional()) return false; + String symbol = node.token.surface; + + // input special cases here, like function signature + if (node.op instanceof LazyValue.Constant) return false; // optimized already + // function or operator + + Fluff.EvalNode operation = ((token == Tokenizer.Token.TokenType.FUNCTION) ? functions : operators).get(symbol); + Context.Type requestedType = operation.staticType(expectedType); + for (ExpressionNode arg : node.args) { + if (optimizeTree(ctx, arg, requestedType, indent+1)) optimized = true; + } + + for (ExpressionNode arg : node.args) { + if (arg.token.type.isConstant()) continue; + if (arg.op instanceof LazyValue.ContextFreeLazyValue) continue; + return optimized; + } + if (!operation.pure()) + { + if (symbol.equals("->") && expectedType == Context.Type.MAPDEF) + { + + } + else { + return optimized; + } + } + List args = new ArrayList<>(node.args.size()); + for (ExpressionNode arg : node.args) { + try { + if (arg.op instanceof LazyValue.Constant) { + Value val = ((LazyValue.Constant) arg.op).get(); + args.add((c, t) -> val); + } + else args.add((c, t) -> arg.op.evalValue(null, requestedType)); + } catch (NullPointerException npe) { + throw new ExpressionException(ctx, this, node.token, "Attempted to evaluate context free expression"); + } + } + // applying argument unpacking + args= AbstractLazyFunction.lazify(AbstractLazyFunction.unpackLazy(args, null, requestedType)); + Value result; + if (operation instanceof ILazyFunction) + { + result = ((ILazyFunction) operation).lazyEval(null, expectedType, this, node.token, args).evalValue(null, expectedType); + } + else if (args.size() == 1) + { + result = ((ILazyOperator)operation).lazyEval(null, expectedType, this, node.token, args.get(0), null).evalValue(null, expectedType); + } + else // args == 2 + { + result = ((ILazyOperator)operation).lazyEval(null, expectedType, this, node.token, args.get(0), args.get(1)).evalValue(null, expectedType); + } + node.op = LazyValue.ofConstant(result); + //node.token.morph(Tokenizer.Token.TokenType.CONSTANT, node.token.surface); + if (CarpetSettings.scriptsDebugging) + CarpetScriptServer.LOG.info(" - "+symbol+"("+args.stream().map(a -> a.evalValue(null, requestedType).getString()).collect(Collectors.joining(", "))+") => "+result.getString()+" at line "+(node.token.lineno+1) +", node depth "+indent); + return true; + } + + private LazyValue extractOp(Context ctx, ExpressionNode node, Context.Type expectedType) + { + if (node.op instanceof LazyValue.Constant) + { + // constants are immutable + if (node.token.type.isConstant()) + { + Value value = ((LazyValue.Constant) node.op).get(); + return (c, t) -> value; + } + return node.op; + } + if (node.op instanceof LazyValue.ContextFreeLazyValue) + { + Value ret = ((LazyValue.ContextFreeLazyValue) node.op).evalType(expectedType); + return (c, t) -> ret; + } + Tokenizer.Token token = node.token; + switch (token.type) + { + case UNARY_OPERATOR: { + ILazyOperator op = operators.get(token.surface); + Context.Type requestedType = op.staticType(expectedType); + LazyValue arg = extractOp(ctx, node.args.get(0), requestedType); + return (c, t) -> op.lazyEval(c, t, this, token, arg, null).evalValue(c, t); + } + case OPERATOR: { + ILazyOperator op = operators.get(token.surface); + Context.Type requestedType = op.staticType(expectedType); + LazyValue arg = extractOp(ctx, node.args.get(0), requestedType); + LazyValue arh = extractOp(ctx, node.args.get(1), requestedType); + return (c, t) -> op.lazyEval(c, t, this, token, arg, arh).evalValue(c, t); + } + case VARIABLE: + return (c, t) -> getOrSetAnyVariable(c, token.surface).evalValue(c, t); + case FUNCTION: { + ILazyFunction f = functions.get(token.surface); + Context.Type requestedType = f.staticType(expectedType); + List params = node.args.stream().map(n -> extractOp(ctx, n, requestedType)).collect(Collectors.toList()); + return (c, t) -> f.lazyEval(c, t, this, token, params).evalValue(c, t); + } + case CONSTANT: + return node.op; + default: + throw new ExpressionException(ctx, this, node.token, "Unexpected token '" + node.token.type +" "+node.token.surface + "'"); + + } } private void validate(Context c, List rpn) diff --git a/src/main/java/carpet/script/Fluff.java b/src/main/java/carpet/script/Fluff.java index 4cb78357..e3bcc743 100644 --- a/src/main/java/carpet/script/Fluff.java +++ b/src/main/java/carpet/script/Fluff.java @@ -30,13 +30,33 @@ public abstract class Fluff @FunctionalInterface public interface SexFunction { R apply(A a, B b, C c, D d, E e, F f);} - public interface ILazyFunction + public interface EvalNode + { + /** + * @return true if function has constant output if arguments are constant and can be evaluated + * statically (without context) + */ + boolean pure(); + + /** + * @return true if function has constant output if arguments are constant and can be evaluated + * statically (without context) + */ + boolean transitive(); + + /** + * @return required argument eval type in case its evaluated statically without context but with a given context type + */ + default Context.Type staticType(Context.Type outerType) {return transitive()?outerType:Context.NONE;}; + } + + public interface ILazyFunction extends EvalNode { int getNumParams(); boolean numParamsVaries(); - LazyValue lazyEval(Context c, Integer type, Expression expr, Tokenizer.Token token, List lazyParams); + LazyValue lazyEval(Context c, Context.Type type, Expression expr, Tokenizer.Token token, List lazyParams); static void checkInterrupts() { @@ -51,13 +71,13 @@ public abstract class Fluff Value eval(List parameters); } - public interface ILazyOperator + public interface ILazyOperator extends EvalNode { int getPrecedence(); boolean isLeftAssoc(); - LazyValue lazyEval(Context c, Integer type, Expression e, Tokenizer.Token t, LazyValue v1, LazyValue v2); + LazyValue lazyEval(Context c, Context.Type type, Expression e, Tokenizer.Token t, LazyValue v1, LazyValue v2); } public interface IOperator extends ILazyOperator @@ -70,7 +90,7 @@ public abstract class Fluff protected String name; int numParams; - AbstractLazyFunction(int numParams, String name) + public AbstractLazyFunction(int numParams, String name) { this.numParams = numParams; this.name = name; @@ -89,7 +109,7 @@ public abstract class Fluff return numParams < 0; } - public static List unpackLazy(List lzargs, Context c, int contextType) + public static List unpackLazy(List lzargs, Context c, Context.Type contextType) { List args = new ArrayList<>(); for (LazyValue lv : lzargs) @@ -103,7 +123,7 @@ public abstract class Fluff return args; } - public List unpackArgs(List lzargs, Context c, int contextType) + public List unpackArgs(List lzargs, Context c, Context.Type contextType) { List args = new ArrayList<>(); for (LazyValue lv : lzargs) @@ -118,6 +138,13 @@ public abstract class Fluff throw new InternalExpressionException("Function " + getName() + " expected " + getNumParams() + " parameters, got " + args.size()); return args; } + + public static List lazify(List args) + { + List lzargs = new ArrayList<>(args.size()); + args.forEach( v -> lzargs.add( (c, t) -> v)); + return lzargs; + } } public abstract static class AbstractFunction extends AbstractLazyFunction implements IFunction @@ -127,7 +154,17 @@ public abstract class Fluff } @Override - public LazyValue lazyEval(Context cc, Integer type, Expression e, Tokenizer.Token t, final List lazyParams) + public boolean pure() { + return true; + } + + @Override + public boolean transitive() { + return false; + } + + @Override + public LazyValue lazyEval(Context cc, Context.Type type, Expression e, Tokenizer.Token t, final List lazyParams) { try { @@ -135,7 +172,7 @@ public abstract class Fluff { // eager evaluation always ignores the required type and evals params by none default private List params; - public Value evalValue(Context c, Integer type) { + public Value evalValue(Context c, Context.Type type) { ILazyFunction.checkInterrupts(); return AbstractFunction.this.eval(getParams(c)); } @@ -143,7 +180,7 @@ public abstract class Fluff private List getParams(Context c) { if (params == null) { // very likely needs to be dynamic, so not static like here, or remember if it was. - params = unpackArgs(lazyParams, c, Context.NONE); + params = unpackArgs(lazyParams, c, Context.Type.NONE); } else { @@ -187,13 +224,22 @@ public abstract class Fluff AbstractOperator(int precedence, boolean leftAssoc) { super(precedence, leftAssoc); } + @Override + public boolean pure() { + return true; + } @Override - public LazyValue lazyEval(Context cc, Integer type, Expression e, Tokenizer.Token t, final LazyValue v1, final LazyValue v2) + public boolean transitive() { + return false; + } + + @Override + public LazyValue lazyEval(Context cc, Context.Type type, Expression e, Tokenizer.Token t, final LazyValue v1, final LazyValue v2) { try { - return (c, type_ignored) -> AbstractOperator.this.eval(v1.evalValue(c, Context.NONE), v2.evalValue(c, Context.NONE)); + return (c, type_ignored) -> AbstractOperator.this.eval(v1.evalValue(c, Context.Type.NONE), v2.evalValue(c, Context.Type.NONE)); } catch (RuntimeException exc) { @@ -207,9 +253,18 @@ public abstract class Fluff AbstractUnaryOperator(int precedence, boolean leftAssoc) { super(precedence, leftAssoc); } + @Override + public boolean pure() { + return true; + } @Override - public LazyValue lazyEval(Context cc, Integer type, Expression e, Tokenizer.Token t, final LazyValue v1, final LazyValue v2) + public boolean transitive() { + return false; + } + + @Override + public LazyValue lazyEval(Context cc, Context.Type type, Expression e, Tokenizer.Token t, final LazyValue v1, final LazyValue v2) { try { @@ -217,7 +272,7 @@ public abstract class Fluff { throw new ExpressionException(cc, e, t, "Did not expect a second parameter for unary operator"); } - return (c, ignored_type) -> AbstractUnaryOperator.this.evalUnary(v1.evalValue(c, Context.NONE)); + return (c, ignored_type) -> AbstractUnaryOperator.this.evalUnary(v1.evalValue(c, Context.Type.NONE)); } catch (RuntimeException exc) { diff --git a/src/main/java/carpet/script/LazyValue.java b/src/main/java/carpet/script/LazyValue.java index 1cfb32a3..6f0fc15b 100644 --- a/src/main/java/carpet/script/LazyValue.java +++ b/src/main/java/carpet/script/LazyValue.java @@ -10,15 +10,50 @@ public interface LazyValue LazyValue TRUE = (c, t) -> Value.TRUE; LazyValue NULL = (c, t) -> Value.NULL; LazyValue ZERO = (c, t) -> Value.ZERO; - /** - * The Value representation of the left parenthesis, used for parsing - * varying numbers of function parameters. - */ - LazyValue PARAMS_START = (c, t) -> null; - Value evalValue(Context c, Integer type); + public static LazyValue ofConstant(Value val) { + return new Constant(val); + } + + Value evalValue(Context c, Context.Type type); default Value evalValue(Context c){ - return evalValue(c, Context.NONE); + return evalValue(c, Context.Type.NONE); + } + + @FunctionalInterface + interface ContextFreeLazyValue extends LazyValue + { + + Value evalType(Context.Type type); + + @Override + default Value evalValue(Context c, Context.Type type) { + return evalType(type); + } + } + + + class Constant implements ContextFreeLazyValue + { + Value result; + + public Constant(Value value) + { + result = value; + } + + public Value get() {return result;} + + @Override + public Value evalType(Context.Type type) { + + return result.fromConstant(); + } + + @Override + public Value evalValue(Context c, Context.Type type) { + return result.fromConstant(); + } } } diff --git a/src/main/java/carpet/script/ScriptHost.java b/src/main/java/carpet/script/ScriptHost.java index 389b8e62..150cc0ea 100644 --- a/src/main/java/carpet/script/ScriptHost.java +++ b/src/main/java/carpet/script/ScriptHost.java @@ -122,11 +122,6 @@ public abstract class ScriptHost void initializeModuleGlobals(ModuleData md) { - md.globalVariables.put("euler", (c, t) -> Arithmetic.euler); - md.globalVariables.put("pi", (c, t) -> Arithmetic.PI); - md.globalVariables.put("null", (c, t) -> Value.NULL); - md.globalVariables.put("true", (c, t) -> Value.TRUE); - md.globalVariables.put("false", (c, t) -> Value.FALSE); } public void importModule(Context c, String moduleName) diff --git a/src/main/java/carpet/script/Tokenizer.java b/src/main/java/carpet/script/Tokenizer.java index 24a92df1..645b53ad 100644 --- a/src/main/java/carpet/script/Tokenizer.java +++ b/src/main/java/carpet/script/Tokenizer.java @@ -398,8 +398,28 @@ public class Tokenizer implements Iterator { enum TokenType { - VARIABLE, FUNCTION, LITERAL, OPERATOR, UNARY_OPERATOR, - OPEN_PAREN, COMMA, CLOSE_PAREN, HEX_LITERAL, STRINGPARAM, MARKER + FUNCTION(true, false), OPERATOR(true, false), UNARY_OPERATOR(true, false), + VARIABLE(false, false), CONSTANT(false, true), + LITERAL(false, true), HEX_LITERAL(false, true), STRINGPARAM(false, true), + OPEN_PAREN(false, true), COMMA(false, true), CLOSE_PAREN(false, true), MARKER(false, true); + + boolean functional; + boolean constant; + + TokenType(boolean functional, boolean constant) + { + this.functional = functional; + this.constant = constant; + } + + public boolean isFunctional() + { + return functional; + } + + public boolean isConstant() { + return constant; + } } public String surface = ""; public TokenType type; diff --git a/src/main/java/carpet/script/api/Auxiliary.java b/src/main/java/carpet/script/api/Auxiliary.java index b276a521..b3a95b65 100644 --- a/src/main/java/carpet/script/api/Auxiliary.java +++ b/src/main/java/carpet/script/api/Auxiliary.java @@ -796,7 +796,7 @@ public class Auxiliary { return Value.TRUE; }); - expression.addFunction("logger", lv -> + expression.addImpureFunction("logger", lv -> { Value res; diff --git a/src/main/java/carpet/script/api/Threading.java b/src/main/java/carpet/script/api/Threading.java index 206977c5..baff04ab 100644 --- a/src/main/java/carpet/script/api/Threading.java +++ b/src/main/java/carpet/script/api/Threading.java @@ -25,7 +25,7 @@ public class Threading }); // has to be lazy due to deferred execution of the expression - expression.addLazyFunctionWithDelegation("task_dock", 1, (c, t, expr, tok, lv) -> { + expression.addLazyFunctionWithDelegation("task_dock", 1, false,true, (c, t, expr, tok, lv) -> { CarpetContext cc = (CarpetContext)c; MinecraftServer server = cc.s.getMinecraftServer(); if (server.isOnThread()) return lv.get(0); // pass through for on thread tasks diff --git a/src/main/java/carpet/script/language/Arithmetic.java b/src/main/java/carpet/script/language/Arithmetic.java index 8bdb3c94..e5b543f9 100644 --- a/src/main/java/carpet/script/language/Arithmetic.java +++ b/src/main/java/carpet/script/language/Arithmetic.java @@ -3,6 +3,7 @@ package carpet.script.language; import carpet.script.Context; import carpet.script.Expression; import carpet.script.exception.InternalExpressionException; +import carpet.script.value.BooleanValue; import carpet.script.value.ListValue; import carpet.script.value.NumericValue; import carpet.script.value.Value; @@ -15,7 +16,7 @@ public class Arithmetic { public static void apply(Expression expression) { - expression.addLazyFunction("not", 1, (c, t, lv) -> lv.get(0).evalValue(c, Context.BOOLEAN).getBoolean() ? ((cc, tt) -> Value.FALSE) : ((cc, tt) -> Value.TRUE)); + expression.addTypedContextFunction("not", 1, Context.Type.BOOLEAN, (c, t, lv) -> BooleanValue.of(lv.get(0).getBoolean()) ); expression.addUnaryFunction("fact", (v) -> { long number = NumericValue.asNumber(v).getLong(); diff --git a/src/main/java/carpet/script/language/ControlFlow.java b/src/main/java/carpet/script/language/ControlFlow.java index cfb78fed..dc64d6f0 100644 --- a/src/main/java/carpet/script/language/ControlFlow.java +++ b/src/main/java/carpet/script/language/ControlFlow.java @@ -21,13 +21,22 @@ public class ControlFlow { public static void apply(Expression expression) // public just to get the javadoc right { // needs to be lazy cause of custom contextualization - expression.addLazyBinaryOperator(";", Operators.precedence.get("nextop;"), true, (c, t, lv1, lv2) -> + expression.addLazyBinaryOperator(";", Operators.precedence.get("nextop;"), true, true, t -> Context.Type.VOID, (c, t, lv1, lv2) -> { lv1.evalValue(c, Context.VOID); Value v2 = lv2.evalValue(c, t); return (cc, tt) -> v2; }); + expression.addPureLazyFunction("then", -1, t -> Context.Type.VOID, (c, t, lv) -> { + int imax = lv.size()-1; + for (int i = 0; i < imax; i++) lv.get(i).evalValue(c, Context.VOID); + Value v = lv.get(imax).evalValue(c, t); + return (cc, tt) -> v; + }); + expression.addFunctionalEquivalence(";", "then"); + + // obvious lazy due to conditional evaluation of arguments expression.addLazyFunction("if", -1, (c, t, lv) -> { @@ -50,9 +59,9 @@ public class ControlFlow { return (cc, tt) -> Value.NULL; }); - expression.addFunction("exit", (lv) -> { throw new ExitStatement(lv.size()==0?Value.NULL:lv.get(0)); }); + expression.addImpureFunction("exit", (lv) -> { throw new ExitStatement(lv.size()==0?Value.NULL:lv.get(0)); }); - expression.addFunction("throw", lv-> + expression.addImpureFunction("throw", lv-> { switch (lv.size()) { diff --git a/src/main/java/carpet/script/language/DataStructures.java b/src/main/java/carpet/script/language/DataStructures.java index 6b75e624..8a3bec45 100644 --- a/src/main/java/carpet/script/language/DataStructures.java +++ b/src/main/java/carpet/script/language/DataStructures.java @@ -189,7 +189,7 @@ public class DataStructures { return Value.NULL; }); - expression.addBinaryContextOperator(":", Operators.precedence.get("attribute~:"),true, (c, t, container, address) -> + expression.addBinaryContextOperator(":", Operators.precedence.get("attribute~:"),true, true, false, (ctx, t, container, address) -> { if (container instanceof LContainerValue) { diff --git a/src/main/java/carpet/script/language/Functions.java b/src/main/java/carpet/script/language/Functions.java index 0fab2de3..c8d4b9b6 100644 --- a/src/main/java/carpet/script/language/Functions.java +++ b/src/main/java/carpet/script/language/Functions.java @@ -3,6 +3,8 @@ package carpet.script.language; import carpet.script.Context; import carpet.script.Expression; import carpet.script.Fluff; +import carpet.script.LazyValue; +import carpet.script.Tokenizer; import carpet.script.argument.FunctionArgument; import carpet.script.exception.InternalExpressionException; import carpet.script.exception.ReturnStatement; @@ -35,53 +37,75 @@ public class Functions { return ListValue.wrap(c.host.availableImports(moduleName).map(StringValue::new).collect(Collectors.toList())); }); + // needs to be lazy because of custom context of execution of arguments as a signature - expression.addLazyFunctionWithDelegation("call",-1, (c, t, expr, tok, lv) -> - { // adjust based on c - if (lv.size() == 0) - throw new InternalExpressionException("'call' expects at least function name to call"); - //lv.remove(lv.size()-1); // aint gonna cut it // maybe it will because of the eager eval changes - if (t != Context.SIGNATURE) // just call the function + expression.addCustomFunction("call", new Fluff.AbstractLazyFunction(-1, "call") { + @Override + public LazyValue lazyEval(Context c, Context.Type t, Expression expr, Tokenizer.Token tok, List lv) { - List args = Fluff.AbstractFunction.unpackLazy(lv, c, Context.NONE); - FunctionArgument functionArgument = FunctionArgument.findIn(c, expression.module, args, 0, false, true); - FunctionValue fun = functionArgument.function; - return fun.callInContext(c, t, functionArgument.args); - } - // gimme signature - String name = lv.get(0).evalValue(c, Context.NONE).getString(); - List args = new ArrayList<>(); - List globals = new ArrayList<>(); - String varArgs = null; - for (int i = 1; i < lv.size(); i++) - { - Value v = lv.get(i).evalValue(c, Context.LOCALIZATION); - if (!v.isBound()) + if (lv.size() == 0) + throw new InternalExpressionException("'call' expects at least function name to call"); + //lv.remove(lv.size()-1); // aint gonna cut it // maybe it will because of the eager eval changes + if (t != Context.SIGNATURE) // just call the function { - throw new InternalExpressionException("Only variables can be used in function signature, not " + v.getString()); + List args = Fluff.AbstractFunction.unpackLazy(lv, c, Context.NONE); + FunctionArgument functionArgument = FunctionArgument.findIn(c, expression.module, args, 0, false, true); + FunctionValue fun = functionArgument.function; + return fun.callInContext(c, t, functionArgument.args); } - if (v instanceof FunctionAnnotationValue) + // gimme signature + String name = lv.get(0).evalValue(c, Context.NONE).getString(); + List args = new ArrayList<>(); + List globals = new ArrayList<>(); + String varArgs = null; + for (int i = 1; i < lv.size(); i++) { - if (((FunctionAnnotationValue) v).type == FunctionAnnotationValue.Type.GLOBAL) + Value v = lv.get(i).evalValue(c, Context.LOCALIZATION); + if (!v.isBound()) { - globals.add(v.boundVariable); + throw new InternalExpressionException("Only variables can be used in function signature, not " + v.getString()); + } + if (v instanceof FunctionAnnotationValue) + { + if (((FunctionAnnotationValue) v).type == FunctionAnnotationValue.Type.GLOBAL) + { + globals.add(v.boundVariable); + } + else + { + if (varArgs != null) + throw new InternalExpressionException("Variable argument identifier is already defined as "+varArgs+", trying to overwrite with "+v.boundVariable); + varArgs = v.boundVariable; + } } else { - if (varArgs != null) - throw new InternalExpressionException("Variable argument identifier is already defined as "+varArgs+", trying to overwrite with "+v.boundVariable); - varArgs = v.boundVariable; + args.add(v.boundVariable); } } - else - { - args.add(v.boundVariable); - } + Value retval = new FunctionSignatureValue(name, args, varArgs, globals); + return (cc, tt) -> retval; + } + + @Override + public boolean pure() + { + return false; //true for sinature, but lets leave it for later + } + + @Override + public boolean transitive() { + return false; + } + + @Override + public Context.Type staticType(Context.Type outerType) + { + return outerType==Context.SIGNATURE?Context.LOCALIZATION:Context.NONE; } - Value retval = new FunctionSignatureValue(name, args, varArgs, globals); - return (cc, tt) -> retval; }); + expression.addContextFunction("outer", 1, (c, t, lv) -> { if (t != Context.LOCALIZATION) @@ -91,7 +115,7 @@ public class Functions { //assigns const procedure to the lhs, returning its previous value // must be lazy due to RHS being an expression to save to execute - expression.addLazyBinaryOperatorWithDelegation("->", Operators.precedence.get("def->"), false, (c, type, e, t, lv1, lv2) -> + expression.addLazyBinaryOperatorWithDelegation("->", Operators.precedence.get("def->"), false, false, (c, type, e, t, lv1, lv2) -> { if (type == Context.MAPDEF) { @@ -106,6 +130,6 @@ public class Functions { return (cc, tt) -> result; }); - expression.addFunction("return", (lv) -> { throw new ReturnStatement(lv.size()==0?Value.NULL:lv.get(0));} ); + expression.addImpureFunction("return", (lv) -> { throw new ReturnStatement(lv.size()==0?Value.NULL:lv.get(0));} ); } } diff --git a/src/main/java/carpet/script/language/Loops.java b/src/main/java/carpet/script/language/Loops.java index 4a566b07..4261bb61 100644 --- a/src/main/java/carpet/script/language/Loops.java +++ b/src/main/java/carpet/script/language/Loops.java @@ -21,14 +21,14 @@ public class Loops { // condition and expression will get a bound '_i' // returns last successful expression or false // while(cond, limit, expr) => ?? - expression.addFunction("break", lv -> + expression.addImpureFunction("break", lv -> { if (lv.size()==0) throw new BreakStatement(null); if (lv.size()==1) throw new BreakStatement(lv.get(0)); throw new InternalExpressionException("'break' can only be called with zero or one argument"); }); - expression.addFunction("continue", lv -> + expression.addImpureFunction("continue", lv -> { if (lv.size()==0) throw new ContinueStatement(null); if (lv.size()==1) throw new ContinueStatement(lv.get(0)); diff --git a/src/main/java/carpet/script/language/Operators.java b/src/main/java/carpet/script/language/Operators.java index 325df33d..f0e12bcd 100644 --- a/src/main/java/carpet/script/language/Operators.java +++ b/src/main/java/carpet/script/language/Operators.java @@ -5,6 +5,7 @@ import carpet.script.Expression; import carpet.script.LazyValue; import carpet.script.exception.InternalExpressionException; import carpet.script.value.AbstractListValue; +import carpet.script.value.BooleanValue; import carpet.script.value.ContainerValueInterface; import carpet.script.value.FunctionAnnotationValue; import carpet.script.value.FunctionUnpackedArgumentsValue; @@ -14,6 +15,7 @@ import carpet.script.value.MapValue; import carpet.script.value.NumericValue; import carpet.script.value.Value; +import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -38,47 +40,203 @@ public class Operators { public static void apply(Expression expression) { expression.addBinaryOperator("+", precedence.get("addition+-"), true, Value::add); + expression.addFunction("sum", lv -> { + int size = lv.size(); + if (size == 0) return Value.NULL; + Value accumulator = lv.get(0); + for (Value v: lv.subList(1, size)) accumulator = accumulator.add(v); + return accumulator; + }); + expression.addFunctionalEquivalence("+", "sum"); + expression.addBinaryOperator("-", precedence.get("addition+-"), true, Value::subtract); + expression.addFunction("difference", lv -> { + int size = lv.size(); + if (size == 0) return Value.NULL; + Value accumulator = lv.get(0); + for (Value v: lv.subList(1, size)) accumulator = accumulator.subtract(v); + return accumulator; + }); + expression.addFunctionalEquivalence("-", "difference"); + expression.addBinaryOperator("*", precedence.get("multiplication*/%"), true, Value::multiply); + expression.addFunction("product", lv -> { + int size = lv.size(); + if (size == 0) return Value.NULL; + Value accumulator = lv.get(0); + for (Value v: lv.subList(1, size)) accumulator = accumulator.multiply(v); + return accumulator; + }); + expression.addFunctionalEquivalence("*", "product"); + expression.addBinaryOperator("/", precedence.get("multiplication*/%"), true, Value::divide); + expression.addFunction("quotient", lv -> { + int size = lv.size(); + if (size == 0) return Value.NULL; + Value accumulator = lv.get(0); + for (Value v: lv.subList(1, size)) accumulator = accumulator.multiply(v); + return accumulator; + }); + expression.addFunctionalEquivalence("/", "quotient"); + expression.addBinaryOperator("%", precedence.get("multiplication*/%"), true, (v1, v2) -> new NumericValue(NumericValue.asNumber(v1).getDouble() % NumericValue.asNumber(v2).getDouble())); expression.addBinaryOperator("^", precedence.get("exponent^"), false, (v1, v2) -> new NumericValue(java.lang.Math.pow(NumericValue.asNumber(v1).getDouble(), NumericValue.asNumber(v2).getDouble()))); // lazy cause RHS is only conditional - expression.addLazyBinaryOperator("&&", precedence.get("and&&"), false, (c, t, lv1, lv2) -> - { + expression.addLazyBinaryOperator("&&", precedence.get("and&&"), false, true, t -> Context.Type.BOOLEAN, (c, t, lv1, lv2) -> + { // todo check how is optimizations going Value v1 = lv1.evalValue(c, Context.BOOLEAN); if (!v1.getBoolean()) return (cc, tt) -> v1; return lv2; }); + expression.addPureLazyFunction("and", -1, t -> Context.Type.BOOLEAN, (c, t, lv) -> { + int last = lv.size()-1; + if (last == -1) return LazyValue.TRUE; + for (LazyValue l: lv.subList(0, last)) + { + Value val = l.evalValue(c, Context.Type.BOOLEAN); + if (val instanceof FunctionUnpackedArgumentsValue) + { + for (Value it : (FunctionUnpackedArgumentsValue) val) + if (!it.getBoolean()) return (cc, tt) -> it; + } + else + { + if (!val.getBoolean()) return (cc, tt) -> val; + } + } + return lv.get(last); + }); + expression.addFunctionalEquivalence("&&", "and"); + // lazy cause RHS is only conditional - expression.addLazyBinaryOperator("||", precedence.get("or||"), false, (c, t, lv1, lv2) -> + expression.addLazyBinaryOperator("||", precedence.get("or||"), false, true, t -> Context.Type.BOOLEAN, (c, t, lv1, lv2) -> { Value v1 = lv1.evalValue(c, Context.BOOLEAN); if (v1.getBoolean()) return (cc, tt) -> v1; return lv2; }); + expression.addPureLazyFunction("or", -1, t -> Context.Type.BOOLEAN, (c, t, lv) -> { + int last = lv.size()-1; + if (last == -1) return LazyValue.FALSE; + for (LazyValue l: lv.subList(0, last)) + { + Value val = l.evalValue(c, Context.Type.BOOLEAN); + if (val instanceof FunctionUnpackedArgumentsValue) + { + for (Value it : (FunctionUnpackedArgumentsValue) val) + if (it.getBoolean()) return (cc, tt) -> it; + } + else + { + if (val.getBoolean()) return (cc, tt) -> val; + } + } + return lv.get(last); + }); + expression.addFunctionalEquivalence("||", "or"); + expression.addBinaryOperator("~", precedence.get("attribute~:"), true, Value::in); expression.addBinaryOperator(">", precedence.get("compare>=><=<"), false, (v1, v2) -> - v1.compareTo(v2) > 0 ? Value.TRUE : Value.FALSE); + BooleanValue.of(v1.compareTo(v2) > 0)); + expression.addFunction("decreasing", lv -> { + int size = lv.size(); + if (size < 2) return Value.TRUE; + Value prev = lv.get(0); + for (Value next: lv.subList(1, size)) + { + if (prev.compareTo(next) <= 0) return Value.FALSE; + prev = next; + } + return Value.TRUE; + }); + expression.addFunctionalEquivalence(">", "decreasing"); + expression.addBinaryOperator(">=", precedence.get("compare>=><=<"), false, (v1, v2) -> - v1.compareTo(v2) >= 0 ? Value.TRUE : Value.FALSE); + BooleanValue.of(v1.compareTo(v2) >= 0)); + expression.addFunction("nonincreasing", lv -> { + int size = lv.size(); + if (size < 2) return Value.TRUE; + Value prev = lv.get(0); + for (Value next: lv.subList(1, size)) + { + if (prev.compareTo(next) < 0) return Value.FALSE; + prev = next; + } + return Value.TRUE; + }); + expression.addFunctionalEquivalence(">=", "nonincreasing"); + expression.addBinaryOperator("<", precedence.get("compare>=><=<"), false, (v1, v2) -> - v1.compareTo(v2) < 0 ? Value.TRUE : Value.FALSE); + BooleanValue.of(v1.compareTo(v2) < 0)); + expression.addFunction("increasing", lv -> { + int size = lv.size(); + if (size < 2) return Value.TRUE; + Value prev = lv.get(0); + for (Value next: lv.subList(1, size)) + { + if (prev.compareTo(next) >= 0) return Value.FALSE; + prev = next; + } + return Value.TRUE; + }); + expression.addFunctionalEquivalence("<", "increasing"); + expression.addBinaryOperator("<=", precedence.get("compare>=><=<"), false, (v1, v2) -> - v1.compareTo(v2) <= 0 ? Value.TRUE : Value.FALSE); + BooleanValue.of(v1.compareTo(v2) <= 0)); + expression.addFunction("nondecreasing", lv -> { + int size = lv.size(); + if (size < 2) return Value.TRUE; + Value prev = lv.get(0); + for (Value next: lv.subList(1, size)) + { + if (prev.compareTo(next) > 0) return Value.FALSE; + prev = next; + } + return Value.TRUE; + }); + expression.addFunctionalEquivalence("<=", "nondecreasing"); + expression.addBinaryOperator("==", precedence.get("equal==!="), false, (v1, v2) -> v1.equals(v2) ? Value.TRUE : Value.FALSE); + expression.addFunction("equal", lv -> { + int size = lv.size(); + if (size < 2) return Value.TRUE; + Value prev = lv.get(0); + for (Value next: lv.subList(1, size)) + { + if (!prev.equals(next)) return Value.FALSE; + prev = next; + } + return Value.TRUE; + }); + expression.addFunctionalEquivalence("==", "equal"); + + expression.addBinaryOperator("!=", precedence.get("equal==!="), false, (v1, v2) -> v1.equals(v2) ? Value.FALSE : Value.TRUE); + expression.addFunction("unique", lv -> { + int size = lv.size(); + if (size < 2) return Value.TRUE; + // need to order them so same obejects will be next to each other. + lv.sort(Comparator.comparingInt(Value::hashCode)); + Value prev = lv.get(0); + for (Value next: lv.subList(1, size)) + { + if (prev.equals(next)) return Value.FALSE; + prev = next; + } + return Value.TRUE; + }); + expression.addFunctionalEquivalence("!=", "unique"); // lazy cause of assignment which is non-trivial - expression.addLazyBinaryOperator("=", precedence.get("assign=<>"), false, (c, t, lv1, lv2) -> + expression.addLazyBinaryOperator("=", precedence.get("assign=<>"), false, false, t -> Context.Type.LVALUE, (c, t, lv1, lv2) -> { Value v1 = lv1.evalValue(c, Context.LVALUE); Value v2 = lv2.evalValue(c); @@ -117,7 +275,7 @@ public class Operators { }); // lazy due to assignment - expression.addLazyBinaryOperator("+=", precedence.get("assign=<>"), false, (c, t, lv1, lv2) -> + expression.addLazyBinaryOperator("+=", precedence.get("assign=<>"), false, false, t -> Context.Type.LVALUE, (c, t, lv1, lv2) -> { Value v1 = lv1.evalValue(c, Context.LVALUE); Value v2 = lv2.evalValue(c); @@ -177,7 +335,7 @@ public class Operators { return boundedLHS; }); - expression.addBinaryContextOperator("<>", precedence.get("assign=<>"), false, (c, t, v1, v2) -> + expression.addBinaryContextOperator("<>", precedence.get("assign=<>"), false, false, false, (c, t, v1, v2) -> { if (v1 instanceof ListValue.ListConstructorValue && v2 instanceof ListValue.ListConstructorValue) { @@ -218,12 +376,12 @@ public class Operators { expression.addUnaryOperator("+", false, NumericValue::asNumber); // could be non-lazy, but who cares - its a small one. - expression.addLazyUnaryOperator("!", precedence.get("unary+-!..."), false, (c, t, lv) -> + expression.addLazyUnaryOperator("!", precedence.get("unary+-!..."), false, true, x -> Context.Type.BOOLEAN, (c, t, lv) -> lv.evalValue(c, Context.BOOLEAN).getBoolean() ? (cc, tt)-> Value.FALSE : (cc, tt) -> Value.TRUE ); // might need context boolean // lazy because of typed evaluation of the argument - expression.addLazyUnaryOperator("...", Operators.precedence.get("unary+-!..."), false, (c, t, lv) -> + expression.addLazyUnaryOperator("...", Operators.precedence.get("unary+-!..."), false, true, t -> t== Context.Type.LOCALIZATION?Context.NONE:t, (c, t, lv) -> { if (t == Context.LOCALIZATION) return (cc, tt) -> new FunctionAnnotationValue(lv.evalValue(c), FunctionAnnotationValue.Type.VARARG); diff --git a/src/main/java/carpet/script/language/Sys.java b/src/main/java/carpet/script/language/Sys.java index 9840e801..04a641d0 100644 --- a/src/main/java/carpet/script/language/Sys.java +++ b/src/main/java/carpet/script/language/Sys.java @@ -36,7 +36,7 @@ public class Sys { { expression.addUnaryFunction("hash_code", v -> new NumericValue(v.hashCode())); - expression.addUnaryFunction("copy", Value::deepcopy); + expression.addImpureUnaryFunction("copy", Value::deepcopy); expression.addTypedContextFunction("bool", 1, Context.BOOLEAN, (c, t, lv) -> { diff --git a/src/main/java/carpet/script/language/Threading.java b/src/main/java/carpet/script/language/Threading.java index ed2691eb..55bfdf6c 100644 --- a/src/main/java/carpet/script/language/Threading.java +++ b/src/main/java/carpet/script/language/Threading.java @@ -12,7 +12,7 @@ public class Threading { public static void apply(Expression expression) { - expression.addFunctionWithDelegation("task", -1, (c, t, expr, tok, lv) -> + expression.addFunctionWithDelegation("task", -1, false, false, (c, t, expr, tok, lv) -> { if (lv.size() == 0) throw new InternalExpressionException("'task' requires at least function to call as a parameter"); @@ -22,7 +22,7 @@ public class Threading return thread; }); - expression.addFunctionWithDelegation("task_thread", -1, (c, t, expr, tok, lv) -> + expression.addFunctionWithDelegation("task_thread", -1, false, false, (c, t, expr, tok, lv) -> { if (lv.size() < 2) throw new InternalExpressionException("'task' requires at least function to call as a parameter"); diff --git a/src/main/java/carpet/script/value/AbstractListValue.java b/src/main/java/carpet/script/value/AbstractListValue.java index 79c730da..c20b9112 100644 --- a/src/main/java/carpet/script/value/AbstractListValue.java +++ b/src/main/java/carpet/script/value/AbstractListValue.java @@ -15,4 +15,9 @@ public abstract class AbstractListValue extends Value implements Iterable { throw new InternalExpressionException("Cannot append a value to an abstract list"); } + + @Override + public Value fromConstant() { + return this.deepcopy(); + } } diff --git a/src/main/java/carpet/script/value/FunctionValue.java b/src/main/java/carpet/script/value/FunctionValue.java index 588b755c..cb91dac2 100644 --- a/src/main/java/carpet/script/value/FunctionValue.java +++ b/src/main/java/carpet/script/value/FunctionValue.java @@ -150,7 +150,7 @@ public class FunctionValue extends Value implements Fluff.ILazyFunction return varArgs != null; } - public LazyValue callInContext(Context c, Integer type, List params) + public LazyValue callInContext(Context c, Context.Type type, List params) { try { @@ -204,12 +204,12 @@ public class FunctionValue extends Value implements Fluff.ILazyFunction } @Override - public LazyValue lazyEval(Context c, Integer type, Expression e, Tokenizer.Token t, List lazyParams) { + public LazyValue lazyEval(Context c, Context.Type type, Expression e, Tokenizer.Token t, List lazyParams) { List resolvedParams = unpackArgs(lazyParams, c); return execute(c, type, e, t, resolvedParams); } - public LazyValue execute(Context c, Integer type, Expression e, Tokenizer.Token t, List params) + public LazyValue execute(Context c, Context.Type type, Expression e, Tokenizer.Token t, List params) { assertArgsOk(params, (fixedArgs) ->{ if (fixedArgs) // wrong number of args for fixed args @@ -301,10 +301,13 @@ public class FunctionValue extends Value implements Fluff.ILazyFunction } } - public static List lazify(List args) - { - List lzargs = new ArrayList<>(args.size()); - args.forEach( v -> lzargs.add( (c, t) -> v)); - return lzargs; + @Override + public boolean pure() { + return false; + } + + @Override + public boolean transitive() { + return false; } } diff --git a/src/main/java/carpet/script/value/LazyListValue.java b/src/main/java/carpet/script/value/LazyListValue.java index 5584e628..725bcfe3 100644 --- a/src/main/java/carpet/script/value/LazyListValue.java +++ b/src/main/java/carpet/script/value/LazyListValue.java @@ -192,6 +192,11 @@ public abstract class LazyListValue extends AbstractListValue implements Iterato return copy; } + @Override + public Value fromConstant() { + return (Value)clone(); + } + @Override public int hashCode() { diff --git a/src/main/java/carpet/script/value/NBTSerializableValue.java b/src/main/java/carpet/script/value/NBTSerializableValue.java index 83959ee2..3d59ec48 100644 --- a/src/main/java/carpet/script/value/NBTSerializableValue.java +++ b/src/main/java/carpet/script/value/NBTSerializableValue.java @@ -150,6 +150,11 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa return copy; } + @Override + public Value fromConstant() { + return deepcopy(); + } + // stolen from HopperBlockEntity, adjusted for threaded operation public static Inventory getInventoryAt(ServerWorld world, BlockPos blockPos) { diff --git a/src/main/java/carpet/script/value/Value.java b/src/main/java/carpet/script/value/Value.java index 341af7c4..56ff2d31 100644 --- a/src/main/java/carpet/script/value/Value.java +++ b/src/main/java/carpet/script/value/Value.java @@ -244,4 +244,11 @@ public abstract class Value implements Comparable, Cloneable } public boolean isNull() { return false; } + + /** + * @return retrieves useful in-run value of an optimized code-base value. + * For immutable values (most of them) it can return itself, + * but for mutables, it needs to be its copy or deep copy. + */ + public Value fromConstant() { return this; } } From 1633cca07a4ab80f301c083a366de5cc9ff1bf11 Mon Sep 17 00:00:00 2001 From: gnembon Date: Wed, 5 May 2021 01:23:17 -0300 Subject: [PATCH 6/6] better reporting --- src/main/java/carpet/script/CarpetScriptHost.java | 10 +--------- src/main/java/carpet/script/Expression.java | 4 ++-- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/main/java/carpet/script/CarpetScriptHost.java b/src/main/java/carpet/script/CarpetScriptHost.java index 3f002753..a4233666 100644 --- a/src/main/java/carpet/script/CarpetScriptHost.java +++ b/src/main/java/carpet/script/CarpetScriptHost.java @@ -1004,15 +1004,7 @@ public class CarpetScriptHost extends ScriptHost return null; } - String shebang = message; - if (expr.module != null) - { - shebang += " in " + expr.module.getName() + ""; - } - else - { - shebang += " in system chat"; - } + String shebang = message+" in "+expr.getModuleName(); if (token != null) { String[] lines = expr.getCodeString().split("\n"); diff --git a/src/main/java/carpet/script/Expression.java b/src/main/java/carpet/script/Expression.java index 9b2c1424..2f33ecc2 100644 --- a/src/main/java/carpet/script/Expression.java +++ b/src/main/java/carpet/script/Expression.java @@ -66,7 +66,7 @@ public class Expression public String getModuleName() { - return module == null?null:module.getName(); + return module == null?"system chat":module.getName(); } public void asATextSource() @@ -1072,7 +1072,7 @@ public class Expression if (!CarpetSettings.scriptsOptimization) return root.op; - CarpetScriptServer.LOG.info("Input code size: " + treeSize(root) + " nodes, " + treeDepth(root) + " deep"); + CarpetScriptServer.LOG.info("Input code size for "+getModuleName()+": " + treeSize(root) + " nodes, " + treeDepth(root) + " deep"); boolean changed = true; while(changed) {