pystacker 1.9.2__tar.gz → 1.13.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. {pystacker-1.9.2/pystacker.egg-info → pystacker-1.13.0}/PKG-INFO +88 -19
  2. {pystacker-1.9.2 → pystacker-1.13.0}/README.md +87 -18
  3. {pystacker-1.9.2 → pystacker-1.13.0}/pyproject.toml +1 -1
  4. {pystacker-1.9.2 → pystacker-1.13.0/pystacker.egg-info}/PKG-INFO +88 -19
  5. {pystacker-1.9.2 → pystacker-1.13.0}/pystacker.egg-info/SOURCES.txt +23 -1
  6. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/__main__.py +34 -5
  7. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/constant.py +1 -1
  8. pystacker-1.13.0/stacker/engine/core.py +1158 -0
  9. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/engine/data_type.py +13 -11
  10. pystacker-1.13.0/stacker/engine/recursion.py +74 -0
  11. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/engine/scope.py +43 -39
  12. pystacker-1.13.0/stacker/engine/sfunction.py +65 -0
  13. pystacker-1.13.0/stacker/engine/slambda.py +76 -0
  14. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/engine/smacro.py +3 -3
  15. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/error.py +63 -12
  16. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/error_formatter.py +0 -1
  17. pystacker-1.13.0/stacker/include/include.py +92 -0
  18. pystacker-1.13.0/stacker/include/stk_file_read.py +22 -0
  19. pystacker-1.13.0/stacker/lib/config.py +32 -0
  20. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/operators/defun.py +2 -2
  21. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/operators/eval.py +0 -1
  22. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/operators/file.py +11 -7
  23. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/operators/if_else.py +33 -9
  24. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/operators/include.py +11 -2
  25. pystacker-1.13.0/stacker/operators/list.py +70 -0
  26. pystacker-1.13.0/stacker/operators/loop.py +142 -0
  27. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/operators/manager.py +77 -32
  28. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/operators/math.py +33 -33
  29. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/operators/os.py +1 -1
  30. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/operators/stack.py +18 -12
  31. pystacker-1.13.0/stacker/operators/types.py +83 -0
  32. pystacker-1.13.0/stacker/reserved.py +1 -0
  33. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/runtime/exec_modes/commandline_mode.py +1 -1
  34. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/runtime/exec_modes/error.py +2 -2
  35. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/runtime/exec_modes/execution_mode.py +45 -56
  36. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/runtime/exec_modes/repl_mode.py +61 -67
  37. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/runtime/exec_modes/script_mode.py +7 -3
  38. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/stacker.py +38 -45
  39. pystacker-1.13.0/stacker/syntax/lexer.py +466 -0
  40. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/syntax/parser.py +35 -34
  41. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/syntax/test_parser.py +3 -1
  42. pystacker-1.13.0/stacker/syntax/token_rules.py +66 -0
  43. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/util/disp.py +34 -33
  44. pystacker-1.13.0/test/test_apply.py +51 -0
  45. pystacker-1.13.0/test/test_block_comments.py +77 -0
  46. pystacker-1.13.0/test/test_cond.py +45 -0
  47. pystacker-1.13.0/test/test_debug_flag.py +49 -0
  48. pystacker-1.13.0/test/test_defmacro_advanced.py +52 -0
  49. {pystacker-1.9.2 → pystacker-1.13.0}/test/test_defun.py +7 -2
  50. pystacker-1.13.0/test/test_defun_advanced.py +65 -0
  51. pystacker-1.13.0/test/test_disp.py +33 -0
  52. {pystacker-1.9.2 → pystacker-1.13.0}/test/test_error_messages.py +15 -0
  53. pystacker-1.13.0/test/test_expand.py +25 -0
  54. pystacker-1.13.0/test/test_hof_advanced.py +98 -0
  55. pystacker-1.13.0/test/test_include.py +184 -0
  56. {pystacker-1.9.2 → pystacker-1.13.0}/test/test_inline_comments.py +49 -0
  57. {pystacker-1.9.2 → pystacker-1.13.0}/test/test_lambda.py +16 -0
  58. pystacker-1.13.0/test/test_lazy_block_binding.py +100 -0
  59. pystacker-1.13.0/test/test_list_primitives.py +139 -0
  60. pystacker-1.13.0/test/test_loop_advanced.py +67 -0
  61. pystacker-1.13.0/test/test_multiline_accumulation.py +64 -0
  62. pystacker-1.13.0/test/test_recursion_limit.py +125 -0
  63. {pystacker-1.9.2 → pystacker-1.13.0}/test/test_reduce.py +33 -0
  64. pystacker-1.13.0/test/test_repl_continuation.py +66 -0
  65. {pystacker-1.9.2 → pystacker-1.13.0}/test/test_scope.py +14 -0
  66. pystacker-1.13.0/test/test_semantics_freeze.py +180 -0
  67. {pystacker-1.9.2 → pystacker-1.13.0}/test/test_stacker.py +62 -0
  68. pystacker-1.13.0/test/test_token_list_cache.py +56 -0
  69. pystacker-1.13.0/test/test_triple_quote_strings.py +95 -0
  70. pystacker-1.13.0/test/test_unterminated_errors.py +124 -0
  71. pystacker-1.13.0/test/test_while_loop.py +47 -0
  72. pystacker-1.9.2/stacker/engine/core.py +0 -875
  73. pystacker-1.9.2/stacker/engine/sfunction.py +0 -43
  74. pystacker-1.9.2/stacker/engine/slambda.py +0 -44
  75. pystacker-1.9.2/stacker/include/include.py +0 -32
  76. pystacker-1.9.2/stacker/include/stk_file_read.py +0 -46
  77. pystacker-1.9.2/stacker/lib/config.py +0 -12
  78. pystacker-1.9.2/stacker/operators/list.py +0 -53
  79. pystacker-1.9.2/stacker/operators/loop.py +0 -93
  80. pystacker-1.9.2/stacker/operators/types.py +0 -41
  81. pystacker-1.9.2/stacker/reserved.py +0 -2
  82. pystacker-1.9.2/stacker/syntax/lexer.py +0 -210
  83. pystacker-1.9.2/test/test_include.py +0 -52
  84. {pystacker-1.9.2 → pystacker-1.13.0}/LICENSE +0 -0
  85. {pystacker-1.9.2 → pystacker-1.13.0}/pystacker.egg-info/dependency_links.txt +0 -0
  86. {pystacker-1.9.2 → pystacker-1.13.0}/pystacker.egg-info/entry_points.txt +0 -0
  87. {pystacker-1.9.2 → pystacker-1.13.0}/pystacker.egg-info/requires.txt +0 -0
  88. {pystacker-1.9.2 → pystacker-1.13.0}/pystacker.egg-info/top_level.txt +0 -0
  89. {pystacker-1.9.2 → pystacker-1.13.0}/setup.cfg +0 -0
  90. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/__init__.py +0 -0
  91. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/data/about.txt +0 -0
  92. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/data/help.txt +0 -0
  93. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/data/top.txt +0 -0
  94. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/engine/__init__.py +0 -0
  95. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/include/__init__.py +0 -0
  96. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/lib/__init__.py +0 -0
  97. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/lib/ui_tools.py +0 -0
  98. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/manager/__init__.py +0 -0
  99. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/operators/__init__.py +0 -0
  100. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/operators/aggregate.py +0 -0
  101. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/operators/algebra.py +0 -0
  102. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/operators/arith.py +0 -0
  103. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/operators/base.py +0 -0
  104. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/operators/bitwise.py +0 -0
  105. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/operators/comparison.py +0 -0
  106. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/operators/defmacro.py +0 -0
  107. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/operators/exit.py +0 -0
  108. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/operators/hof.py +0 -0
  109. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/operators/io.py +0 -0
  110. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/operators/lmd.py +0 -0
  111. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/operators/logic.py +0 -0
  112. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/operators/random.py +0 -0
  113. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/operators/setting.py +0 -0
  114. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/operators/string.py +0 -0
  115. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/operators/system.py +0 -0
  116. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/operators/time.py +0 -0
  117. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/operators/transform.py +0 -0
  118. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/plugins/__init__.py +0 -0
  119. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/plugins/matrix.py +0 -0
  120. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/plugins/whos.py +0 -0
  121. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/runtime/__init__.py +0 -0
  122. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/runtime/exec_modes/__init__.py +0 -0
  123. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/slib/__init__.py +0 -0
  124. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/syntax/__init__.py +0 -0
  125. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/util/__init__.py +0 -0
  126. {pystacker-1.9.2 → pystacker-1.13.0}/stacker/util/color.py +0 -0
  127. {pystacker-1.9.2 → pystacker-1.13.0}/test/test_aggregate.py +0 -0
  128. {pystacker-1.9.2 → pystacker-1.13.0}/test/test_defmacro.py +0 -0
  129. {pystacker-1.9.2 → pystacker-1.13.0}/test/test_error_formatter.py +0 -0
  130. {pystacker-1.9.2 → pystacker-1.13.0}/test/test_if_else_statement.py +0 -0
  131. {pystacker-1.9.2 → pystacker-1.13.0}/test/test_loop_statement.py +0 -0
  132. {pystacker-1.9.2 → pystacker-1.13.0}/test/test_no_dollar_in_blocks.py +0 -0
  133. {pystacker-1.9.2 → pystacker-1.13.0}/test/test_no_dollar_syntax.py +0 -0
  134. {pystacker-1.9.2 → pystacker-1.13.0}/test/test_string_apostrophe.py +0 -0
  135. {pystacker-1.9.2 → pystacker-1.13.0}/test/test_transform.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pystacker
3
- Version: 1.9.2
3
+ Version: 1.13.0
4
4
  Summary: Stacker: RPN Calculator in Python
5
5
  Author: remokasu
6
6
  License: MIT License
@@ -276,6 +276,26 @@ Stacker allows for straightforward RPN input. For example:
276
276
 
277
277
  Result: Pushes `7` onto the stack as `x` equals `0`.
278
278
 
279
+ - ##### cond Statement
280
+
281
+ The `cond` statement evaluates condition-result pairs in order and executes the first matching branch.
282
+
283
+ Syntax:
284
+ ```bash
285
+ {condition1} {result1} {condition2} {result2} ... n cond
286
+ ```
287
+
288
+ `n` is the number of condition-result pairs.
289
+
290
+ Example:
291
+ ```bash
292
+ stacker:0> 5 $x set
293
+ stacker:1> {x 0 >} {"positive"} {x 0 <} {"negative"} {true} {"zero"} 3 cond
294
+ ["positive"]
295
+ ```
296
+
297
+ Result: Pushes `"positive"` onto the stack as `x` is greater than `0`. Use `{true}` as the last condition to provide a default branch.
298
+
279
299
  - #### Loops
280
300
 
281
301
  Loops in Stacker allow for repeated execution of code blocks.
@@ -347,23 +367,43 @@ Stacker allows for straightforward RPN input. For example:
347
367
 
348
368
  Result: Pushes numbers 1 through 11 onto the stack by repeatedly duplicating and incrementing.
349
369
 
370
+ - ##### while Loop
371
+
372
+ The `while` loop repeats a code block while a condition is true.
373
+
374
+ Syntax:
375
+ ```bash
376
+ {condition} {body} while
377
+ ```
378
+
379
+ Example:
380
+ ```bash
381
+ stacker:0> 1 $i set 0 $s set
382
+ stacker:2> {i 6 <=} {s i + $s set i ++ $i set} while
383
+ stacker:2> s
384
+ [21]
385
+ ```
386
+
387
+ Result: Sums numbers from 1 to 6.
388
+
389
+ Note: Use `{condition}` as a code block that leaves a boolean on the stack. The loop terminates when the condition evaluates to false.
390
+
350
391
  - #### break
351
- - syntax:
352
- ```bash
353
- {break}
354
- ```
355
- - example:
356
- ```bash
357
- stacker:0> 0 i set
358
- stacker:1> 0 9 i {{break} i 5 == if i echo} do
359
- 0
360
- 1
361
- 2
362
- 3
363
- 4
364
- 5
365
- ```
366
- This example prints the numbers from 0 to 5. When `i` is equal to `5`, the loop is terminated by `break`.
392
+
393
+ `break` immediately exits the innermost loop (`do`, `dolist`, `times`, `while`).
394
+
395
+ Syntax:
396
+ ```bash
397
+ {break}
398
+ ```
399
+
400
+ Example:
401
+ ```bash
402
+ stacker:0> 0 $s set 1 10 $i {i 5 == {break} if s i + $s set} do s
403
+ [10]
404
+ ```
405
+
406
+ Result: Sums 1 through 4, then exits when `i` reaches 5. Note that `break` inside `if` immediately stops execution of the current block — `s i + $s set` is not executed when `i` equals 5.
367
407
 
368
408
  - ### Define a function:
369
409
  - syntax:
@@ -722,11 +762,14 @@ print(stacker.eval("3 4 +"))
722
762
  ### Control Operators
723
763
  | Operator | Description | Example |
724
764
  |----------|-------------------------------------------------------|----------------------------|
725
- | if | Conditional statement | `{...} true if` |
726
- | ifelse | Conditional statement with an else block | `{true block} {false block} true ifelse` |
765
+ | if | Conditional statement | `true {...} if` |
766
+ | ifelse | Conditional statement with an else block | `true {true block} {false block} ifelse` |
727
767
  | iferror | Conditional statement for error handling | `{try block} {catch block} iferror` |
768
+ | cond | Multi-branch conditional (first matching pair wins) | `{x 0 >} {"pos"} {true} {"other"} 2 cond` |
728
769
  | do | Loop | `0 10 i {i echo} do` |
770
+ | dolist | Loop over the elements of a list | `[1 2 3] x {x echo} dolist` |
729
771
  | times | Loop a specified number of times | `{dup ++} 10 times` |
772
+ | while | Loop while a condition is true | `{i 10 <} {i ++ $i set} while` |
730
773
  | break | Break out of a loop | `break` |
731
774
 
732
775
 
@@ -749,6 +792,32 @@ print(stacker.eval("3 4 +"))
749
792
  | filter | Filter an array based on a condition | `[1 2 3 4 5] {2 % 0 ==} filter` |
750
793
  | all | Check if all elements of an array satisfy a condition | `[1 2 3 4 5] {2 % 0 ==} all` |
751
794
  | any | Check if any element of an array satisfies a condition | `[1 2 3 4 5] {2 % 0 ==} any` |
795
+ | car | Return the first element of a list | `[1 2 3] car` |
796
+ | cdr | Return the list without its first element | `[1 2 3] cdr` |
797
+ | cons | Prepend an element to a list | `1 [2 3] cons` |
798
+ | pair? | Return true if the value is a non-empty list | `[1 2 3] pair?` |
799
+ | apply | Expand list elements onto the stack and apply a function | `[3 4] {+} apply` |
800
+
801
+ Note: Blocks passed to `map`, `filter`, `reduce`, and `fold` must leave a value on the stack. For side-effect-only iteration (e.g. printing each element), use `dolist` instead.
802
+
803
+
804
+ ### Type Operators
805
+ | Operator | Description | Example |
806
+ |----------|-------------------------------------------------------|----------------------------|
807
+ | int | Convert to int | `3.7 int` |
808
+ | float | Convert to float | `3 float` |
809
+ | str | Convert to string | `42 str` |
810
+ | bool | Convert to bool | `1 bool` |
811
+ | complex | Convert to complex number | `3 complex` |
812
+ | type | Get the type of a value | `42 type` |
813
+ | int? | Returns true if the value is an int (not bool) | `42 int?` |
814
+ | float? | Returns true if the value is a float | `3.14 float?` |
815
+ | str? | Returns true if the value is a string | `"hello" str?` |
816
+ | bool? | Returns true if the value is a bool | `true bool?` |
817
+ | complex? | Returns true if the value is a complex number | `1+2j complex?` |
818
+ | list? | Returns true if the value is a list | `[1 2 3] list?` |
819
+ | null? | Returns true if the value is null or an empty list | `null null?` |
820
+ | number? | Returns true if the value is a number (int, float, or complex) | `42 number?` |
752
821
 
753
822
 
754
823
  ### Other Operators
@@ -241,6 +241,26 @@ Stacker allows for straightforward RPN input. For example:
241
241
 
242
242
  Result: Pushes `7` onto the stack as `x` equals `0`.
243
243
 
244
+ - ##### cond Statement
245
+
246
+ The `cond` statement evaluates condition-result pairs in order and executes the first matching branch.
247
+
248
+ Syntax:
249
+ ```bash
250
+ {condition1} {result1} {condition2} {result2} ... n cond
251
+ ```
252
+
253
+ `n` is the number of condition-result pairs.
254
+
255
+ Example:
256
+ ```bash
257
+ stacker:0> 5 $x set
258
+ stacker:1> {x 0 >} {"positive"} {x 0 <} {"negative"} {true} {"zero"} 3 cond
259
+ ["positive"]
260
+ ```
261
+
262
+ Result: Pushes `"positive"` onto the stack as `x` is greater than `0`. Use `{true}` as the last condition to provide a default branch.
263
+
244
264
  - #### Loops
245
265
 
246
266
  Loops in Stacker allow for repeated execution of code blocks.
@@ -312,23 +332,43 @@ Stacker allows for straightforward RPN input. For example:
312
332
 
313
333
  Result: Pushes numbers 1 through 11 onto the stack by repeatedly duplicating and incrementing.
314
334
 
335
+ - ##### while Loop
336
+
337
+ The `while` loop repeats a code block while a condition is true.
338
+
339
+ Syntax:
340
+ ```bash
341
+ {condition} {body} while
342
+ ```
343
+
344
+ Example:
345
+ ```bash
346
+ stacker:0> 1 $i set 0 $s set
347
+ stacker:2> {i 6 <=} {s i + $s set i ++ $i set} while
348
+ stacker:2> s
349
+ [21]
350
+ ```
351
+
352
+ Result: Sums numbers from 1 to 6.
353
+
354
+ Note: Use `{condition}` as a code block that leaves a boolean on the stack. The loop terminates when the condition evaluates to false.
355
+
315
356
  - #### break
316
- - syntax:
317
- ```bash
318
- {break}
319
- ```
320
- - example:
321
- ```bash
322
- stacker:0> 0 i set
323
- stacker:1> 0 9 i {{break} i 5 == if i echo} do
324
- 0
325
- 1
326
- 2
327
- 3
328
- 4
329
- 5
330
- ```
331
- This example prints the numbers from 0 to 5. When `i` is equal to `5`, the loop is terminated by `break`.
357
+
358
+ `break` immediately exits the innermost loop (`do`, `dolist`, `times`, `while`).
359
+
360
+ Syntax:
361
+ ```bash
362
+ {break}
363
+ ```
364
+
365
+ Example:
366
+ ```bash
367
+ stacker:0> 0 $s set 1 10 $i {i 5 == {break} if s i + $s set} do s
368
+ [10]
369
+ ```
370
+
371
+ Result: Sums 1 through 4, then exits when `i` reaches 5. Note that `break` inside `if` immediately stops execution of the current block — `s i + $s set` is not executed when `i` equals 5.
332
372
 
333
373
  - ### Define a function:
334
374
  - syntax:
@@ -687,11 +727,14 @@ print(stacker.eval("3 4 +"))
687
727
  ### Control Operators
688
728
  | Operator | Description | Example |
689
729
  |----------|-------------------------------------------------------|----------------------------|
690
- | if | Conditional statement | `{...} true if` |
691
- | ifelse | Conditional statement with an else block | `{true block} {false block} true ifelse` |
730
+ | if | Conditional statement | `true {...} if` |
731
+ | ifelse | Conditional statement with an else block | `true {true block} {false block} ifelse` |
692
732
  | iferror | Conditional statement for error handling | `{try block} {catch block} iferror` |
733
+ | cond | Multi-branch conditional (first matching pair wins) | `{x 0 >} {"pos"} {true} {"other"} 2 cond` |
693
734
  | do | Loop | `0 10 i {i echo} do` |
735
+ | dolist | Loop over the elements of a list | `[1 2 3] x {x echo} dolist` |
694
736
  | times | Loop a specified number of times | `{dup ++} 10 times` |
737
+ | while | Loop while a condition is true | `{i 10 <} {i ++ $i set} while` |
695
738
  | break | Break out of a loop | `break` |
696
739
 
697
740
 
@@ -714,6 +757,32 @@ print(stacker.eval("3 4 +"))
714
757
  | filter | Filter an array based on a condition | `[1 2 3 4 5] {2 % 0 ==} filter` |
715
758
  | all | Check if all elements of an array satisfy a condition | `[1 2 3 4 5] {2 % 0 ==} all` |
716
759
  | any | Check if any element of an array satisfies a condition | `[1 2 3 4 5] {2 % 0 ==} any` |
760
+ | car | Return the first element of a list | `[1 2 3] car` |
761
+ | cdr | Return the list without its first element | `[1 2 3] cdr` |
762
+ | cons | Prepend an element to a list | `1 [2 3] cons` |
763
+ | pair? | Return true if the value is a non-empty list | `[1 2 3] pair?` |
764
+ | apply | Expand list elements onto the stack and apply a function | `[3 4] {+} apply` |
765
+
766
+ Note: Blocks passed to `map`, `filter`, `reduce`, and `fold` must leave a value on the stack. For side-effect-only iteration (e.g. printing each element), use `dolist` instead.
767
+
768
+
769
+ ### Type Operators
770
+ | Operator | Description | Example |
771
+ |----------|-------------------------------------------------------|----------------------------|
772
+ | int | Convert to int | `3.7 int` |
773
+ | float | Convert to float | `3 float` |
774
+ | str | Convert to string | `42 str` |
775
+ | bool | Convert to bool | `1 bool` |
776
+ | complex | Convert to complex number | `3 complex` |
777
+ | type | Get the type of a value | `42 type` |
778
+ | int? | Returns true if the value is an int (not bool) | `42 int?` |
779
+ | float? | Returns true if the value is a float | `3.14 float?` |
780
+ | str? | Returns true if the value is a string | `"hello" str?` |
781
+ | bool? | Returns true if the value is a bool | `true bool?` |
782
+ | complex? | Returns true if the value is a complex number | `1+2j complex?` |
783
+ | list? | Returns true if the value is a list | `[1 2 3] list?` |
784
+ | null? | Returns true if the value is null or an empty list | `null null?` |
785
+ | number? | Returns true if the value is a number (int, float, or complex) | `42 number?` |
717
786
 
718
787
 
719
788
  ### Other Operators
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pystacker"
7
- version = "1.9.2"
7
+ version = "1.13.0"
8
8
  authors = [
9
9
  {name = "remokasu"}
10
10
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pystacker
3
- Version: 1.9.2
3
+ Version: 1.13.0
4
4
  Summary: Stacker: RPN Calculator in Python
5
5
  Author: remokasu
6
6
  License: MIT License
@@ -276,6 +276,26 @@ Stacker allows for straightforward RPN input. For example:
276
276
 
277
277
  Result: Pushes `7` onto the stack as `x` equals `0`.
278
278
 
279
+ - ##### cond Statement
280
+
281
+ The `cond` statement evaluates condition-result pairs in order and executes the first matching branch.
282
+
283
+ Syntax:
284
+ ```bash
285
+ {condition1} {result1} {condition2} {result2} ... n cond
286
+ ```
287
+
288
+ `n` is the number of condition-result pairs.
289
+
290
+ Example:
291
+ ```bash
292
+ stacker:0> 5 $x set
293
+ stacker:1> {x 0 >} {"positive"} {x 0 <} {"negative"} {true} {"zero"} 3 cond
294
+ ["positive"]
295
+ ```
296
+
297
+ Result: Pushes `"positive"` onto the stack as `x` is greater than `0`. Use `{true}` as the last condition to provide a default branch.
298
+
279
299
  - #### Loops
280
300
 
281
301
  Loops in Stacker allow for repeated execution of code blocks.
@@ -347,23 +367,43 @@ Stacker allows for straightforward RPN input. For example:
347
367
 
348
368
  Result: Pushes numbers 1 through 11 onto the stack by repeatedly duplicating and incrementing.
349
369
 
370
+ - ##### while Loop
371
+
372
+ The `while` loop repeats a code block while a condition is true.
373
+
374
+ Syntax:
375
+ ```bash
376
+ {condition} {body} while
377
+ ```
378
+
379
+ Example:
380
+ ```bash
381
+ stacker:0> 1 $i set 0 $s set
382
+ stacker:2> {i 6 <=} {s i + $s set i ++ $i set} while
383
+ stacker:2> s
384
+ [21]
385
+ ```
386
+
387
+ Result: Sums numbers from 1 to 6.
388
+
389
+ Note: Use `{condition}` as a code block that leaves a boolean on the stack. The loop terminates when the condition evaluates to false.
390
+
350
391
  - #### break
351
- - syntax:
352
- ```bash
353
- {break}
354
- ```
355
- - example:
356
- ```bash
357
- stacker:0> 0 i set
358
- stacker:1> 0 9 i {{break} i 5 == if i echo} do
359
- 0
360
- 1
361
- 2
362
- 3
363
- 4
364
- 5
365
- ```
366
- This example prints the numbers from 0 to 5. When `i` is equal to `5`, the loop is terminated by `break`.
392
+
393
+ `break` immediately exits the innermost loop (`do`, `dolist`, `times`, `while`).
394
+
395
+ Syntax:
396
+ ```bash
397
+ {break}
398
+ ```
399
+
400
+ Example:
401
+ ```bash
402
+ stacker:0> 0 $s set 1 10 $i {i 5 == {break} if s i + $s set} do s
403
+ [10]
404
+ ```
405
+
406
+ Result: Sums 1 through 4, then exits when `i` reaches 5. Note that `break` inside `if` immediately stops execution of the current block — `s i + $s set` is not executed when `i` equals 5.
367
407
 
368
408
  - ### Define a function:
369
409
  - syntax:
@@ -722,11 +762,14 @@ print(stacker.eval("3 4 +"))
722
762
  ### Control Operators
723
763
  | Operator | Description | Example |
724
764
  |----------|-------------------------------------------------------|----------------------------|
725
- | if | Conditional statement | `{...} true if` |
726
- | ifelse | Conditional statement with an else block | `{true block} {false block} true ifelse` |
765
+ | if | Conditional statement | `true {...} if` |
766
+ | ifelse | Conditional statement with an else block | `true {true block} {false block} ifelse` |
727
767
  | iferror | Conditional statement for error handling | `{try block} {catch block} iferror` |
768
+ | cond | Multi-branch conditional (first matching pair wins) | `{x 0 >} {"pos"} {true} {"other"} 2 cond` |
728
769
  | do | Loop | `0 10 i {i echo} do` |
770
+ | dolist | Loop over the elements of a list | `[1 2 3] x {x echo} dolist` |
729
771
  | times | Loop a specified number of times | `{dup ++} 10 times` |
772
+ | while | Loop while a condition is true | `{i 10 <} {i ++ $i set} while` |
730
773
  | break | Break out of a loop | `break` |
731
774
 
732
775
 
@@ -749,6 +792,32 @@ print(stacker.eval("3 4 +"))
749
792
  | filter | Filter an array based on a condition | `[1 2 3 4 5] {2 % 0 ==} filter` |
750
793
  | all | Check if all elements of an array satisfy a condition | `[1 2 3 4 5] {2 % 0 ==} all` |
751
794
  | any | Check if any element of an array satisfies a condition | `[1 2 3 4 5] {2 % 0 ==} any` |
795
+ | car | Return the first element of a list | `[1 2 3] car` |
796
+ | cdr | Return the list without its first element | `[1 2 3] cdr` |
797
+ | cons | Prepend an element to a list | `1 [2 3] cons` |
798
+ | pair? | Return true if the value is a non-empty list | `[1 2 3] pair?` |
799
+ | apply | Expand list elements onto the stack and apply a function | `[3 4] {+} apply` |
800
+
801
+ Note: Blocks passed to `map`, `filter`, `reduce`, and `fold` must leave a value on the stack. For side-effect-only iteration (e.g. printing each element), use `dolist` instead.
802
+
803
+
804
+ ### Type Operators
805
+ | Operator | Description | Example |
806
+ |----------|-------------------------------------------------------|----------------------------|
807
+ | int | Convert to int | `3.7 int` |
808
+ | float | Convert to float | `3 float` |
809
+ | str | Convert to string | `42 str` |
810
+ | bool | Convert to bool | `1 bool` |
811
+ | complex | Convert to complex number | `3 complex` |
812
+ | type | Get the type of a value | `42 type` |
813
+ | int? | Returns true if the value is an int (not bool) | `42 int?` |
814
+ | float? | Returns true if the value is a float | `3.14 float?` |
815
+ | str? | Returns true if the value is a string | `"hello" str?` |
816
+ | bool? | Returns true if the value is a bool | `true bool?` |
817
+ | complex? | Returns true if the value is a complex number | `1+2j complex?` |
818
+ | list? | Returns true if the value is a list | `[1 2 3] list?` |
819
+ | null? | Returns true if the value is null or an empty list | `null null?` |
820
+ | number? | Returns true if the value is a number (int, float, or complex) | `42 number?` |
752
821
 
753
822
 
754
823
  ### Other Operators
@@ -20,6 +20,7 @@ stacker/data/top.txt
20
20
  stacker/engine/__init__.py
21
21
  stacker/engine/core.py
22
22
  stacker/engine/data_type.py
23
+ stacker/engine/recursion.py
23
24
  stacker/engine/scope.py
24
25
  stacker/engine/sfunction.py
25
26
  stacker/engine/slambda.py
@@ -77,23 +78,44 @@ stacker/syntax/__init__.py
77
78
  stacker/syntax/lexer.py
78
79
  stacker/syntax/parser.py
79
80
  stacker/syntax/test_parser.py
81
+ stacker/syntax/token_rules.py
80
82
  stacker/util/__init__.py
81
83
  stacker/util/color.py
82
84
  stacker/util/disp.py
83
85
  test/test_aggregate.py
86
+ test/test_apply.py
87
+ test/test_block_comments.py
88
+ test/test_cond.py
89
+ test/test_debug_flag.py
84
90
  test/test_defmacro.py
91
+ test/test_defmacro_advanced.py
85
92
  test/test_defun.py
93
+ test/test_defun_advanced.py
94
+ test/test_disp.py
86
95
  test/test_error_formatter.py
87
96
  test/test_error_messages.py
97
+ test/test_expand.py
98
+ test/test_hof_advanced.py
88
99
  test/test_if_else_statement.py
89
100
  test/test_include.py
90
101
  test/test_inline_comments.py
91
102
  test/test_lambda.py
103
+ test/test_lazy_block_binding.py
104
+ test/test_list_primitives.py
105
+ test/test_loop_advanced.py
92
106
  test/test_loop_statement.py
107
+ test/test_multiline_accumulation.py
93
108
  test/test_no_dollar_in_blocks.py
94
109
  test/test_no_dollar_syntax.py
110
+ test/test_recursion_limit.py
95
111
  test/test_reduce.py
112
+ test/test_repl_continuation.py
96
113
  test/test_scope.py
114
+ test/test_semantics_freeze.py
97
115
  test/test_stacker.py
98
116
  test/test_string_apostrophe.py
99
- test/test_transform.py
117
+ test/test_token_list_cache.py
118
+ test/test_transform.py
119
+ test/test_triple_quote_strings.py
120
+ test/test_unterminated_errors.py
121
+ test/test_while_loop.py
@@ -10,8 +10,16 @@ import stacker
10
10
 
11
11
  from stacker.runtime.exec_modes import CommandLineMode, ReplMode, ScriptMode
12
12
 
13
+ from stacker.engine import recursion
13
14
  from stacker.lib import disp_logo
14
- from stacker.lib.config import plugins_dir_path, stacker_dotfile_path
15
+ from stacker.lib.config import (
16
+ DEFAULT_RECURSION_LIMIT,
17
+ MAX_RECURSION_LIMIT,
18
+ RECURSION_BACKSTOP_MARGIN,
19
+ RECURSION_FRAMES_PER_LEVEL,
20
+ plugins_dir_path,
21
+ stacker_dotfile_path,
22
+ )
15
23
  from stacker.stacker import Stacker
16
24
  from stacker.util import colored
17
25
 
@@ -21,13 +29,34 @@ parser.add_argument(
21
29
  )
22
30
  parser.add_argument("--debug", action="store_true", help="Enable debug mode")
23
31
  parser.add_argument("-e", default=None, help="Execute the given command.")
32
+ parser.add_argument(
33
+ "--recursion-limit",
34
+ metavar="N",
35
+ type=int,
36
+ default=DEFAULT_RECURSION_LIMIT,
37
+ help=(
38
+ "Maximum Stacker function recursion depth "
39
+ f"(1-{MAX_RECURSION_LIMIT}, default {DEFAULT_RECURSION_LIMIT})."
40
+ ),
41
+ )
24
42
  parser.add_argument("script", nargs="?", default=None, help="Script file to run.")
25
43
  argv = parser.parse_args()
26
44
 
27
- sys.setrecursionlimit(1 << 30)
45
+ if not 1 <= argv.recursion_limit <= MAX_RECURSION_LIMIT:
46
+ parser.error(
47
+ f"--recursion-limit must be between 1 and {MAX_RECURSION_LIMIT}, "
48
+ f"got {argv.recursion_limit}"
49
+ )
50
+ recursion.set_limit(argv.recursion_limit)
51
+
52
+ # Finite backstop replacing the old `1 << 30`: pathological recursion that
53
+ # bypasses the function-call guard stops with RecursionError, not a segfault
54
+ sys.setrecursionlimit(
55
+ MAX_RECURSION_LIMIT * RECURSION_FRAMES_PER_LEVEL + RECURSION_BACKSTOP_MARGIN
56
+ )
28
57
 
29
58
 
30
- def load_stacker_lib(stacker: Stacker, dir_path) -> bool:
59
+ def load_stacker_lib(stacker: Stacker, dir_path: str) -> bool:
31
60
  """Load the Stacker library from the specified directory.
32
61
  :param stacker: The Stacker instance to pass to the plugins.
33
62
  :param dir_path: The directory to load the Stacker library from.
@@ -48,7 +77,7 @@ def load_stacker_lib(stacker: Stacker, dir_path) -> bool:
48
77
  return True
49
78
 
50
79
 
51
- def load_plugins(stacker: Stacker, plugins_dir_path) -> bool:
80
+ def load_plugins(stacker: Stacker, plugins_dir_path: str) -> bool:
52
81
  """Load plugins from the plugins directory.
53
82
  :param stacker: The Stacker instance to pass to the plugins.
54
83
  :return: None
@@ -111,7 +140,7 @@ def copy_plugin_to_install_dir(plugin_path: str, debug_mode: bool) -> None:
111
140
  traceback.print_exc()
112
141
 
113
142
 
114
- def main():
143
+ def main() -> None:
115
144
  """Main entry point for the Stacker CLI."""
116
145
  # add plugin
117
146
  if argv.addplugin:
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import math
4
4
 
5
5
 
6
- constants = {
6
+ constants: dict[str, int | float | bool | None] = {
7
7
  "e": math.e,
8
8
  "pi": math.pi,
9
9
  "phi": (1 + math.sqrt(5)) / 2,