rapydscript-ns 0.8.1 → 0.8.3

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 (58) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/CONTRIBUTORS +3 -2
  3. package/PYTHON_DIFFERENCES_REPORT.md +291 -0
  4. package/PYTHON_FEATURE_COVERAGE.md +200 -0
  5. package/README.md +480 -79
  6. package/TODO.md +6 -318
  7. package/hack_demo.pyj +112 -0
  8. package/language-service/index.js +4474 -0
  9. package/language-service/language-service.d.ts +40 -0
  10. package/package.json +9 -10
  11. package/src/ast.pyj +30 -6
  12. package/src/baselib-builtins.pyj +181 -11
  13. package/src/baselib-containers.pyj +154 -5
  14. package/src/baselib-errors.pyj +3 -0
  15. package/src/baselib-internal.pyj +40 -1
  16. package/src/baselib-str.pyj +42 -1
  17. package/src/lib/collections.pyj +1 -1
  18. package/src/lib/numpy.pyj +10 -10
  19. package/src/monaco-language-service/analyzer.js +132 -22
  20. package/src/monaco-language-service/builtins.js +22 -2
  21. package/src/monaco-language-service/completions.js +224 -3
  22. package/src/monaco-language-service/diagnostics.js +55 -5
  23. package/src/monaco-language-service/index.js +26 -5
  24. package/src/monaco-language-service/scope.js +3 -0
  25. package/src/output/classes.pyj +20 -3
  26. package/src/output/codegen.pyj +38 -3
  27. package/src/output/functions.pyj +35 -25
  28. package/src/output/loops.pyj +64 -11
  29. package/src/output/modules.pyj +1 -4
  30. package/src/output/operators.pyj +67 -1
  31. package/src/output/statements.pyj +7 -3
  32. package/src/output/stream.pyj +6 -13
  33. package/src/parse.pyj +94 -14
  34. package/src/tokenizer.pyj +1 -0
  35. package/test/baselib.pyj +4 -4
  36. package/test/classes.pyj +56 -17
  37. package/test/collections.pyj +5 -5
  38. package/test/python_compat.pyj +326 -0
  39. package/test/python_features.pyj +1271 -0
  40. package/test/slice.pyj +105 -0
  41. package/test/str.pyj +25 -0
  42. package/test/unit/fixtures/fibonacci_expected.js +1 -1
  43. package/test/unit/index.js +119 -7
  44. package/test/unit/language-service-builtins.js +70 -0
  45. package/test/unit/language-service-bundle.js +83 -0
  46. package/test/unit/language-service-completions.js +289 -0
  47. package/test/unit/language-service-index.js +350 -0
  48. package/test/unit/language-service-scope.js +255 -0
  49. package/test/unit/language-service.js +158 -1
  50. package/test/unit/run-language-service.js +2 -0
  51. package/test/unit/web-repl.js +134 -0
  52. package/tools/build-language-service.js +2 -2
  53. package/tools/compiler.js +0 -24
  54. package/tools/export.js +3 -37
  55. package/tools/lint.js +1 -1
  56. package/tools/self.js +1 -9
  57. package/web-repl/rapydscript.js +6 -40
  58. package/web-repl/language-service.js +0 -4084
package/README.md CHANGED
@@ -2,15 +2,14 @@ RapydScript
2
2
  ===========
3
3
 
4
4
 
5
- [![Build Status](https://github.com/ebook-utils/kovidgoyal/rapydscript-ng/CI/badge.svg)](https://github.com/kovidgoyal/rapydscript-ng/actions?query=workflow%3ACI)
6
- [![Downloads](https://img.shields.io/npm/dm/rapydscript-ng.svg)](https://www.npmjs.com/package/rapydscript-ng)
7
- [![Current Release](https://img.shields.io/npm/v/rapydscript-ng.svg)](https://www.npmjs.com/package/rapydscript-ng)
8
- [![Known Vulnerabilities](https://snyk.io/test/github/kovidgoyal/rapydscript-ng/badge.svg)](https://snyk.io/test/github/kovidgoyal/rapydscript-ng)
5
+ [![Build Status](https://github.com/ficocelliguy/rapydscript-ns/actions/workflows/ci.yml/badge.svg)](https://github.com/ficocelliguy/rapydscript-ns/actions?query=workflow%3ACI)
6
+ [![Current Release](https://img.shields.io/npm/v/rapydscript-ns)](https://www.npmjs.com/package/rapydscript-ns)
7
+ [![Known Vulnerabilities](https://snyk.io/test/github/ficocelliguy/rapydscript-ns/badge.svg)](https://snyk.io/test/github/ficocelliguy/rapydscript-ns)
9
8
 
10
9
  This is a fork of the original RapydScript that adds many new (not always
11
10
  backwards compatible) features. For more on the forking, [see the bottom of this file](#reasons-for-the-fork)
12
11
 
13
- [Try RapydScript-ng live via an in-browser REPL!](https://sw.kovidgoyal.net/rapydscript/repl/)
12
+ [Try RapydScript-ns live via an in-browser REPL!](https://ficocelliguy.github.io/rapydscript-ns/)
14
13
 
15
14
  <!-- START doctoc generated TOC please keep comment here to allow auto update -->
16
15
  <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
@@ -109,29 +108,26 @@ Here are a few features of RapydScript:
109
108
  - similar to above, ability to use both, Python's and JavaScript's tutorials (as well as widgets)
110
109
  - it's self-hosting, that means the compiler is itself written in RapydScript and compiles into JavaScript
111
110
 
112
- Let's not waste any more time with the introductions, however. The best way to
113
- learn a new language/framework is to dive in.
114
-
115
111
 
116
112
  Installation
117
113
  ------------
118
114
 
119
- [Try RapydScript-ng live via an in-browser REPL!](https://sw.kovidgoyal.net/rapydscript/repl/)
115
+ [Try RapydScript-ns live via an in-browser REPL!](https://ficocelliguy.github.io/rapydscript-ns/)
120
116
 
121
117
  First make sure you have installed the latest version of [node.js](https://nodejs.org/) (You may need to restart your computer after this step).
122
118
 
123
119
  From NPM for use as a command line app:
124
120
 
125
- npm install rapydscript-ng -g
121
+ npm install rapydscript-ns -g
126
122
 
127
123
  From NPM for use in your own node project:
128
124
 
129
- npm install rapydscript-ng
125
+ npm install rapydscript-ns
130
126
 
131
127
  From Git:
132
128
 
133
- git clone https://github.com/kovidgoyal/rapydscript-ng.git
134
- cd rapydscript-ng
129
+ git clone https://github.com/ficocelliguy/rapydscript-ns.git
130
+ cd rapydscript-ns
135
131
  sudo npm link .
136
132
  npm install # This will automatically install the dependencies for RapydScript
137
133
 
@@ -358,17 +354,10 @@ math_ops = {
358
354
  }
359
355
  ```
360
356
 
361
- I'm sure you will agree that the above code is cleaner than declaring 5
362
- temporary variables first and assigning them to the object literal keys after.
363
357
  Note that the example puts the function header (def()) and content on the same
364
- line. I'll refer to it as function inlining. This is meant as a feature of
365
- RapydScript to make the code cleaner in cases like the example above. While you
366
- can use it in longer functions by chaining statements together using `;`, a
367
- good rule of thumb (to keep your code clean) is if your function needs
368
- semi-colons ask yourself whether you should be inlining, and if it needs more
369
- than 2 semi-colons, the answer is probably no (note that you can also use
370
- semi-colons as newline separators within functions that aren't inlined, as in
371
- the example in the previous section).
358
+ line (function inlining). This is a feature of RapydScript that can be used
359
+ to make the code cleaner in cases like the example above. You can also use it
360
+ in longer functions by chaining statements together using `;`.
372
361
 
373
362
 
374
363
  Lambda Expressions
@@ -544,7 +533,7 @@ $(element)\
544
533
  .show()
545
534
  ```
546
535
 
547
- Some of you might welcome this feature, some of you might not. RapydScript always aims to make its unique features unobtrusive to regular Python, which means that you don't have to use them if you disagree with them. Recently, we have enhanced this feature to handle `do/while` loops as well:
536
+ This feature handles `do/while` loops as well:
548
537
 
549
538
  ```js
550
539
  a = 0
@@ -647,7 +636,9 @@ into the names optional parameters you specified in the function definition.
647
636
 
648
637
  Inferred Tuple Packing/Unpacking
649
638
  --------------------------------
650
- Like Python, RapydScript allows inferred tuple packing/unpacking and assignment. While inferred/implicit logic is usually bad, it can sometimes make the code cleaner, and based on the order of statements in the Zen of Python, 'beautiful' takes priority over 'explicit'. For example, if you wanted to swap two variables, the following looks cleaner than explicitly declaring a temporary variable:
639
+ Like Python, RapydScript allows inferred tuple packing/unpacking and assignment. For
640
+ example, if you wanted to swap two variables, the following is simpler than explicitly
641
+ declaring a temporary variable:
651
642
 
652
643
  ```py
653
644
  a, b = b, a
@@ -727,7 +718,7 @@ Keywords:
727
718
  Operators:
728
719
 
729
720
  RapydScript JavaScript
730
-
721
+
731
722
  and &&
732
723
  or ||
733
724
  not !
@@ -736,7 +727,10 @@ Operators:
736
727
  +=1 ++
737
728
  -=1 --
738
729
  ** Math.pow()
739
-
730
+ **= x = Math.pow(x, y)
731
+
732
+ All Python augmented assignment operators are supported: `+=`, `-=`, `*=`, `/=`, `//=`, `**=`, `%=`, `>>=`, `<<=`, `|=`, `^=`, `&=`.
733
+
740
734
  Admittedly, `is` is not exactly the same thing in Python as `===` in JavaScript, but JavaScript is quirky when it comes to comparing objects anyway.
741
735
 
742
736
 
@@ -764,14 +758,19 @@ Containers (lists/sets/dicts)
764
758
  ### Lists
765
759
 
766
760
  Lists in RapydScript are almost identical to lists in Python, but are also
767
- native JavaScript arrays. The only small caveats are that the ``sort()`` and
768
- ``pop()`` methods are renamed to ``pysort()`` and ``pypop()``. This is so that
769
- you can pass RapydScript lists to external JavaScript libraries without any
770
- conflicts. Note that even list literals in RapydScript create python like list
771
- objects, and you can also use the builtin ``list()`` function to create lists
772
- from other iterable objects, just as you would in python. You can create a
773
- RapydScript list from a plain native JavaScript array by using the ``list_wrap()``
774
- function, like this:
761
+ native JavaScript arrays. The ``sort()`` and ``pop()`` methods behave exactly
762
+ as in Python: ``sort()`` performs a numeric sort (in-place, with optional ``key``
763
+ and ``reverse`` arguments) and ``pop()`` performs a bounds-checked pop (raises
764
+ ``IndexError`` for out-of-bounds indices). If you need the native JavaScript
765
+ behavior for interop with external JS libraries, use ``jssort()`` (lexicographic
766
+ sort) and ``jspop()`` (no bounds check, always pops the last element). The old
767
+ ``pysort()`` and ``pypop()`` names are kept as backward-compatible aliases.
768
+
769
+ Note that even list literals in RapydScript create Python-like list objects,
770
+ and you can also use the builtin ``list()`` function to create lists from other
771
+ iterable objects, just as you would in Python. You can create a RapydScript
772
+ list from a plain native JavaScript array by using the ``list_wrap()`` function,
773
+ like this:
775
774
 
776
775
  ```py
777
776
  a = v'[1, 2]'
@@ -779,6 +778,29 @@ pya = list_wrap(a)
779
778
  # Now pya is a python like list object that satisfies pya === a
780
779
  ```
781
780
 
781
+ ### List Concatenation
782
+
783
+ The `+` operator concatenates two lists and returns a new list, exactly as in Python:
784
+
785
+ ```py
786
+ a = [1, 2]
787
+ b = [3, 4]
788
+ c = a + b # [1, 2, 3, 4] — a and b are unchanged
789
+ ```
790
+
791
+ The `+=` operator extends a list in-place (the original list object is mutated):
792
+
793
+ ```py
794
+ a = [1, 2]
795
+ ref = a # ref and a point to the same list
796
+ a += [3, 4] # mutates a in-place
797
+ print(ref) # [1, 2, 3, 4] — ref sees the update
798
+ ```
799
+
800
+ No special flag is required. The `+` operator compiles to a lightweight helper
801
+ (`ρσ_list_add`) that uses `Array.concat` for lists and falls back to native JS
802
+ `+` for numbers and strings.
803
+
782
804
  ### Sets
783
805
 
784
806
  Sets in RapydScript are identical to those in python. You can create them using
@@ -895,6 +917,30 @@ merged = {**pd1, **pd2} # isinstance(merged, dict) == True
895
917
  The spread items are translated using `Object.assign` for plain JS objects
896
918
  and `dict.update()` for Python dicts.
897
919
 
920
+ ### Dict merge operator `|` and `|=` (Python 3.9+)
921
+
922
+ When `from __python__ import overload_operators, dict_literals` is active,
923
+ Python dicts support the `|` (merge) and `|=` (update in-place) operators:
924
+
925
+ ```py
926
+ from __python__ import overload_operators, dict_literals
927
+
928
+ d1 = {'x': 1, 'y': 2}
929
+ d2 = {'y': 99, 'z': 3}
930
+
931
+ # Create a new merged dict — right-side values win on key conflict
932
+ merged = d1 | d2 # {'x': 1, 'y': 99, 'z': 3}
933
+
934
+ # Update d1 in place
935
+ d1 |= d2 # d1 is now {'x': 1, 'y': 99, 'z': 3}
936
+ ```
937
+
938
+ `d1 | d2` creates a new dict (neither operand is mutated).
939
+ `d1 |= d2` merges `d2` into `d1` and returns `d1`.
940
+
941
+ Without `overload_operators` the `|` symbol is bitwise OR — use
942
+ `{**d1, **d2}` spread syntax as a flag-free alternative.
943
+
898
944
 
899
945
  ### Arithmetic operator overloading
900
946
 
@@ -949,6 +995,16 @@ to the native JavaScript operator, so plain numbers, strings, and booleans
949
995
  continue to work as expected with no performance penalty when no dunder method
950
996
  is defined.
951
997
 
998
+ When `overload_operators` is active, string and list repetition with `*` works just like Python:
999
+
1000
+ ```py
1001
+ from __python__ import overload_operators
1002
+ 'ha' * 3 # 'hahaha'
1003
+ 3 * 'ha' # 'hahaha'
1004
+ [0] * 4 # [0, 0, 0, 0]
1005
+ [1, 2] * 2 # [1, 2, 1, 2]
1006
+ ```
1007
+
952
1008
  Because the dispatch adds one or two property lookups per operation, the flag
953
1009
  is **opt-in** rather than always-on. Enable it only in the files or scopes
954
1010
  where you need it.
@@ -978,7 +1034,166 @@ for your own types.
978
1034
 
979
1035
  RapydScript does not overload the ordering operators ```(>, <, >=,
980
1036
  <=)``` as doing so would be a big performance impact (function calls in
981
- JavaScript are very slow). So using them on containers is useless.
1037
+ JavaScript are very slow). So using them on containers is useless.
1038
+
1039
+ Chained comparisons work just like Python — each middle operand is evaluated only once:
1040
+
1041
+ ```py
1042
+ # All of these work correctly, including mixed-direction chains
1043
+ assert 1 < 2 < 3 # True
1044
+ assert 1 < 2 > 0 # True (1<2 AND 2>0)
1045
+ assert 1 < 2 > 3 == False # 1<2 AND 2>3 = True AND False = False
1046
+ ```
1047
+
1048
+ ### Python Truthiness and `__bool__`
1049
+
1050
+ By default RapydScript uses JavaScript truthiness, where empty arrays `[]` and
1051
+ empty objects `{}` are **truthy**. Activate full Python truthiness semantics
1052
+ with:
1053
+
1054
+ ```py
1055
+ from __python__ import truthiness
1056
+ ```
1057
+
1058
+ When this flag is active:
1059
+
1060
+ - **Empty containers are falsy**: `[]`, `{}`, `set()`, `''`, `0`, `None` are all falsy.
1061
+ - **`__bool__` is dispatched**: objects with a `__bool__` method control their truthiness.
1062
+ - **`__len__` is used**: objects with `__len__` are falsy when `len(obj) == 0`.
1063
+ - **`and`/`or` return operand values** (not booleans), just like Python.
1064
+ - **All condition positions** (`if`, `while`, `assert`, `not`, ternary) use Python semantics.
1065
+
1066
+ ```py
1067
+ from __python__ import truthiness
1068
+
1069
+ class Empty:
1070
+ def __bool__(self): return False
1071
+
1072
+ if not []: # True — [] is falsy
1073
+ print('empty')
1074
+
1075
+ x = [] or 'default' # x == 'default'
1076
+ y = [1] or 'default' # y == [1]
1077
+ z = [1] and 'ok' # z == 'ok'
1078
+ ```
1079
+
1080
+ The flag is **scoped** — it applies until the end of the enclosing
1081
+ function or class body. Use `from __python__ import no_truthiness` to
1082
+ disable it in a sub-scope.
1083
+
1084
+ ### Callable Objects (`__call__`)
1085
+
1086
+ Any class that defines `__call__` can be invoked directly with `obj(args)`,
1087
+ just like Python callable objects. This requires `from __python__ import truthiness`:
1088
+
1089
+ ```python
1090
+ from __python__ import truthiness
1091
+
1092
+ class Multiplier:
1093
+ def __init__(self, factor):
1094
+ self.factor = factor
1095
+ def __call__(self, x):
1096
+ return self.factor * x
1097
+
1098
+ triple = Multiplier(3)
1099
+ triple(7) # 21 — dispatches to triple.__call__(7)
1100
+ ```
1101
+
1102
+ `callable(obj)` returns `True` when `__call__` is defined. The dispatch is
1103
+ automatic for all direct function-call expressions that are simple names
1104
+ (i.e. not method accesses like `obj.method()`).
1105
+
1106
+ ### `frozenset`
1107
+
1108
+ RapydScript provides a full `frozenset` builtin — an immutable, unordered
1109
+ collection of unique elements, identical to Python's `frozenset`.
1110
+
1111
+ ```python
1112
+ fs = frozenset([1, 2, 3])
1113
+ len(fs) # 3
1114
+ 2 in fs # True
1115
+ isinstance(fs, frozenset) # True
1116
+
1117
+ # Set operations return new frozensets
1118
+ a = frozenset([1, 2, 3])
1119
+ b = frozenset([2, 3, 4])
1120
+ a.union(b) # frozenset({1, 2, 3, 4})
1121
+ a.intersection(b) # frozenset({2, 3})
1122
+ a.difference(b) # frozenset({1})
1123
+ a.symmetric_difference(b) # frozenset({1, 4})
1124
+
1125
+ a.issubset(frozenset([1, 2, 3, 4])) # True
1126
+ a.issuperset(frozenset([1, 2])) # True
1127
+ a.isdisjoint(frozenset([5, 6])) # True
1128
+
1129
+ # Compares equal to a set with the same elements
1130
+ frozenset([1, 2]).__eq__({1, 2}) # True
1131
+ ```
1132
+
1133
+ Mutation methods (`add`, `remove`, `discard`, `clear`, `update`) are not
1134
+ present on `frozenset` instances, enforcing immutability at the API level.
1135
+ `frozenset` objects can be iterated and copied with `.copy()`.
1136
+
1137
+ ### `issubclass`
1138
+
1139
+ `issubclass(cls, classinfo)` checks whether a class is a subclass of another
1140
+ class (or any class in a tuple of classes). Every class is considered a
1141
+ subclass of itself.
1142
+
1143
+ ```python
1144
+ class Animal: pass
1145
+ class Dog(Animal): pass
1146
+ class Poodle(Dog): pass
1147
+ class Cat(Animal): pass
1148
+
1149
+ issubclass(Dog, Animal) # True
1150
+ issubclass(Poodle, Animal) # True — transitive
1151
+ issubclass(Poodle, Dog) # True
1152
+ issubclass(Dog, Dog) # True — a class is its own subclass
1153
+ issubclass(Cat, Dog) # False
1154
+ issubclass(Animal, Dog) # False — parent is not a subclass of child
1155
+
1156
+ # tuple form — True if cls is a subclass of any entry
1157
+ issubclass(Dog, (Cat, Animal)) # True
1158
+ issubclass(Poodle, (Cat, Dog)) # True
1159
+ ```
1160
+
1161
+ `TypeError` is raised when either argument is not a class.
1162
+
1163
+ ### `hash`
1164
+
1165
+ `hash(obj)` returns an integer hash value for an object, following Python
1166
+ semantics:
1167
+
1168
+ | Type | Hash rule |
1169
+ |---|---|
1170
+ | `None` | `0` |
1171
+ | `bool` | `1` for `True`, `0` for `False` |
1172
+ | `int` / whole `float` | the integer value itself |
1173
+ | other `float` | derived from the bit pattern |
1174
+ | `str` | djb2 algorithm — stable within a process |
1175
+ | object with `__hash__` | dispatches to `__hash__()` |
1176
+ | class instance | stable identity hash (assigned on first call) |
1177
+ | `list` | `TypeError: unhashable type: 'list'` |
1178
+ | `set` | `TypeError: unhashable type: 'set'` |
1179
+ | `dict` | `TypeError: unhashable type: 'dict'` |
1180
+
1181
+ ```python
1182
+ hash(None) # 0
1183
+ hash(True) # 1
1184
+ hash(42) # 42
1185
+ hash(3.0) # 3 (whole float → same as int)
1186
+ hash('hello') # stable integer
1187
+
1188
+ class Point:
1189
+ def __init__(self, x, y):
1190
+ self.x = x
1191
+ self.y = y
1192
+ def __hash__(self):
1193
+ return self.x * 31 + self.y
1194
+
1195
+ hash(Point(1, 2)) # 33
1196
+ ```
982
1197
 
983
1198
  Loops
984
1199
  -----
@@ -1000,6 +1215,39 @@ for index, animal in enumerate(animals):
1000
1215
  print("index:"+index, "animal:"+animal)
1001
1216
  ```
1002
1217
 
1218
+ `enumerate()` supports an optional `start` argument (default 0):
1219
+
1220
+ ```py
1221
+ for index, animal in enumerate(animals, 1):
1222
+ print(str(index) + '. ' + animal) # 1-based numbering
1223
+ ```
1224
+
1225
+ Like in Python, `for` loops support an `else` clause that runs only when the loop completes without hitting a `break`:
1226
+
1227
+ ```py
1228
+ for animal in animals:
1229
+ if animal == 'cat':
1230
+ print('found a cat')
1231
+ break
1232
+ else:
1233
+ print('no cat found')
1234
+ ```
1235
+
1236
+ This is useful for search patterns where you want to take an action only if the searched item was not found.
1237
+
1238
+ `while` loops also support an `else` clause, which runs when the condition becomes `False` (i.e. no `break` was executed):
1239
+
1240
+ ```py
1241
+ i = 0
1242
+ while i < len(items):
1243
+ if items[i] == target:
1244
+ print('found at', i)
1245
+ break
1246
+ i += 1
1247
+ else:
1248
+ print('not found')
1249
+ ```
1250
+
1003
1251
  Like in Python, if you just want the index, you can use range:
1004
1252
 
1005
1253
  ```py
@@ -1118,6 +1366,45 @@ str.format('{0:02d} {n}', 1, n=2) == '01 2'
1118
1366
  ...
1119
1367
  ```
1120
1368
 
1369
+ The `format(value[, spec])` builtin is also supported. It applies the Python
1370
+ format-spec mini-language to a single value — the same mini-language that
1371
+ follows `:` in f-strings and `str.format()` fields:
1372
+
1373
+ ```py
1374
+ format(42, '08b') # '00101010' — zero-padded binary
1375
+ format(3.14159, '.2f') # '3.14' — fixed-point
1376
+ format('hi', '>10') # ' hi' — right-aligned in 10-char field
1377
+ format(42) # '42' — no spec: same as str(42)
1378
+ ```
1379
+
1380
+ Objects with a `__format__` method are dispatched to it, matching Python's
1381
+ protocol exactly.
1382
+
1383
+ String predicate methods are also available:
1384
+
1385
+ ```py
1386
+ str.isalpha('abc') # True — all alphabetic
1387
+ str.isdigit('123') # True — all digits
1388
+ str.isalnum('abc123') # True — alphanumeric
1389
+ str.isspace(' ') # True — all whitespace
1390
+ str.isupper('ABC') # True
1391
+ str.islower('abc') # True
1392
+ str.isidentifier('my_var') # True — valid Python identifier
1393
+ ```
1394
+
1395
+ Python 3.9 prefix/suffix removal:
1396
+
1397
+ ```py
1398
+ str.removeprefix('HelloWorld', 'Hello') # 'World'
1399
+ str.removesuffix('HelloWorld', 'World') # 'Hello'
1400
+ ```
1401
+
1402
+ Case-folding for locale-insensitive lowercase comparison:
1403
+
1404
+ ```py
1405
+ str.casefold('ÄÖÜ') == str.casefold('äöü') # True (maps to lowercase)
1406
+ ```
1407
+
1121
1408
  However, if you want to make the python string methods available on string
1122
1409
  objects, there is a convenience method in the standard library to do so. Use
1123
1410
  the following code:
@@ -1444,7 +1731,7 @@ E.a(onclick=def():
1444
1731
 
1445
1732
  Classes
1446
1733
  -------
1447
- This is where RapydScript really starts to shine. JavaScript is known for having really crappy class implementation (it's basically a hack on top of a normal function, most experienced users suggest using external libraries for creating those instead of creating them in pure JavaScript). Luckily RapydScript fixes that. Let's imagine we want a special text field that takes in a user color string and changes color based on it. Let's create such field via a class.
1734
+ JavaScript is not known for having excellent class implementation - but RapydScript improves on that. Imagine we want a special text field that takes in a user color string and changes color based on it. Let's create such field via a class:
1448
1735
 
1449
1736
  ```js
1450
1737
  class ColorfulTextField:
@@ -1627,6 +1914,65 @@ print(Counter.get_count()) # 2
1627
1914
 
1628
1915
  The `@classmethod` decorator compiles to a method placed directly on the class (not its prototype), with `cls` mapped to `this`. A prototype delegation shim is also generated so instance calls work correctly.
1629
1916
 
1917
+ ### Nested Classes
1918
+
1919
+ A class may be defined inside another class. The nested class becomes an attribute of the outer class (accessible as `Outer.Inner`) and is also reachable via instances (`self.Inner` inside methods). This mirrors Python semantics exactly.
1920
+
1921
+ ```py
1922
+ class Molecule:
1923
+ class Atom:
1924
+ def __init__(self, element):
1925
+ self.element = element
1926
+ def __repr__(self):
1927
+ return 'Atom(' + self.element + ')'
1928
+
1929
+ def __init__(self, elements):
1930
+ self.structure = []
1931
+ for e in elements:
1932
+ self.structure.push(Molecule.Atom(e))
1933
+
1934
+ water = Molecule(['H', 'H', 'O'])
1935
+ print(len(water.structure)) # 3
1936
+ print(water.structure[0].element) # H
1937
+ print(isinstance(water.structure[0], Molecule.Atom)) # True
1938
+ ```
1939
+
1940
+ The nested class is a full class in every respect — it can have its own methods, inherit from other classes, and contain further nested classes:
1941
+
1942
+ ```py
1943
+ class Universe:
1944
+ class Galaxy:
1945
+ class Star:
1946
+ def __init__(self, name):
1947
+ self.name = name
1948
+ def __init__(self, star_name):
1949
+ self.star = Universe.Galaxy.Star(star_name)
1950
+ def __init__(self, star_name):
1951
+ self.galaxy = Universe.Galaxy(star_name)
1952
+
1953
+ u = Universe('Sol')
1954
+ print(u.galaxy.star.name) # Sol
1955
+ print(isinstance(u.galaxy.star, Universe.Galaxy.Star)) # True
1956
+ ```
1957
+
1958
+ A nested class may also inherit from classes defined in the outer scope:
1959
+
1960
+ ```py
1961
+ class Animal:
1962
+ def __init__(self, sound):
1963
+ self.sound = sound
1964
+
1965
+ class Zoo:
1966
+ class Dog(Animal):
1967
+ def __init__(self):
1968
+ Animal.__init__(self, 'woof')
1969
+
1970
+ fido = Zoo.Dog()
1971
+ print(fido.sound) # woof
1972
+ print(isinstance(fido, Animal)) # True
1973
+ print(isinstance(fido, Zoo.Dog)) # True
1974
+ ```
1975
+
1630
1976
  ### External Classes
1631
1977
 
1632
1978
  RapydScript will automatically detect classes declared within the same scope (as long as the declaration occurs before use), as well as classes properly imported into the module (each module making use of a certain class should explicitly import the module containing that class). RapydScript will also properly detect native JavaScript classes (String, Array, Date, etc.). Unfortunately, RapydScript has no way of detecting classes from third-party libraries. In those cases, you could use the `new` keyword every time you create an object from such class. Alternatively, you could mark the class as external.
@@ -1756,11 +2102,45 @@ an ``__iter__`` method, just as you would in python. For example:
1756
2102
  print (x) # Will print 1, 2, 3
1757
2103
  ```
1758
2104
 
1759
- Note that unlike python, an iterators ``next()`` method does not return
1760
- the next value, but instead an object with two properties: ``done and value``.
1761
- ``value`` is the next value and done will be ``True`` when the iterator is
1762
- exhausted. No ``StopIteration`` exception is raised. These choices were
1763
- made so that the iterator works with other JavaScript code.
2105
+ Internally, an iterator's ``.next()`` method returns a JavaScript object with
2106
+ two properties: ``done`` and ``value``. ``value`` is the next value and
2107
+ ``done`` is ``True`` when the iterator is exhausted. This matches the
2108
+ JavaScript iterator protocol and allows interoperability with vanilla JS code.
2109
+
2110
+ RapydScript also provides the Python-style ``next()`` builtin, which wraps
2111
+ this protocol transparently:
2112
+
2113
+ ```python
2114
+ it = iter([1, 2, 3])
2115
+ next(it) # 1
2116
+ next(it) # 2
2117
+ next(it, 'end') # 3
2118
+ next(it, 'end') # 'end' (iterator exhausted, default returned)
2119
+ next(it) # raises StopIteration
2120
+ ```
2121
+
2122
+ When the iterator is exhausted and no default is given, ``StopIteration``
2123
+ is raised — matching standard Python behaviour.
2124
+
2125
+ The two-argument form ``iter(callable, sentinel)`` repeatedly calls
2126
+ ``callable`` (with no arguments) and yields each return value until it equals
2127
+ ``sentinel``, at which point the iterator stops:
2128
+
2129
+ ```python
2130
+ count = [0]
2131
+ def next_val():
2132
+ count[0] += 1
2133
+ return count[0]
2134
+
2135
+ list(iter(next_val, 4)) # [1, 2, 3]
2136
+
2137
+ for val in iter(next_val, 7):
2138
+ print(val) # 5, 6
2139
+ ```
2140
+
2141
+ The callable may be any function or object with a ``__call__`` method.
2142
+ Sentinel comparison uses strict equality (``===``), matching Python's
2143
+ ``is``-then-``==`` semantics for the common case of plain values.
1764
2144
 
1765
2145
  Generators
1766
2146
  ------------
@@ -1772,18 +2152,19 @@ def f():
1772
2152
  for i in range(3):
1773
2153
  yield i
1774
2154
 
1775
- [x for x in f()] == [1, 2, 3]
2155
+ [x for x in f()] == [0, 1, 2]
1776
2156
  ```
1777
2157
 
1778
2158
  There is full support for generators including the Python 3, ```yield from```
1779
- syntax.
2159
+ syntax.
1780
2160
 
1781
2161
  Generators create JavaScript iterator objects. For differences between python
1782
- and JavaScript iterators, see the section on iterators above.
2162
+ and JavaScript iterators, see the section on iterators above.
1783
2163
 
1784
- Currently, generators are down-converted to ES 5 switch statements. In the
1785
- future, when ES 6 support is widespread, they will be converted to native
1786
- JavaScript ES 6 generators.
2164
+ By default, generators are down-converted to ES 5 switch statements. Pass
2165
+ ``--js-version 6`` to the compiler (or set ``js_version: 6`` in the embedded
2166
+ compiler options) to emit native ES 6 generator functions instead, which are
2167
+ smaller and faster.
1787
2168
 
1788
2169
  Modules
1789
2170
  -------
@@ -1924,7 +2305,7 @@ finally:
1924
2305
  ```
1925
2306
 
1926
2307
  You can create your own Exception classes by inheriting from `Exception`, which
1927
- is the JavaScript Error class, for more details on this, see (the MDN documentation)[https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Error].
2308
+ is the JavaScript Error class, for more details on this, see the [MDN documentation](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Error).
1928
2309
 
1929
2310
  ```py
1930
2311
  class MyError(Exception):
@@ -1935,7 +2316,7 @@ class MyError(Exception):
1935
2316
  raise MyError('This is a custom error!')
1936
2317
  ```
1937
2318
 
1938
- You can lump multiple errors in the same except block as well:
2319
+ You can catch multiple exception types in one `except` clause. Both the comma form and the tuple form are supported:
1939
2320
 
1940
2321
  ```py
1941
2322
  try:
@@ -1943,9 +2324,31 @@ try:
1943
2324
  except ReferenceError, TypeError as e:
1944
2325
  print(e.name + ':' + e.message)
1945
2326
  raise # re-raise the exception
2327
+
2328
+ # Equivalent tuple form (Python 3 style):
2329
+ try:
2330
+ risky()
2331
+ except (ReferenceError, TypeError) as e:
2332
+ handle(e)
1946
2333
  ```
1947
2334
 
1948
- Basically, `try/except/finally` in RapydScript works very similar to the way it does in Python 3.
2335
+ Exception chaining with `raise X from Y` is supported. The cause is stored on the raised exception's `__cause__` attribute:
2336
+
2337
+ ```py
2338
+ try:
2339
+ open_file('data.txt')
2340
+ except OSError as exc:
2341
+ raise ValueError('Could not read config') from exc
2342
+ ```
2343
+
2344
+ Use `raise X from None` to explicitly suppress the chained context:
2345
+
2346
+ ```py
2347
+ except SomeInternalError:
2348
+ raise PublicError('something went wrong') from None
2349
+ ```
2350
+
2351
+ Basically, `try/except/finally` in RapydScript works very similar to the way it does in Python 3.
1949
2352
 
1950
2353
  Scope Control
1951
2354
  -------------
@@ -1999,30 +2402,31 @@ Shadowing is preferred in most cases, since it can't accidentally damage outside
1999
2402
  Available Libraries
2000
2403
  -------------------
2001
2404
 
2002
- One of Python's main strengths is the number of libraries available to the developer. This is something very few other `Python-in-a-browser` frameworks understand. In the browser JavaScript is king, and no matter how many libraries the community for the given project will write, the readily-available JavaScript libraries will always outnumber them. This is why RapydScript was designed with JavaScript and DOM integration in mind from the beginning. Indeed, plugging `underscore.js` in place of RapydScript's `stdlib` will work just as well, and some developers may choose to do so, after all, `underscore.js` is very Pythonic and very complete.
2405
+ One of Python's main strengths is the number of libraries available to the developer. The large number of readily-available JavaScript libraries will always outnumber community-made Rapydscript libraries. This is why RapydScript was designed with JS and DOM integration in mind from the beginning. For example, plugging in `lodash` in place of RapydScript's `stdlib` will work fine!
2003
2406
 
2004
- It is for that reason that I try to keep RapydScript bells and whistles to a minimum. RapydScript's main strength is easy integration with JavaScript and DOM, which allows me to stay sane and not rewrite my own versions of the libraries that are already available. That doesn't mean, however, that pythonic libraries can't be written for RapydScript. To prove that, I have implemented lightweight clones of several popular Python libraries and bundled them into RapydScript, you can find them in `src` directory. The following libraries are included:
2407
+ RapydScript's main strength is easy integration with JavaScript and DOM, and easy use of libraries that are already available. That doesn't mean, however, that pythonic libraries can't be written for RapydScript. Rapydscript comes with lightweight clones of several popular Python libraries, which you can find them in `src` directory.
2005
2408
 
2006
2409
  math # replicates almost all of the functionality from Python's math library
2007
2410
  re # replicates almost all of the functionality from Python's re library
2008
2411
  random # replicates most of the functionality from Python's random library
2412
+ numpy # NumPy-compatible array library (ndarray, ufuncs, numpy.random, numpy.linalg)
2009
2413
  elementmaker # easily construct DOM trees
2010
2414
  aes # Implement AES symmetric encryption
2011
2415
  encodings # Convert to/from UTF-8 bytearrays, base64 strings and native strings
2012
2416
  gettext # Support for internationalization of your RapydScript app
2013
- operator # a subset of python;s operator module
2417
+ operator # a subset of Python's operator module
2014
2418
  functools # reduce, partial, wraps, lru_cache, cache, total_ordering, cmp_to_key
2015
2419
  collections # namedtuple, deque, Counter, OrderedDict, defaultdict
2016
2420
  itertools # count, cycle, repeat, accumulate, chain, compress, dropwhile, filterfalse,
2017
2421
  # groupby, islice, pairwise, starmap, takewhile, zip_longest,
2018
2422
  # product, permutations, combinations, combinations_with_replacement
2019
2423
 
2020
- For the most part, the logic implemented in these libraries functions identically to the Python versions. I'd be happy to include more libraries, if other members of the community want to implement them (it's fun to do, `re.pyj` is a good example), but I want to reemphasize that unlike most other Python-to-JavaScript compilers, RapydScript doesn't need them to be complete since there are already tons of available JavaScript libraries that it can use natively.
2424
+ For the most part, the logic implemented in these libraries functions identically to the Python versions. I'd be happy to include more libraries, if other members of the community want them. However, unlike most other Python-to-JavaScript compilers, RapydScript doesn't need them to be complete since there are already tons of available JavaScript libraries that it can use natively.
2021
2425
 
2022
2426
  Linter
2023
2427
  ---------
2024
2428
 
2025
- The RapydScript compiler includes its own, built in linter. The linter is
2429
+ The RapydScript compiler includes its own, built-in linter. The linter is
2026
2430
  modeled on pyflakes, it catches instances of unused/undefined variables,
2027
2431
  functions, symbols, etc. While this sounds simple, it is surprisingly effective
2028
2432
  in practice. To run the linter:
@@ -2102,19 +2506,18 @@ RapydScript will pick up any classes you declare yourself as well as native
2102
2506
  JavaScript classes. It will not, however, pick up class-like objects created by
2103
2507
  outside frameworks. There are two approaches for dealing with those. One is via
2104
2508
  `@external` decorator, the other is via `new` operator when declaring such
2105
- object. To keep code legible and consistent, I strongly prefer the use of
2106
- `@external` decorator over the `new` operator for several reasons, even if it
2107
- may be more verbose:
2509
+ object. The `@external` decorator is recommended over the `new` operator for
2510
+ several reasons, even if it may be more verbose:
2108
2511
 
2109
2512
  - `@external` decorator makes classes declared externally obvious to anyone looking at your code
2110
2513
  - class declaration that uses `@external` decorator can be exported into a reusable module
2111
- - developers are much more likely to forget a single instance of `new` operator when declaring an object than to forget an import, the errors due to omitted `new` keyword are also likely to be more subtle and devious to debug
2514
+ - developers are much more likely to forget a single instance of `new` operator when declaring an object than to forget an import. the errors due to omitted `new` keyword are also likely to be more subtle and devious to debug
2112
2515
 
2113
2516
  #### Embedding the RapydScript compiler in your webpage
2114
2517
 
2115
2518
  You can embed the RapydScript compiler in your webpage so that you can have
2116
2519
  your webapp directly compile user supplied RapydScript code into JavaScript.
2117
- To do so, simply include the [embeddable rapydscript compiler](https://sw.kovidgoyal.net/rapydscript/repl/rapydscript.js)
2520
+ To do so, simply include the [embeddable rapydscript compiler](https://github.com/ficocelliguy/rapydscript-ns/blob/master/web-repl/rapydscript.js)
2118
2521
  in your page, and use it to compile arbitrary RapydScript code.
2119
2522
 
2120
2523
  You create the compiler by calling: `RapydScript.create_embedded_compiler()` and compile
@@ -2128,7 +2531,7 @@ HTML below for an example.
2128
2531
  <head>
2129
2532
  <meta charset="UTF-8">
2130
2533
  <title>Test embedded RapydScript</title>
2131
- <script charset="UTF-8" src="https://sw.kovidgoyal.net/rapydscript/repl/rapydscript.js"></script>
2534
+ <script charset="UTF-8" src="rapydscript.js"></script>
2132
2535
  <script>
2133
2536
  var compiler = RapydScript.create_embedded_compiler();
2134
2537
  var js = compiler.compile("def hello_world():\n a='RapydScript is cool!'\n print(a)\n alert(a)");
@@ -2222,9 +2625,10 @@ to an experienced Python developer. The most important such gotchas are listed
2222
2625
  below:
2223
2626
 
2224
2627
  - Truthiness in JavaScript is very different from Python. Empty lists and dicts
2225
- are ``False`` in Python but ``True`` in JavaScript. The compiler could work
2226
- around that, but not without a significant performance cost, so it is best to
2227
- just get used to checking the length instead of the object directly.
2628
+ are ``False`` in Python but ``True`` in JavaScript. You can opt in to full
2629
+ Python truthiness semantics (where empty containers are falsy and ``__bool__``
2630
+ is dispatched) with ``from __python__ import truthiness``. Without that flag,
2631
+ test the length explicitly instead of the container directly.
2228
2632
 
2229
2633
  - Operators in JavaScript are very different from Python. ``1 + '1'`` would be
2230
2634
  an error in Python, but results in ``'11'`` in JavaScript. Similarly, ``[1] +
@@ -2285,7 +2689,7 @@ npm run build:ls
2285
2689
  node tools/build-language-service.js --out path/to/language-service.js
2286
2690
  ```
2287
2691
 
2288
- The output is written to `web-repl/language-service.js` by default.
2692
+ The output is written to `language-service/index.js` by default.
2289
2693
 
2290
2694
  ### Basic setup
2291
2695
 
@@ -2480,7 +2884,7 @@ and assembled into the standard `mappings` field. The implementation lives in
2480
2884
 
2481
2885
  ```bash
2482
2886
  node bin/web-repl-export web-repl # rebuilds web-repl/rapydscript.js
2483
- node tools/build-language-service.js # rebuilds web-repl/language-service.js
2887
+ node tools/build-language-service.js # rebuilds language-service/index.js
2484
2888
  ```
2485
2889
 
2486
2890
  ### Running the tests
@@ -2496,17 +2900,14 @@ completions, signature help, hover, DTS registry, and built-in stubs).
2496
2900
  Reasons for the fork
2497
2901
  ----------------------
2498
2902
 
2499
- The fork was initially created because the original developer of RapydScript
2500
- did not have the time to keep up with the pace of development. Since then,
2501
- development on the original RapydScript seems to have stalled completely.
2502
- Also, there are certain disagreements on the future direction of RapydScript.
2503
-
2504
- Regardless, this fork is not a hostile fork, if development on the original
2505
- ever resumes, they are welcome to use the code from this fork. I have kept all
2506
- new code under the same license, to make that possible.
2903
+ The fork was created because both the original developer of RapydScript
2904
+ and the developer of the prior fork rapydscript-ng both did not have
2905
+ the time to keep up with the pace of development. Rapydscript has not had
2906
+ any npm updates since 2020, and rapydscript-ng since 2022.
2507
2907
 
2508
- See the [Changelog](https://github.com/kovidgoyal/rapydscript-ng/blob/master/CHANGELOG.md)
2509
- for a list of changes to rapydscript-ng since the fork.
2908
+ This fork is not a hostile fork - if development on the prior versions
2909
+ ever resumes, they are welcome to use the code from this fork. All the
2910
+ new code is under the same license, to make that possible.
2510
2911
 
2511
- For some discussion surrounding the fork, see
2512
- [this bug report](https://github.com/kovidgoyal/rapydscript-ng/issues/15)
2912
+ See the [Changelog](https://github.com/ficocelliguy/rapydscript-ns/blob/master/CHANGELOG.md)
2913
+ for a list of changes to rapydscript-ns, including this fork at version 8.0