rapydscript-ns 0.8.2 → 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 (50) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/PYTHON_DIFFERENCES_REPORT.md +291 -0
  3. package/PYTHON_FEATURE_COVERAGE.md +96 -5
  4. package/README.md +161 -46
  5. package/TODO.md +2 -283
  6. package/language-service/index.js +4474 -0
  7. package/language-service/language-service.d.ts +40 -0
  8. package/package.json +9 -7
  9. package/src/baselib-builtins.pyj +77 -1
  10. package/src/baselib-containers.pyj +8 -4
  11. package/src/baselib-internal.pyj +30 -1
  12. package/src/baselib-str.pyj +8 -1
  13. package/src/lib/collections.pyj +1 -1
  14. package/src/lib/numpy.pyj +10 -10
  15. package/src/monaco-language-service/analyzer.js +131 -9
  16. package/src/monaco-language-service/builtins.js +12 -2
  17. package/src/monaco-language-service/completions.js +170 -1
  18. package/src/monaco-language-service/diagnostics.js +1 -1
  19. package/src/monaco-language-service/index.js +17 -0
  20. package/src/monaco-language-service/scope.js +3 -0
  21. package/src/output/classes.pyj +20 -3
  22. package/src/output/codegen.pyj +1 -1
  23. package/src/output/functions.pyj +4 -16
  24. package/src/output/loops.pyj +0 -9
  25. package/src/output/modules.pyj +1 -4
  26. package/src/output/operators.pyj +14 -0
  27. package/src/output/stream.pyj +0 -13
  28. package/src/parse.pyj +17 -1
  29. package/test/baselib.pyj +4 -4
  30. package/test/classes.pyj +56 -17
  31. package/test/collections.pyj +5 -5
  32. package/test/python_compat.pyj +326 -0
  33. package/test/python_features.pyj +110 -23
  34. package/test/slice.pyj +105 -0
  35. package/test/str.pyj +25 -0
  36. package/test/unit/fixtures/fibonacci_expected.js +1 -1
  37. package/test/unit/index.js +119 -7
  38. package/test/unit/language-service-builtins.js +70 -0
  39. package/test/unit/language-service-bundle.js +5 -5
  40. package/test/unit/language-service-completions.js +180 -0
  41. package/test/unit/language-service-index.js +350 -0
  42. package/test/unit/language-service-scope.js +255 -0
  43. package/test/unit/language-service.js +35 -0
  44. package/test/unit/run-language-service.js +1 -0
  45. package/test/unit/web-repl.js +134 -0
  46. package/tools/build-language-service.js +2 -2
  47. package/tools/compiler.js +0 -24
  48. package/tools/export.js +3 -37
  49. package/web-repl/rapydscript.js +6 -40
  50. package/web-repl/language-service.js +0 -4187
package/README.md CHANGED
@@ -108,9 +108,6 @@ Here are a few features of RapydScript:
108
108
  - similar to above, ability to use both, Python's and JavaScript's tutorials (as well as widgets)
109
109
  - it's self-hosting, that means the compiler is itself written in RapydScript and compiles into JavaScript
110
110
 
111
- Let's not waste any more time with the introductions, however. The best way to
112
- learn a new language/framework is to dive in.
113
-
114
111
 
115
112
  Installation
116
113
  ------------
@@ -357,17 +354,10 @@ math_ops = {
357
354
  }
358
355
  ```
359
356
 
360
- I'm sure you will agree that the above code is cleaner than declaring 5
361
- temporary variables first and assigning them to the object literal keys after.
362
357
  Note that the example puts the function header (def()) and content on the same
363
- line. I'll refer to it as function inlining. This is meant as a feature of
364
- RapydScript to make the code cleaner in cases like the example above. While you
365
- can use it in longer functions by chaining statements together using `;`, a
366
- good rule of thumb (to keep your code clean) is if your function needs
367
- semi-colons ask yourself whether you should be inlining, and if it needs more
368
- than 2 semi-colons, the answer is probably no (note that you can also use
369
- semi-colons as newline separators within functions that aren't inlined, as in
370
- 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 `;`.
371
361
 
372
362
 
373
363
  Lambda Expressions
@@ -543,7 +533,7 @@ $(element)\
543
533
  .show()
544
534
  ```
545
535
 
546
- 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:
547
537
 
548
538
  ```js
549
539
  a = 0
@@ -646,7 +636,9 @@ into the names optional parameters you specified in the function definition.
646
636
 
647
637
  Inferred Tuple Packing/Unpacking
648
638
  --------------------------------
649
- 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:
650
642
 
651
643
  ```py
652
644
  a, b = b, a
@@ -766,14 +758,19 @@ Containers (lists/sets/dicts)
766
758
  ### Lists
767
759
 
768
760
  Lists in RapydScript are almost identical to lists in Python, but are also
769
- native JavaScript arrays. The only small caveats are that the ``sort()`` and
770
- ``pop()`` methods are renamed to ``pysort()`` and ``pypop()``. This is so that
771
- you can pass RapydScript lists to external JavaScript libraries without any
772
- conflicts. Note that even list literals in RapydScript create python like list
773
- objects, and you can also use the builtin ``list()`` function to create lists
774
- from other iterable objects, just as you would in python. You can create a
775
- RapydScript list from a plain native JavaScript array by using the ``list_wrap()``
776
- 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:
777
774
 
778
775
  ```py
779
776
  a = v'[1, 2]'
@@ -781,6 +778,29 @@ pya = list_wrap(a)
781
778
  # Now pya is a python like list object that satisfies pya === a
782
779
  ```
783
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
+
784
804
  ### Sets
785
805
 
786
806
  Sets in RapydScript are identical to those in python. You can create them using
@@ -1346,6 +1366,20 @@ str.format('{0:02d} {n}', 1, n=2) == '01 2'
1346
1366
  ...
1347
1367
  ```
1348
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
+
1349
1383
  String predicate methods are also available:
1350
1384
 
1351
1385
  ```py
@@ -1697,7 +1731,7 @@ E.a(onclick=def():
1697
1731
 
1698
1732
  Classes
1699
1733
  -------
1700
- 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:
1701
1735
 
1702
1736
  ```js
1703
1737
  class ColorfulTextField:
@@ -1880,6 +1914,65 @@ print(Counter.get_count()) # 2
1880
1914
 
1881
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.
1882
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
+
1883
1976
  ### External Classes
1884
1977
 
1885
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.
@@ -2029,6 +2122,26 @@ next(it) # raises StopIteration
2029
2122
  When the iterator is exhausted and no default is given, ``StopIteration``
2030
2123
  is raised — matching standard Python behaviour.
2031
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.
2144
+
2032
2145
  Generators
2033
2146
  ------------
2034
2147
 
@@ -2039,18 +2152,19 @@ def f():
2039
2152
  for i in range(3):
2040
2153
  yield i
2041
2154
 
2042
- [x for x in f()] == [1, 2, 3]
2155
+ [x for x in f()] == [0, 1, 2]
2043
2156
  ```
2044
2157
 
2045
2158
  There is full support for generators including the Python 3, ```yield from```
2046
- syntax.
2159
+ syntax.
2047
2160
 
2048
2161
  Generators create JavaScript iterator objects. For differences between python
2049
- and JavaScript iterators, see the section on iterators above.
2162
+ and JavaScript iterators, see the section on iterators above.
2050
2163
 
2051
- Currently, generators are down-converted to ES 5 switch statements. In the
2052
- future, when ES 6 support is widespread, they will be converted to native
2053
- 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.
2054
2168
 
2055
2169
  Modules
2056
2170
  -------
@@ -2191,7 +2305,7 @@ finally:
2191
2305
  ```
2192
2306
 
2193
2307
  You can create your own Exception classes by inheriting from `Exception`, which
2194
- 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).
2195
2309
 
2196
2310
  ```py
2197
2311
  class MyError(Exception):
@@ -2288,30 +2402,31 @@ Shadowing is preferred in most cases, since it can't accidentally damage outside
2288
2402
  Available Libraries
2289
2403
  -------------------
2290
2404
 
2291
- 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!
2292
2406
 
2293
- 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.
2294
2408
 
2295
2409
  math # replicates almost all of the functionality from Python's math library
2296
2410
  re # replicates almost all of the functionality from Python's re library
2297
2411
  random # replicates most of the functionality from Python's random library
2412
+ numpy # NumPy-compatible array library (ndarray, ufuncs, numpy.random, numpy.linalg)
2298
2413
  elementmaker # easily construct DOM trees
2299
2414
  aes # Implement AES symmetric encryption
2300
2415
  encodings # Convert to/from UTF-8 bytearrays, base64 strings and native strings
2301
2416
  gettext # Support for internationalization of your RapydScript app
2302
- operator # a subset of python;s operator module
2417
+ operator # a subset of Python's operator module
2303
2418
  functools # reduce, partial, wraps, lru_cache, cache, total_ordering, cmp_to_key
2304
2419
  collections # namedtuple, deque, Counter, OrderedDict, defaultdict
2305
2420
  itertools # count, cycle, repeat, accumulate, chain, compress, dropwhile, filterfalse,
2306
2421
  # groupby, islice, pairwise, starmap, takewhile, zip_longest,
2307
2422
  # product, permutations, combinations, combinations_with_replacement
2308
2423
 
2309
- 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.
2310
2425
 
2311
2426
  Linter
2312
2427
  ---------
2313
2428
 
2314
- 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
2315
2430
  modeled on pyflakes, it catches instances of unused/undefined variables,
2316
2431
  functions, symbols, etc. While this sounds simple, it is surprisingly effective
2317
2432
  in practice. To run the linter:
@@ -2391,13 +2506,12 @@ RapydScript will pick up any classes you declare yourself as well as native
2391
2506
  JavaScript classes. It will not, however, pick up class-like objects created by
2392
2507
  outside frameworks. There are two approaches for dealing with those. One is via
2393
2508
  `@external` decorator, the other is via `new` operator when declaring such
2394
- object. To keep code legible and consistent, I strongly prefer the use of
2395
- `@external` decorator over the `new` operator for several reasons, even if it
2396
- 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:
2397
2511
 
2398
2512
  - `@external` decorator makes classes declared externally obvious to anyone looking at your code
2399
2513
  - class declaration that uses `@external` decorator can be exported into a reusable module
2400
- - 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
2401
2515
 
2402
2516
  #### Embedding the RapydScript compiler in your webpage
2403
2517
 
@@ -2417,7 +2531,7 @@ HTML below for an example.
2417
2531
  <head>
2418
2532
  <meta charset="UTF-8">
2419
2533
  <title>Test embedded RapydScript</title>
2420
- <script charset="UTF-8" src="https://github.com/ficocelliguy/rapydscript-ns/blob/master/web-repl/rapydscript.js"></script>
2534
+ <script charset="UTF-8" src="rapydscript.js"></script>
2421
2535
  <script>
2422
2536
  var compiler = RapydScript.create_embedded_compiler();
2423
2537
  var js = compiler.compile("def hello_world():\n a='RapydScript is cool!'\n print(a)\n alert(a)");
@@ -2511,9 +2625,10 @@ to an experienced Python developer. The most important such gotchas are listed
2511
2625
  below:
2512
2626
 
2513
2627
  - Truthiness in JavaScript is very different from Python. Empty lists and dicts
2514
- are ``False`` in Python but ``True`` in JavaScript. The compiler could work
2515
- around that, but not without a significant performance cost, so it is best to
2516
- 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.
2517
2632
 
2518
2633
  - Operators in JavaScript are very different from Python. ``1 + '1'`` would be
2519
2634
  an error in Python, but results in ``'11'`` in JavaScript. Similarly, ``[1] +
@@ -2574,7 +2689,7 @@ npm run build:ls
2574
2689
  node tools/build-language-service.js --out path/to/language-service.js
2575
2690
  ```
2576
2691
 
2577
- The output is written to `web-repl/language-service.js` by default.
2692
+ The output is written to `language-service/index.js` by default.
2578
2693
 
2579
2694
  ### Basic setup
2580
2695
 
@@ -2769,7 +2884,7 @@ and assembled into the standard `mappings` field. The implementation lives in
2769
2884
 
2770
2885
  ```bash
2771
2886
  node bin/web-repl-export web-repl # rebuilds web-repl/rapydscript.js
2772
- node tools/build-language-service.js # rebuilds web-repl/language-service.js
2887
+ node tools/build-language-service.js # rebuilds language-service/index.js
2773
2888
  ```
2774
2889
 
2775
2890
  ### Running the tests