koatl 0.3.19__tar.gz → 0.4.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 (156) hide show
  1. {koatl-0.3.19 → koatl-0.4.0}/Cargo.lock +1 -1
  2. {koatl-0.3.19 → koatl-0.4.0}/PKG-INFO +1 -1
  3. {koatl-0.3.19 → koatl-0.4.0}/koatl/Cargo.toml +1 -1
  4. {koatl-0.3.19 → koatl-0.4.0}/koatl/python/koatl/runtime/__init__.py +10 -0
  5. {koatl-0.3.19 → koatl-0.4.0}/koatl/src/emit_py.rs +8 -2
  6. koatl-0.4.0/koatl/tests/e2e/base/elif.tl +92 -0
  7. koatl-0.4.0/koatl/tests/e2e/base/fstr.tl +51 -0
  8. koatl-0.4.0/koatl/tests/e2e/base/locals.tl +68 -0
  9. {koatl-0.3.19 → koatl-0.4.0}/koatl-core/src/ast.rs +1 -1
  10. {koatl-0.3.19 → koatl-0.4.0}/koatl-core/src/ast_builder.rs +1 -1
  11. {koatl-0.3.19 → koatl-0.4.0}/koatl-core/src/lift_cst.rs +19 -4
  12. {koatl-0.3.19 → koatl-0.4.0}/koatl-core/src/py/ast_builder.rs +5 -1
  13. {koatl-0.3.19 → koatl-0.4.0}/koatl-core/src/resolve_scopes.rs +66 -29
  14. {koatl-0.3.19 → koatl-0.4.0}/koatl-core/src/transform.rs +133 -20
  15. {koatl-0.3.19 → koatl-0.4.0}/koatl-parser/src/cst.rs +1 -0
  16. {koatl-0.3.19 → koatl-0.4.0}/koatl-parser/src/lexer.rs +8 -6
  17. {koatl-0.3.19 → koatl-0.4.0}/koatl-parser/src/parser.rs +19 -0
  18. {koatl-0.3.19 → koatl-0.4.0}/koatl-parser/src/simple_fmt.rs +19 -10
  19. {koatl-0.3.19 → koatl-0.4.0}/python/koatl/runtime/__init__.py +10 -0
  20. koatl-0.3.19/koatl/tests/e2e/base/fstr.tl +0 -27
  21. {koatl-0.3.19 → koatl-0.4.0}/Cargo.toml +0 -0
  22. {koatl-0.3.19 → koatl-0.4.0}/README.md +0 -0
  23. {koatl-0.3.19 → koatl-0.4.0}/koatl/.github/workflows/CI.yml +0 -0
  24. {koatl-0.3.19 → koatl-0.4.0}/koatl/.gitignore +0 -0
  25. {koatl-0.3.19 → koatl-0.4.0}/koatl/LICENSE +0 -0
  26. {koatl-0.3.19 → koatl-0.4.0}/koatl/README.md +0 -0
  27. {koatl-0.3.19 → koatl-0.4.0}/koatl/python/koatl/__init__.py +0 -0
  28. {koatl-0.3.19 → koatl-0.4.0}/koatl/python/koatl/__main__.py +0 -0
  29. {koatl-0.3.19 → koatl-0.4.0}/koatl/python/koatl/cli.py +0 -0
  30. {koatl-0.3.19 → koatl-0.4.0}/koatl/python/koatl/notebook/__init__.py +0 -0
  31. {koatl-0.3.19 → koatl-0.4.0}/koatl/python/koatl/notebook/magic.py +0 -0
  32. {koatl-0.3.19 → koatl-0.4.0}/koatl/python/koatl/prelude/__init__.tl +0 -0
  33. {koatl-0.3.19 → koatl-0.4.0}/koatl/python/koatl/runtime/meta_finder.py +0 -0
  34. {koatl-0.3.19 → koatl-0.4.0}/koatl/python/koatl/runtime/record.py +0 -0
  35. {koatl-0.3.19 → koatl-0.4.0}/koatl/python/koatl/runtime/vattr.py +0 -0
  36. {koatl-0.3.19 → koatl-0.4.0}/koatl/python/koatl/std/__init__.tl +0 -0
  37. {koatl-0.3.19 → koatl-0.4.0}/koatl/python/koatl/std/control/__init__.tl +0 -0
  38. {koatl-0.3.19 → koatl-0.4.0}/koatl/python/koatl/std/control/async.tl +0 -0
  39. {koatl-0.3.19 → koatl-0.4.0}/koatl/python/koatl/std/control/base.tl +0 -0
  40. {koatl-0.3.19 → koatl-0.4.0}/koatl/python/koatl/std/control/do.tl +0 -0
  41. {koatl-0.3.19 → koatl-0.4.0}/koatl/python/koatl/std/control/env.tl +0 -0
  42. {koatl-0.3.19 → koatl-0.4.0}/koatl/python/koatl/std/control/memo.tl +0 -0
  43. {koatl-0.3.19 → koatl-0.4.0}/koatl/python/koatl/std/control/result.tl +0 -0
  44. {koatl-0.3.19 → koatl-0.4.0}/koatl/python/koatl/std/data/__init__.tl +0 -0
  45. {koatl-0.3.19 → koatl-0.4.0}/koatl/python/koatl/std/data/list.tl +0 -0
  46. {koatl-0.3.19 → koatl-0.4.0}/koatl/python/koatl/std/data/record.tl +0 -0
  47. {koatl-0.3.19 → koatl-0.4.0}/koatl/python/koatl/std/ext.tl +0 -0
  48. {koatl-0.3.19 → koatl-0.4.0}/koatl/python/koatl/std/io.tl +0 -0
  49. {koatl-0.3.19 → koatl-0.4.0}/koatl/python/koatl/std/iter.tl +0 -0
  50. {koatl-0.3.19 → koatl-0.4.0}/koatl/python/koatl/std/json.tl +0 -0
  51. {koatl-0.3.19 → koatl-0.4.0}/koatl/python/koatl/std/lazy_module.tl +0 -0
  52. {koatl-0.3.19 → koatl-0.4.0}/koatl/python/koatl/std/pickle.tl +0 -0
  53. {koatl-0.3.19 → koatl-0.4.0}/koatl/python/koatl/std/re.tl +0 -0
  54. {koatl-0.3.19 → koatl-0.4.0}/koatl/python/koatl/std/trait.py +0 -0
  55. {koatl-0.3.19 → koatl-0.4.0}/koatl/requirements.txt +0 -0
  56. {koatl-0.3.19 → koatl-0.4.0}/koatl/src/lib.rs +0 -0
  57. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/conftest.py +0 -0
  58. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/base/arguments.tl +0 -0
  59. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/base/containers.tl +0 -0
  60. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/base/data.txt +0 -0
  61. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/base/decorators.tl +0 -0
  62. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/base/destructure-for-and-fn.tl +0 -0
  63. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/base/destructure.tl +0 -0
  64. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/base/escape_ident.tl +0 -0
  65. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/base/functions.tl +0 -0
  66. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/base/generator.tl +0 -0
  67. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/base/if_expr.tl +0 -0
  68. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/base/imports.tl +0 -0
  69. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/base/imports2.tl +0 -0
  70. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/base/loops.tl +0 -0
  71. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/base/match.tl +0 -0
  72. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/base/nary-list.tl +0 -0
  73. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/base/placeholder.tl +0 -0
  74. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/base/precedence.tl +0 -0
  75. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/base/scopes.tl +0 -0
  76. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/base/semantic_whitespace.tl +0 -0
  77. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/base/short_circuit.tl +0 -0
  78. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/base/with.tl +0 -0
  79. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/prelude/async.tl +0 -0
  80. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/prelude/aug_assign.tl +0 -0
  81. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/prelude/coal.tl +0 -0
  82. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/prelude/env.tl +0 -0
  83. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/prelude/iterables.tl +0 -0
  84. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/prelude/list.tl +0 -0
  85. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/prelude/memo.tl +0 -0
  86. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/prelude/record.tl +0 -0
  87. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/prelude/result.tl +0 -0
  88. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/prelude/slice.tl +0 -0
  89. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/prelude/try.tl +0 -0
  90. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/prelude/virtual.tl +0 -0
  91. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/test_modules/module0.tl +0 -0
  92. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/test_modules/module1.tl +0 -0
  93. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/e2e/test_modules/module2.tl +0 -0
  94. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/fail-parse/arguments_mixed_defaults.tl +0 -0
  95. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/fail-parse/arguments_multiple_kwargs.tl +0 -0
  96. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/fail-parse/arguments_multiple_kwonly_markers.tl +0 -0
  97. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/fail-parse/arguments_multiple_posonly_markers.tl +0 -0
  98. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/fail-parse/arguments_multiple_varargs.tl +0 -0
  99. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/fail-parse/arguments_posonly_after_kwonly.tl +0 -0
  100. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/fail-parse/arguments_vararg_and_kwonly_marker.tl +0 -0
  101. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/fail-parse/unmatched_closing_delimiter.tl +0 -0
  102. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/parse/arith.tl +0 -0
  103. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/parse/assign.tl +0 -0
  104. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/parse/block-comments.tl +0 -0
  105. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/parse/deco.tl +0 -0
  106. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/parse/func.tl +0 -0
  107. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/parse/matches.tl +0 -0
  108. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/parse/mutual-recursion.tl +0 -0
  109. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/parse/numbers.tl +0 -0
  110. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/parse/operators.tl +0 -0
  111. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/test_e2e.py +0 -0
  112. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/test_iterable.tl +0 -0
  113. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/test_parse.py +0 -0
  114. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/test_parse_fail.py +0 -0
  115. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/test_re.tl +0 -0
  116. {koatl-0.3.19 → koatl-0.4.0}/koatl/tests/util/__init__.py +0 -0
  117. {koatl-0.3.19 → koatl-0.4.0}/koatl-core/Cargo.toml +0 -0
  118. {koatl-0.3.19 → koatl-0.4.0}/koatl-core/src/inference.rs +0 -0
  119. {koatl-0.3.19 → koatl-0.4.0}/koatl-core/src/lib.rs +0 -0
  120. {koatl-0.3.19 → koatl-0.4.0}/koatl-core/src/main.rs +0 -0
  121. {koatl-0.3.19 → koatl-0.4.0}/koatl-core/src/py/ast.rs +0 -0
  122. {koatl-0.3.19 → koatl-0.4.0}/koatl-core/src/py/emit.rs +0 -0
  123. {koatl-0.3.19 → koatl-0.4.0}/koatl-core/src/py/mod.rs +0 -0
  124. {koatl-0.3.19 → koatl-0.4.0}/koatl-core/src/types.rs +0 -0
  125. {koatl-0.3.19 → koatl-0.4.0}/koatl-core/src/util.rs +0 -0
  126. {koatl-0.3.19 → koatl-0.4.0}/koatl-parser/Cargo.toml +0 -0
  127. {koatl-0.3.19 → koatl-0.4.0}/koatl-parser/src/lib.rs +0 -0
  128. {koatl-0.3.19 → koatl-0.4.0}/pyproject.toml +0 -0
  129. {koatl-0.3.19 → koatl-0.4.0}/python/koatl/__init__.py +0 -0
  130. {koatl-0.3.19 → koatl-0.4.0}/python/koatl/__main__.py +0 -0
  131. {koatl-0.3.19 → koatl-0.4.0}/python/koatl/cli.py +0 -0
  132. {koatl-0.3.19 → koatl-0.4.0}/python/koatl/notebook/__init__.py +0 -0
  133. {koatl-0.3.19 → koatl-0.4.0}/python/koatl/notebook/magic.py +0 -0
  134. {koatl-0.3.19 → koatl-0.4.0}/python/koatl/prelude/__init__.tl +0 -0
  135. {koatl-0.3.19 → koatl-0.4.0}/python/koatl/runtime/meta_finder.py +0 -0
  136. {koatl-0.3.19 → koatl-0.4.0}/python/koatl/runtime/record.py +0 -0
  137. {koatl-0.3.19 → koatl-0.4.0}/python/koatl/runtime/vattr.py +0 -0
  138. {koatl-0.3.19 → koatl-0.4.0}/python/koatl/std/__init__.tl +0 -0
  139. {koatl-0.3.19 → koatl-0.4.0}/python/koatl/std/control/__init__.tl +0 -0
  140. {koatl-0.3.19 → koatl-0.4.0}/python/koatl/std/control/async.tl +0 -0
  141. {koatl-0.3.19 → koatl-0.4.0}/python/koatl/std/control/base.tl +0 -0
  142. {koatl-0.3.19 → koatl-0.4.0}/python/koatl/std/control/do.tl +0 -0
  143. {koatl-0.3.19 → koatl-0.4.0}/python/koatl/std/control/env.tl +0 -0
  144. {koatl-0.3.19 → koatl-0.4.0}/python/koatl/std/control/memo.tl +0 -0
  145. {koatl-0.3.19 → koatl-0.4.0}/python/koatl/std/control/result.tl +0 -0
  146. {koatl-0.3.19 → koatl-0.4.0}/python/koatl/std/data/__init__.tl +0 -0
  147. {koatl-0.3.19 → koatl-0.4.0}/python/koatl/std/data/list.tl +0 -0
  148. {koatl-0.3.19 → koatl-0.4.0}/python/koatl/std/data/record.tl +0 -0
  149. {koatl-0.3.19 → koatl-0.4.0}/python/koatl/std/ext.tl +0 -0
  150. {koatl-0.3.19 → koatl-0.4.0}/python/koatl/std/io.tl +0 -0
  151. {koatl-0.3.19 → koatl-0.4.0}/python/koatl/std/iter.tl +0 -0
  152. {koatl-0.3.19 → koatl-0.4.0}/python/koatl/std/json.tl +0 -0
  153. {koatl-0.3.19 → koatl-0.4.0}/python/koatl/std/lazy_module.tl +0 -0
  154. {koatl-0.3.19 → koatl-0.4.0}/python/koatl/std/pickle.tl +0 -0
  155. {koatl-0.3.19 → koatl-0.4.0}/python/koatl/std/re.tl +0 -0
  156. {koatl-0.3.19 → koatl-0.4.0}/python/koatl/std/trait.py +0 -0
@@ -190,7 +190,7 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
190
190
 
191
191
  [[package]]
192
192
  name = "koatl"
193
- version = "0.3.19"
193
+ version = "0.4.0"
194
194
  dependencies = [
195
195
  "ariadne",
196
196
  "koatl-core",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: koatl
3
- Version: 0.3.19
3
+ Version: 0.4.0
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Python :: Implementation :: PyPy
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "koatl"
3
- version = "0.3.19"
3
+ version = "0.4.0"
4
4
  edition = "2021"
5
5
  homepage = "https://koatl.org"
6
6
 
@@ -1,5 +1,6 @@
1
1
  import functools
2
2
  import importlib
3
+ import builtins
3
4
  from types import SimpleNamespace
4
5
 
5
6
  from koatl.runtime.record import Record
@@ -33,6 +34,13 @@ def set_exports(package_name, globals_dict, exports, module_star_exports):
33
34
  globals_dict["__all__"] = tuple(set(globals_dict["__all__"]) | exports)
34
35
 
35
36
 
37
+ def redirect_locals(locals_name_map, py_locals_dict):
38
+ new_locals = {}
39
+ for tl_name, py_name in locals_name_map.items():
40
+ new_locals[tl_name] = py_locals_dict[py_name]
41
+ return new_locals
42
+
43
+
36
44
  __tl__ = SimpleNamespace(
37
45
  Exception=Exception,
38
46
  slice=slice,
@@ -42,6 +50,8 @@ __tl__ = SimpleNamespace(
42
50
  record_literal=Record.from_dict_ref,
43
51
  #
44
52
  set_exports=set_exports,
53
+ redirect_locals=redirect_locals,
54
+ builtins=builtins,
45
55
  #
46
56
  vget=vget,
47
57
  vhas=vhas,
@@ -708,11 +708,17 @@ impl<'src> PyExprExt<'src> for SPyExpr<'src> {
708
708
  PyFstrPart::Str(s) => {
709
709
  values.push(ctx.ast_node("Constant", (s.as_ref(),), &self.tl_span)?);
710
710
  }
711
- PyFstrPart::Expr(expr, _format_spec) => {
711
+ PyFstrPart::Expr(expr, format_spec) => {
712
712
  let expr_ast = expr.emit_py(ctx)?;
713
+ let format_spec_ast = if let Some(spec) = format_spec {
714
+ spec.emit_py(ctx)?
715
+ } else {
716
+ ctx.py.None()
717
+ };
718
+
713
719
  values.push(ctx.ast_node(
714
720
  "FormattedValue",
715
- (expr_ast, -1, ctx.py.None()),
721
+ (expr_ast, -1, format_spec_ast),
716
722
  &self.tl_span,
717
723
  )?);
718
724
  }
@@ -0,0 +1,92 @@
1
+ import util.assert_eq
2
+
3
+ # Test basic elif
4
+ x = 2
5
+ let result
6
+ if x == 1:
7
+ result = "one"
8
+ elif x == 2:
9
+ result = "two"
10
+ else:
11
+ result = "other"
12
+ assert_eq(result, "two")
13
+
14
+ # Test multiple elif clauses
15
+ score = 75
16
+ let grade = if score >= 90:
17
+ "A"
18
+ elif score >= 80:
19
+ "B"
20
+ elif score >= 70:
21
+ "C"
22
+ elif score >= 60:
23
+ "D"
24
+ else:
25
+ "F"
26
+ assert_eq(grade, "C")
27
+
28
+ # Test elif without else
29
+ value = 5
30
+ output = "default"
31
+ if value < 0:
32
+ output = "negative"
33
+ elif value == 0:
34
+ output = "zero"
35
+ elif value > 0:
36
+ output = "positive"
37
+ assert_eq(output, "positive")
38
+
39
+ # Test elif with expressions
40
+ a, b = 10, 20
41
+ let relation
42
+ if a > b:
43
+ relation = "greater"
44
+ elif a < b:
45
+ relation = "less"
46
+ elif a == b:
47
+ relation = "equal"
48
+ assert_eq(relation, "less")
49
+
50
+ # Test nested if with elif
51
+ outer_val = 2
52
+ inner_val = 3
53
+ let result2
54
+ if outer_val == 1:
55
+ result2 = "outer1"
56
+ elif outer_val == 2:
57
+ if inner_val == 1:
58
+ result2 = "inner1"
59
+ elif inner_val == 2:
60
+ result2 = "inner2"
61
+ elif inner_val == 3:
62
+ result2 = "inner3"
63
+ else:
64
+ result2 = "inner_else"
65
+ else:
66
+ result2 = "outer_else"
67
+ assert_eq(result2, "inner3")
68
+
69
+ # Test elif in expression context (as part of block expression)
70
+ num = 15
71
+ msg = if num < 10:
72
+ "small"
73
+ elif num < 20:
74
+ "medium"
75
+ else:
76
+ "large"
77
+ assert_eq(msg, "medium")
78
+
79
+ # Test elif with complex conditions
80
+ x, y = 5, 11
81
+ let result3
82
+ if x > 10 and y > 10:
83
+ result3 = "both large"
84
+ elif x > 10 or y > 10:
85
+ result3 = "one large"
86
+ elif x == y:
87
+ result3 = "equal"
88
+ else:
89
+ result3 = "both small"
90
+ assert_eq(result3, "one large")
91
+
92
+ print("All elif tests passed!")
@@ -0,0 +1,51 @@
1
+ import util.assert_eq
2
+
3
+ assert_eq(f"{1}", "1")
4
+
5
+ x = 1
6
+
7
+ fstr = f"x = {x + 1}"
8
+ assert_eq(fstr, "x = 2")
9
+
10
+ fstr = f"x = {(y => y)(x)}"
11
+ assert_eq(fstr, "x = 1")
12
+
13
+ fstr = f"={(
14
+ let a = 1
15
+ let b = 2
16
+ let c = 3
17
+ a * b * c
18
+ )}="
19
+ assert_eq(fstr, "=6=")
20
+
21
+ assert_eq(f"{{}}", "{}")
22
+ assert_eq(f"{{{1}}}", "{1}")
23
+
24
+ assert_eq(f"""
25
+
26
+ {{1}}
27
+ {2 * 5}""", "\n\n{1}\n10")
28
+
29
+ # Test format specifiers
30
+ num = 42
31
+ assert_eq(f"{num%d}", "42")
32
+ assert_eq(f"{num%05d}", "00042")
33
+ assert_eq(f"{num%>5d}", " 42")
34
+ assert_eq(f"{num%<5d}", "42 ")
35
+ assert_eq(f"{num%^5d}", " 42 ")
36
+
37
+ pi = 3.14159
38
+ assert_eq(f"{pi%.2f}", "3.14")
39
+ assert_eq(f"{pi%.4f}", "3.1416")
40
+
41
+ hex_num = 255
42
+ assert_eq(f"{hex_num%x}", "ff")
43
+ assert_eq(f"{hex_num%X}", "FF")
44
+ assert_eq(f"{hex_num%#x}", "0xff")
45
+
46
+ # Test multiple format specs in one string
47
+ assert_eq(f"{num%05d} and {pi%.2f}", "00042 and 3.14")
48
+
49
+ # Test format spec with expressions
50
+ assert_eq(f"{1 + 1%d}", "2")
51
+ assert_eq(f"{(10 * 5)%03d}", "050")
@@ -0,0 +1,68 @@
1
+ import util.assert_eq
2
+
3
+ # Test __locals__ in function scope with let declarations
4
+ test_function_locals = () =>
5
+ let y = 1
6
+ let z = 2
7
+ assert_eq(__locals__, {y: 1, z: 2})
8
+
9
+ test_function_locals()
10
+
11
+ # Test __locals__ in nested function
12
+ test_nested = () =>
13
+ let outer = 1
14
+ let inner = () =>
15
+ let inner_var = 2
16
+ assert_eq(__locals__, {inner_var: 2})
17
+ inner()
18
+
19
+ test_nested()
20
+
21
+ # Test __locals__ with function arguments (not mangled)
22
+ test_with_args = (arg1, arg2) =>
23
+ let local = 100
24
+ assert_eq(__locals__, {local: 100, arg1: arg1, arg2: arg2})
25
+
26
+ test_with_args(10, 20)
27
+
28
+ # Test __captures__ in nested function
29
+ test_nested_captures = () =>
30
+ let outer_var = 1
31
+ let another_outer = 2
32
+ let inner = () =>
33
+ let inner_var = 3
34
+ outer_var, another_outer
35
+
36
+ # Should capture outer_var and another_outer but not inner_var
37
+ assert_eq(__captures__, {assert_eq, inner, outer_var, another_outer})
38
+ inner()
39
+
40
+ test_nested_captures()
41
+
42
+ # Test __captures__ with multiple nesting levels
43
+ test_deep_nesting = () =>
44
+ let level1 = 1
45
+ let middle = () =>
46
+ let level2 = 2
47
+ let inner = () =>
48
+ let level3 = 3
49
+ # Should capture level1 and level2 but not level3
50
+ assert_eq(__captures__, {assert_eq, inner, middle, level1, level2})
51
+ inner()
52
+ middle()
53
+
54
+ test_deep_nesting()
55
+
56
+ test_no_captures = () =>
57
+ assert_eq(__captures__, {test_no_captures, assert_eq})
58
+
59
+ test_no_captures()
60
+
61
+ # Test __captures__ with function arguments in outer scope
62
+ test_args_captured = (arg1, arg2) =>
63
+ let local_outer = 100
64
+ let inner = () =>
65
+ assert_eq(__captures__, {assert_eq, inner, arg1, arg2, local_outer})
66
+ inner()
67
+
68
+ test_args_captured(10, 20)
@@ -74,7 +74,7 @@ pub enum ImportLeaf<'a> {
74
74
  #[derive(Debug, Clone)]
75
75
  pub struct ImportTree<'a> {
76
76
  pub trunk: Vec<SIdent<'a>>,
77
- pub leaf: Spanned<ImportLeaf<'a>>,
77
+ pub leaf: Indirect<Spanned<ImportLeaf<'a>>>,
78
78
 
79
79
  // number of dots prepending the trunk
80
80
  pub level: usize,
@@ -231,7 +231,7 @@ impl AstBuilder {
231
231
  .into_iter()
232
232
  .map(|t| t.into().spanned(self.span))
233
233
  .collect(),
234
- leaf: ImportLeaf::Star.spanned(self.span),
234
+ leaf: ImportLeaf::Star.spanned(self.span).indirect(),
235
235
  level,
236
236
  }
237
237
  }
@@ -208,15 +208,30 @@ impl<'src, 'tok> Lift<Indirect<ast::SExpr<'src>>> for cst::SExpr<'src, 'tok> {
208
208
  body,
209
209
  else_clause,
210
210
  ..
211
+ } => {
212
+ let else_expr = else_clause.as_ref().map(|(_, expr)| expr.lift());
213
+ ast::Expr::If(cond.lift(), body.lift(), else_expr)
211
214
  }
212
- | cst::Expr::ClassicIf {
215
+ cst::Expr::ClassicIf {
213
216
  cond,
214
217
  body,
218
+ elif_clauses,
215
219
  else_clause,
216
220
  ..
217
221
  } => {
218
- let else_expr = else_clause.as_ref().map(|(_, expr)| expr.lift());
219
- ast::Expr::If(cond.lift(), body.lift(), else_expr)
222
+ // Convert elif chain into nested if-else
223
+ let mut result_else = else_clause.as_ref().map(|(_, expr)| expr.lift());
224
+
225
+ // Process elif clauses in reverse order to build nested structure
226
+ for (_, elif_cond, elif_body) in elif_clauses.iter().rev() {
227
+ let elif_body_lifted = elif_body.lift();
228
+ let span = elif_body_lifted.span;
229
+ let elif_expr = ast::Expr::If(elif_cond.lift(), elif_body_lifted, result_else)
230
+ .spanned(span);
231
+ result_else = Some(elif_expr.indirect());
232
+ }
233
+
234
+ ast::Expr::If(cond.lift(), body.lift(), result_else)
220
235
  }
221
236
  cst::Expr::Parenthesized { expr, .. } => return expr.lift(),
222
237
  cst::Expr::Slice {
@@ -637,7 +652,7 @@ impl<'src, 'tok> Lift<ast::ImportTree<'src>> for cst::ImportTree<cst::STree<'src
637
652
  .iter()
638
653
  .map(|(ident, _)| ident.lift_as_ident())
639
654
  .collect(),
640
- leaf: self.leaf.value.lift().spanned(self.leaf.span),
655
+ leaf: self.leaf.value.lift().spanned(self.leaf.span).indirect(),
641
656
  }
642
657
  }
643
658
  }
@@ -252,7 +252,11 @@ impl PyAstBuilder {
252
252
  }
253
253
 
254
254
  pub fn py_builtin<'src>(&self, name: &'static str) -> SPyExpr<'src> {
255
- self.attribute(self.load_ident("__builtins__"), name, PyAccessCtx::Load)
255
+ self.attribute(
256
+ self.attribute(self.load_ident("__tl__"), "builtins", PyAccessCtx::Load),
257
+ name,
258
+ PyAccessCtx::Load,
259
+ )
256
260
  }
257
261
 
258
262
  pub fn subscript<'src>(
@@ -38,6 +38,7 @@ pub struct ResolveState<'src> {
38
38
  pub export_stars: Vec<SIdent<'src>>,
39
39
 
40
40
  pub resolutions: HashMap<RefHash, DeclarationKey>,
41
+ pub locals_scope_refs: HashMap<RefHash, ScopeKey>,
41
42
  pub patterns: HashMap<RefHash, PatternInfo>,
42
43
 
43
44
  // TODO: these should all be collapsed into the same thing...
@@ -62,7 +63,7 @@ pub struct ResolveState<'src> {
62
63
  errors: TlErrs,
63
64
 
64
65
  placeholder_stack: Vec<PlaceholderGuard>,
65
- fn_stack: Vec<FnInfo>,
66
+ phantom_fn_stack: Vec<FnInfo>,
66
67
  scope_stack: Vec<ScopeKey>,
67
68
  }
68
69
 
@@ -99,6 +100,7 @@ impl<'src> ResolveState<'src> {
99
100
  source,
100
101
 
101
102
  resolutions: HashMap::new(),
103
+ locals_scope_refs: HashMap::new(),
102
104
  functions: HashMap::new(),
103
105
  patterns: HashMap::new(),
104
106
  memo_fninfo: HashMap::new(),
@@ -114,7 +116,7 @@ impl<'src> ResolveState<'src> {
114
116
  errors: TlErrs::new(),
115
117
 
116
118
  placeholder_stack: Vec::new(),
117
- fn_stack: Vec::new(),
119
+ phantom_fn_stack: Vec::new(),
118
120
  scope_stack: Vec::new(),
119
121
  }
120
122
  }
@@ -137,14 +139,14 @@ impl<'src> ResolveState<'src> {
137
139
  return Ok(found.decl);
138
140
  }
139
141
 
140
- let Some(fn_ctx) = self.fn_stack.last_mut() else {
142
+ let Some(fn_ctx) = self.phantom_fn_stack.last_mut() else {
141
143
  // This should never happen since if not fn_local, there must be at least one function context
142
144
  return Err(simple_err("Internal error: no function context", ident.span).into());
143
145
  };
144
146
 
145
147
  fn_ctx.captures.insert(found.decl.clone().into());
146
148
 
147
- for fn_ctx in self.fn_stack.iter_mut().rev().skip(1) {
149
+ for fn_ctx in self.phantom_fn_stack.iter_mut().rev().skip(1) {
148
150
  if fn_ctx.decls.contains(&found.decl) {
149
151
  break;
150
152
  }
@@ -160,7 +162,7 @@ impl<'src> ResolveState<'src> {
160
162
  if lhs {
161
163
  if scope.is_class || scope.is_global {
162
164
  let decl = self.declarations.insert_declaration(
163
- &mut self.fn_stack,
165
+ &mut self.phantom_fn_stack,
164
166
  ident.clone(),
165
167
  scope_key,
166
168
  DeclType::Let,
@@ -174,7 +176,7 @@ impl<'src> ResolveState<'src> {
174
176
 
175
177
  if !lhs {
176
178
  let decl = self.declarations.insert_declaration(
177
- &mut self.fn_stack,
179
+ &mut self.phantom_fn_stack,
178
180
  ident.clone(),
179
181
  self.root_scope,
180
182
  DeclType::Let,
@@ -227,7 +229,7 @@ impl<'src> ResolveState<'src> {
227
229
  let temp_scope_key = self.scopes.insert(dummy_scope);
228
230
 
229
231
  let ph_decl = self.declarations.insert_declaration(
230
- &mut self.fn_stack,
232
+ &mut self.phantom_fn_stack,
231
233
  Ident("x".into()).spanned(span),
232
234
  temp_scope_key,
233
235
  DeclType::Let,
@@ -242,11 +244,11 @@ impl<'src> ResolveState<'src> {
242
244
  self.placeholder_stack
243
245
  .push(PlaceholderGuard::new(ph_decl, span));
244
246
 
245
- self.fn_stack.push(FnInfo::new());
247
+ self.phantom_fn_stack.push(FnInfo::new());
246
248
 
247
249
  let result = f(self);
248
250
 
249
- let mut ph_fn_ctx = self.fn_stack.pop().unwrap();
251
+ let mut ph_fn_ctx = self.phantom_fn_stack.pop().unwrap();
250
252
  let placeholder_ctx = self.placeholder_stack.pop().unwrap();
251
253
 
252
254
  if placeholder_ctx.activated {
@@ -292,7 +294,7 @@ impl<'src> ResolveState<'src> {
292
294
  self.set_generator(span);
293
295
  }
294
296
 
295
- if let Some(cur_fn_ctx) = self.fn_stack.last_mut() {
297
+ if let Some(cur_fn_ctx) = self.phantom_fn_stack.last_mut() {
296
298
  cur_fn_ctx.captures.extend(ph_fn_ctx.captures);
297
299
  }
298
300
 
@@ -352,9 +354,12 @@ impl<'src> ResolveState<'src> {
352
354
 
353
355
  let key = *self.scope_stack.last().unwrap();
354
356
 
355
- let decl =
356
- self.declarations
357
- .insert_declaration(&mut self.fn_stack, name.clone(), key, modifier);
357
+ let decl = self.declarations.insert_declaration(
358
+ &mut self.phantom_fn_stack,
359
+ name.clone(),
360
+ key,
361
+ modifier,
362
+ );
358
363
 
359
364
  if lifted {
360
365
  self.scopes[key].lifted_decls.push(decl.clone());
@@ -366,7 +371,7 @@ impl<'src> ResolveState<'src> {
366
371
  }
367
372
 
368
373
  fn set_async(&mut self, span: Span) -> () {
369
- if let Some(fn_ctx) = self.fn_stack.last_mut() {
374
+ if let Some(fn_ctx) = self.phantom_fn_stack.last_mut() {
370
375
  fn_ctx.is_async = true;
371
376
  } else if !self.allow_top_level_await {
372
377
  self.errors.extend(simple_err(
@@ -377,7 +382,7 @@ impl<'src> ResolveState<'src> {
377
382
  }
378
383
 
379
384
  fn set_do(&mut self, span: Span) -> () {
380
- if let Some(fn_ctx) = self.fn_stack.last_mut() {
385
+ if let Some(fn_ctx) = self.phantom_fn_stack.last_mut() {
381
386
  fn_ctx.is_do = true;
382
387
  } else {
383
388
  self.errors.extend(simple_err(
@@ -388,7 +393,7 @@ impl<'src> ResolveState<'src> {
388
393
  }
389
394
 
390
395
  fn set_generator(&mut self, span: Span) -> () {
391
- if let Some(fn_ctx) = self.fn_stack.last_mut() {
396
+ if let Some(fn_ctx) = self.phantom_fn_stack.last_mut() {
392
397
  fn_ctx.is_generator = true;
393
398
  } else {
394
399
  self.errors.extend(simple_err(
@@ -650,7 +655,7 @@ fn error_ident_and_decl<'src>(
650
655
  let expr = Expr::Ident(ident.clone()).spanned(span).indirect();
651
656
 
652
657
  let decl = state.declarations.insert_declaration(
653
- &mut state.fn_stack,
658
+ &mut state.phantom_fn_stack,
654
659
  ident.clone(),
655
660
  state.err_scope,
656
661
  DeclType::Let,
@@ -901,7 +906,7 @@ fn pattern_scoped<'src>(
901
906
  .iter()
902
907
  .map(|x| {
903
908
  state.declarations.insert_declaration(
904
- &mut state.fn_stack,
909
+ &mut state.phantom_fn_stack,
905
910
  x.clone().spanned(pattern.span),
906
911
  scope_key,
907
912
  DeclType::Let,
@@ -928,11 +933,11 @@ where
928
933
  {
929
934
  let fn_ctx = FnInfo::new();
930
935
 
931
- state.fn_stack.push(fn_ctx);
936
+ state.phantom_fn_stack.push(fn_ctx);
932
937
 
933
938
  let ret = f(state);
934
939
 
935
- let fn_ctx = state.fn_stack.pop().unwrap();
940
+ let fn_ctx = state.phantom_fn_stack.pop().unwrap();
936
941
 
937
942
  if fn_ctx.is_async {
938
943
  state.set_async(span);
@@ -989,6 +994,21 @@ impl<'src> SExprExt<'src> for Indirect<SExpr<'src>> {
989
994
  let t = match self.value {
990
995
  Expr::Literal(_) => return self,
991
996
  Expr::Ident(ident) => {
997
+ // Check for special __locals__ identifier
998
+ if ident.value.0.as_ref() == "__locals__" {
999
+ let expr = Expr::Ident(ident).spanned(span).indirect();
1000
+ // Store the current scope for __locals__
1001
+ let current_scope = *state.scope_stack.last().unwrap();
1002
+ state
1003
+ .locals_scope_refs
1004
+ .insert(expr.as_ref().into(), current_scope);
1005
+ return expr;
1006
+ } else if ident.value.0.as_ref() == "__captures__" {
1007
+ let expr = Expr::Ident(ident).spanned(span).indirect();
1008
+
1009
+ return expr;
1010
+ }
1011
+
992
1012
  let decl = match state.resolve(&ident, false) {
993
1013
  Ok(decl) => decl,
994
1014
  Err(errs) => {
@@ -1240,7 +1260,7 @@ impl<'src> SExprExt<'src> for Indirect<SExpr<'src>> {
1240
1260
  let cap = capture.spanned(pattern.span);
1241
1261
 
1242
1262
  let decl = state.declarations.insert_declaration(
1243
- &mut state.fn_stack,
1263
+ &mut state.phantom_fn_stack,
1244
1264
  cap,
1245
1265
  scope,
1246
1266
  DeclType::Let,
@@ -1264,7 +1284,7 @@ impl<'src> SExprExt<'src> for Indirect<SExpr<'src>> {
1264
1284
  }
1265
1285
  ArgDefItem::ArgSpread(arg) => {
1266
1286
  let decl = state.declarations.insert_declaration(
1267
- &mut state.fn_stack,
1287
+ &mut state.phantom_fn_stack,
1268
1288
  arg.clone(),
1269
1289
  scope,
1270
1290
  DeclType::Let,
@@ -1276,7 +1296,7 @@ impl<'src> SExprExt<'src> for Indirect<SExpr<'src>> {
1276
1296
  }
1277
1297
  ArgDefItem::KwargSpread(arg) => {
1278
1298
  let decl = state.declarations.insert_declaration(
1279
- &mut state.fn_stack,
1299
+ &mut state.phantom_fn_stack,
1280
1300
  arg.clone(),
1281
1301
  scope,
1282
1302
  DeclType::Let,
@@ -1312,7 +1332,7 @@ impl<'src> SExprExt<'src> for Indirect<SExpr<'src>> {
1312
1332
 
1313
1333
  state.scopes[scope].locals.extend(decls);
1314
1334
 
1315
- state.fn_stack.push(fn_info);
1335
+ state.phantom_fn_stack.push(fn_info);
1316
1336
 
1317
1337
  let n_scopes = state.scope_stack.len();
1318
1338
 
@@ -1341,7 +1361,9 @@ impl<'src> SExprExt<'src> for Indirect<SExpr<'src>> {
1341
1361
  (n_fndef_lifted, n_classdef_lifted)
1342
1362
  };
1343
1363
 
1344
- let body = state.scoped(scope, |state| body.traverse(state)).value;
1364
+ let body = state
1365
+ .scoped(scope, |state| body.traverse_expecting_scope(state))
1366
+ .value;
1345
1367
 
1346
1368
  {
1347
1369
  // put back
@@ -1362,7 +1384,7 @@ impl<'src> SExprExt<'src> for Indirect<SExpr<'src>> {
1362
1384
  }
1363
1385
  }
1364
1386
 
1365
- let fn_ctx = state.fn_stack.pop().unwrap();
1387
+ let fn_ctx = state.phantom_fn_stack.pop().unwrap();
1366
1388
  let expr = Expr::Fn(items, body).spanned(span).indirect();
1367
1389
 
1368
1390
  state.functions.insert(expr.as_ref().into(), fn_ctx);
@@ -1700,7 +1722,7 @@ impl<'src> SStmtExt<'src> for Indirect<SStmt<'src>> {
1700
1722
  let scope = &mut state.scopes[scope_key];
1701
1723
 
1702
1724
  let decl = state.declarations.insert_declaration(
1703
- &mut state.fn_stack,
1725
+ &mut state.phantom_fn_stack,
1704
1726
  alias.clone().unwrap_or(ident.clone()),
1705
1727
  scope_key,
1706
1728
  if reexport {
@@ -1712,6 +1734,10 @@ impl<'src> SStmtExt<'src> for Indirect<SStmt<'src>> {
1712
1734
 
1713
1735
  state.declarations[decl].is_import = true;
1714
1736
 
1737
+ state
1738
+ .resolutions
1739
+ .insert(tree.leaf.as_ref().into(), decl.clone());
1740
+
1715
1741
  scope.locals.push(decl);
1716
1742
  }
1717
1743
  ImportLeaf::This(alias) => {
@@ -1727,7 +1753,7 @@ impl<'src> SStmtExt<'src> for Indirect<SStmt<'src>> {
1727
1753
  }
1728
1754
 
1729
1755
  let decl = state.declarations.insert_declaration(
1730
- &mut state.fn_stack,
1756
+ &mut state.phantom_fn_stack,
1731
1757
  alias.clone().unwrap_or(trunk_accum.last().unwrap().clone()),
1732
1758
  scope_key,
1733
1759
  if reexport {
@@ -1739,9 +1765,20 @@ impl<'src> SStmtExt<'src> for Indirect<SStmt<'src>> {
1739
1765
 
1740
1766
  state.declarations[decl].is_import = true;
1741
1767
 
1768
+ state
1769
+ .resolutions
1770
+ .insert(tree.leaf.as_ref().into(), decl.clone());
1771
+
1742
1772
  scope.locals.push(decl);
1743
1773
  }
1744
1774
  ImportLeaf::Star => {
1775
+ if state.scope_stack.len() > 1 {
1776
+ state.errors.extend(simple_err(
1777
+ "Wildcard imports are only allowed in the global scope",
1778
+ tree.leaf.span,
1779
+ ));
1780
+ }
1781
+
1745
1782
  if reexport {
1746
1783
  state
1747
1784
  .export_stars
@@ -1777,7 +1814,7 @@ pub fn resolve_names<'src>(
1777
1814
  })
1778
1815
  .value;
1779
1816
 
1780
- if !state.fn_stack.is_empty() {
1817
+ if !state.phantom_fn_stack.is_empty() {
1781
1818
  panic!("Function context stack is not empty after resolving names");
1782
1819
  }
1783
1820