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.
- package/CHANGELOG.md +31 -0
- package/CONTRIBUTORS +3 -2
- package/PYTHON_DIFFERENCES_REPORT.md +291 -0
- package/PYTHON_FEATURE_COVERAGE.md +200 -0
- package/README.md +480 -79
- package/TODO.md +6 -318
- package/hack_demo.pyj +112 -0
- package/language-service/index.js +4474 -0
- package/language-service/language-service.d.ts +40 -0
- package/package.json +9 -10
- package/src/ast.pyj +30 -6
- package/src/baselib-builtins.pyj +181 -11
- package/src/baselib-containers.pyj +154 -5
- package/src/baselib-errors.pyj +3 -0
- package/src/baselib-internal.pyj +40 -1
- package/src/baselib-str.pyj +42 -1
- package/src/lib/collections.pyj +1 -1
- package/src/lib/numpy.pyj +10 -10
- package/src/monaco-language-service/analyzer.js +132 -22
- package/src/monaco-language-service/builtins.js +22 -2
- package/src/monaco-language-service/completions.js +224 -3
- package/src/monaco-language-service/diagnostics.js +55 -5
- package/src/monaco-language-service/index.js +26 -5
- package/src/monaco-language-service/scope.js +3 -0
- package/src/output/classes.pyj +20 -3
- package/src/output/codegen.pyj +38 -3
- package/src/output/functions.pyj +35 -25
- package/src/output/loops.pyj +64 -11
- package/src/output/modules.pyj +1 -4
- package/src/output/operators.pyj +67 -1
- package/src/output/statements.pyj +7 -3
- package/src/output/stream.pyj +6 -13
- package/src/parse.pyj +94 -14
- package/src/tokenizer.pyj +1 -0
- package/test/baselib.pyj +4 -4
- package/test/classes.pyj +56 -17
- package/test/collections.pyj +5 -5
- package/test/python_compat.pyj +326 -0
- package/test/python_features.pyj +1271 -0
- package/test/slice.pyj +105 -0
- package/test/str.pyj +25 -0
- package/test/unit/fixtures/fibonacci_expected.js +1 -1
- package/test/unit/index.js +119 -7
- package/test/unit/language-service-builtins.js +70 -0
- package/test/unit/language-service-bundle.js +83 -0
- package/test/unit/language-service-completions.js +289 -0
- package/test/unit/language-service-index.js +350 -0
- package/test/unit/language-service-scope.js +255 -0
- package/test/unit/language-service.js +158 -1
- package/test/unit/run-language-service.js +2 -0
- package/test/unit/web-repl.js +134 -0
- package/tools/build-language-service.js +2 -2
- package/tools/compiler.js +0 -24
- package/tools/export.js +3 -37
- package/tools/lint.js +1 -1
- package/tools/self.js +1 -9
- package/web-repl/rapydscript.js +6 -40
- package/web-repl/language-service.js +0 -4084
package/README.md
CHANGED
|
@@ -2,15 +2,14 @@ RapydScript
|
|
|
2
2
|
===========
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
[](https://snyk.io/test/github/kovidgoyal/rapydscript-ng)
|
|
5
|
+
[](https://github.com/ficocelliguy/rapydscript-ns/actions?query=workflow%3ACI)
|
|
6
|
+
[](https://www.npmjs.com/package/rapydscript-ns)
|
|
7
|
+
[](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-
|
|
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-
|
|
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-
|
|
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-
|
|
125
|
+
npm install rapydscript-ns
|
|
130
126
|
|
|
131
127
|
From Git:
|
|
132
128
|
|
|
133
|
-
git clone https://github.com/
|
|
134
|
-
cd rapydscript-
|
|
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
|
|
365
|
-
|
|
366
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
768
|
-
``
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1760
|
-
|
|
1761
|
-
``
|
|
1762
|
-
|
|
1763
|
-
|
|
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
|
|
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
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
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.
|
|
2106
|
-
|
|
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
|
|
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://
|
|
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="
|
|
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.
|
|
2226
|
-
|
|
2227
|
-
|
|
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 `
|
|
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
|
|
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
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
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
|
-
|
|
2509
|
-
|
|
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
|
-
|
|
2512
|
-
|
|
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
|