rapydscript-ns 0.8.0

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 (144) hide show
  1. package/.agignore +1 -0
  2. package/.gitattributes +4 -0
  3. package/.github/workflows/ci.yml +38 -0
  4. package/.github/workflows/web-repl-page-deploy.yml +42 -0
  5. package/=template.pyj +5 -0
  6. package/CHANGELOG.md +456 -0
  7. package/CONTRIBUTORS +13 -0
  8. package/HACKING.md +103 -0
  9. package/LICENSE +24 -0
  10. package/README.md +2512 -0
  11. package/TODO.md +327 -0
  12. package/add-toc-to-readme +2 -0
  13. package/bin/export +75 -0
  14. package/bin/rapydscript +70 -0
  15. package/bin/web-repl-export +102 -0
  16. package/build +3 -0
  17. package/package.json +46 -0
  18. package/publish.py +37 -0
  19. package/release/baselib-plain-pretty.js +4370 -0
  20. package/release/baselib-plain-ugly.js +3 -0
  21. package/release/compiler.js +18394 -0
  22. package/release/signatures.json +31 -0
  23. package/session.vim +4 -0
  24. package/setup.cfg +2 -0
  25. package/src/ast.pyj +1356 -0
  26. package/src/baselib-builtins.pyj +279 -0
  27. package/src/baselib-containers.pyj +723 -0
  28. package/src/baselib-errors.pyj +37 -0
  29. package/src/baselib-internal.pyj +421 -0
  30. package/src/baselib-itertools.pyj +97 -0
  31. package/src/baselib-str.pyj +798 -0
  32. package/src/compiler.pyj +36 -0
  33. package/src/errors.pyj +30 -0
  34. package/src/lib/aes.pyj +646 -0
  35. package/src/lib/collections.pyj +695 -0
  36. package/src/lib/elementmaker.pyj +83 -0
  37. package/src/lib/encodings.pyj +126 -0
  38. package/src/lib/functools.pyj +148 -0
  39. package/src/lib/gettext.pyj +569 -0
  40. package/src/lib/itertools.pyj +580 -0
  41. package/src/lib/math.pyj +193 -0
  42. package/src/lib/numpy.pyj +2101 -0
  43. package/src/lib/operator.pyj +11 -0
  44. package/src/lib/pythonize.pyj +20 -0
  45. package/src/lib/random.pyj +118 -0
  46. package/src/lib/re.pyj +470 -0
  47. package/src/lib/traceback.pyj +63 -0
  48. package/src/lib/uuid.pyj +77 -0
  49. package/src/monaco-language-service/analyzer.js +526 -0
  50. package/src/monaco-language-service/builtins.js +543 -0
  51. package/src/monaco-language-service/completions.js +498 -0
  52. package/src/monaco-language-service/diagnostics.js +643 -0
  53. package/src/monaco-language-service/dts.js +550 -0
  54. package/src/monaco-language-service/hover.js +121 -0
  55. package/src/monaco-language-service/index.js +386 -0
  56. package/src/monaco-language-service/scope.js +162 -0
  57. package/src/monaco-language-service/signature.js +144 -0
  58. package/src/output/__init__.pyj +0 -0
  59. package/src/output/classes.pyj +296 -0
  60. package/src/output/codegen.pyj +492 -0
  61. package/src/output/comments.pyj +45 -0
  62. package/src/output/exceptions.pyj +105 -0
  63. package/src/output/functions.pyj +491 -0
  64. package/src/output/literals.pyj +109 -0
  65. package/src/output/loops.pyj +444 -0
  66. package/src/output/modules.pyj +329 -0
  67. package/src/output/operators.pyj +429 -0
  68. package/src/output/statements.pyj +463 -0
  69. package/src/output/stream.pyj +309 -0
  70. package/src/output/treeshake.pyj +182 -0
  71. package/src/output/utils.pyj +72 -0
  72. package/src/parse.pyj +3106 -0
  73. package/src/string_interpolation.pyj +72 -0
  74. package/src/tokenizer.pyj +702 -0
  75. package/src/unicode_aliases.pyj +576 -0
  76. package/src/utils.pyj +192 -0
  77. package/test/_import_one.pyj +37 -0
  78. package/test/_import_two/__init__.pyj +11 -0
  79. package/test/_import_two/level2/__init__.pyj +0 -0
  80. package/test/_import_two/level2/deep.pyj +4 -0
  81. package/test/_import_two/other.pyj +6 -0
  82. package/test/_import_two/sub.pyj +13 -0
  83. package/test/aes_vectors.pyj +421 -0
  84. package/test/annotations.pyj +80 -0
  85. package/test/baselib.pyj +319 -0
  86. package/test/classes.pyj +452 -0
  87. package/test/collections.pyj +152 -0
  88. package/test/decorators.pyj +77 -0
  89. package/test/dict_spread.pyj +76 -0
  90. package/test/docstrings.pyj +39 -0
  91. package/test/elementmaker_test.pyj +45 -0
  92. package/test/ellipsis.pyj +49 -0
  93. package/test/functions.pyj +151 -0
  94. package/test/generators.pyj +41 -0
  95. package/test/generic.pyj +370 -0
  96. package/test/imports.pyj +72 -0
  97. package/test/internationalization.pyj +73 -0
  98. package/test/lint.pyj +164 -0
  99. package/test/loops.pyj +85 -0
  100. package/test/numpy.pyj +734 -0
  101. package/test/omit_function_metadata.pyj +20 -0
  102. package/test/regexp.pyj +55 -0
  103. package/test/repl.pyj +121 -0
  104. package/test/scoped_flags.pyj +76 -0
  105. package/test/starargs.pyj +506 -0
  106. package/test/starred_assign.pyj +104 -0
  107. package/test/str.pyj +198 -0
  108. package/test/subscript_tuple.pyj +53 -0
  109. package/test/unit/fixtures/fibonacci_expected.js +46 -0
  110. package/test/unit/index.js +2989 -0
  111. package/test/unit/language-service-builtins.js +815 -0
  112. package/test/unit/language-service-completions.js +1067 -0
  113. package/test/unit/language-service-dts.js +543 -0
  114. package/test/unit/language-service-hover.js +455 -0
  115. package/test/unit/language-service-scope.js +833 -0
  116. package/test/unit/language-service-signature.js +458 -0
  117. package/test/unit/language-service.js +705 -0
  118. package/test/unit/run-language-service.js +41 -0
  119. package/test/unit/web-repl.js +484 -0
  120. package/tools/build-language-service.js +190 -0
  121. package/tools/cli.js +547 -0
  122. package/tools/compile.js +219 -0
  123. package/tools/compiler.js +108 -0
  124. package/tools/completer.js +131 -0
  125. package/tools/embedded_compiler.js +251 -0
  126. package/tools/export.js +316 -0
  127. package/tools/gettext.js +185 -0
  128. package/tools/ini.js +65 -0
  129. package/tools/lint.js +705 -0
  130. package/tools/msgfmt.js +187 -0
  131. package/tools/repl.js +223 -0
  132. package/tools/self.js +162 -0
  133. package/tools/test.js +118 -0
  134. package/tools/utils.js +128 -0
  135. package/tools/web_repl.js +95 -0
  136. package/try +41 -0
  137. package/web-repl/env.js +74 -0
  138. package/web-repl/index.html +163 -0
  139. package/web-repl/language-service.js +4084 -0
  140. package/web-repl/main.js +254 -0
  141. package/web-repl/prism.css +139 -0
  142. package/web-repl/prism.js +113 -0
  143. package/web-repl/rapydscript.js +435 -0
  144. package/web-repl/sha1.js +25 -0
package/README.md ADDED
@@ -0,0 +1,2512 @@
1
+ RapydScript
2
+ ===========
3
+
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)
9
+
10
+ This is a fork of the original RapydScript that adds many new (not always
11
+ backwards compatible) features. For more on the forking, [see the bottom of this file](#reasons-for-the-fork)
12
+
13
+ [Try RapydScript-ng live via an in-browser REPL!](https://sw.kovidgoyal.net/rapydscript/repl/)
14
+
15
+ <!-- START doctoc generated TOC please keep comment here to allow auto update -->
16
+ <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
17
+ **Contents**
18
+
19
+ - [What is RapydScript?](#what-is-rapydscript)
20
+ - [Installation](#installation)
21
+ - [Compilation](#compilation)
22
+ - [Getting Started](#getting-started)
23
+ - [Leveraging other APIs](#leveraging-other-apis)
24
+ - [Anonymous Functions](#anonymous-functions)
25
+ - [Lambda Expressions](#lambda-expressions)
26
+ - [Decorators](#decorators)
27
+ - [Self-Executing Functions](#self-executing-functions)
28
+ - [Chaining Blocks](#chaining-blocks)
29
+ - [Function calling with optional arguments](#function-calling-with-optional-arguments)
30
+ - [Positional-only and keyword-only parameters](#positional-only-and-keyword-only-parameters)
31
+ - [Inferred Tuple Packing/Unpacking](#inferred-tuple-packingunpacking)
32
+ - [Operators and keywords](#operators-and-keywords)
33
+ - [Literal JavaScript](#literal-javascript)
34
+ - [Containers (lists/sets/dicts)](#containers-listssetsdicts)
35
+ - [Container comparisons](#container-comparisons)
36
+ - [Loops](#loops)
37
+ - [List/Set/Dict Comprehensions](#listsetdict-comprehensions)
38
+ - [Strings](#strings)
39
+ - [The Existential Operator](#the-existential-operator)
40
+ - [Walrus Operator](#walrus-operator)
41
+ - [Ellipsis Literal](#ellipsis-literal)
42
+ - [Extended Subscript Syntax](#extended-subscript-syntax)
43
+ - [Variable Type Annotations](#variable-type-annotations)
44
+ - [Regular Expressions](#regular-expressions)
45
+ - [Creating DOM trees easily](#creating-dom-trees-easily)
46
+ - [Classes](#classes)
47
+ - [External Classes](#external-classes)
48
+ - [Method Binding](#method-binding)
49
+ - [Iterators](#iterators)
50
+ - [Generators](#generators)
51
+ - [Modules](#modules)
52
+ - [Structural Pattern Matching](#structural-pattern-matching)
53
+ - [Exception Handling](#exception-handling)
54
+ - [Scope Control](#scope-control)
55
+ - [Available Libraries](#available-libraries)
56
+ - [Linter](#linter)
57
+ - [Making RapydScript even more pythonic](#making-rapydscript-even-more-pythonic)
58
+ - [Advanced Usage Topics](#advanced-usage-topics)
59
+ - [Browser Compatibility](#browser-compatibility)
60
+ - [Tabs vs Spaces](#tabs-vs-spaces)
61
+ - [External Libraries and Classes](#external-libraries-and-classes)
62
+ - [Embedding the RapydScript compiler in your webpage](#embedding-the-rapydscript-compiler-in-your-webpage)
63
+ - [Internationalization](#internationalization)
64
+ - [Gotchas](#gotchas)
65
+ - [Monaco Language Service](#monaco-language-service)
66
+ - [Building the bundle](#building-the-bundle)
67
+ - [Basic setup](#basic-setup)
68
+ - [Options](#options)
69
+ - [Runtime API](#runtime-api)
70
+ - [TypeScript declaration files](#typescript-declaration-files)
71
+ - [Virtual modules](#virtual-modules)
72
+ - [Source maps](#source-maps)
73
+ - [Running the tests](#running-the-tests)
74
+ - [Reasons for the fork](#reasons-for-the-fork)
75
+
76
+ <!-- END doctoc generated TOC please keep comment here to allow auto update -->
77
+
78
+
79
+ What is RapydScript?
80
+ --------------------
81
+
82
+ RapydScript (pronounced 'RapidScript') is a pre-compiler for JavaScript,
83
+ similar to CoffeeScript, but with cleaner, more readable syntax. The syntax is
84
+ almost identical to Python, but RapydScript has a focus on performance and
85
+ interoperability with external JavaScript libraries. This means that the
86
+ JavaScript that RapydScript generates is performant and quite close to hand
87
+ written JavaScript.
88
+
89
+ RapydScript allows to write your front-end in Python without the overhead that
90
+ other similar frameworks introduce (the performance is the same as with pure
91
+ JavaScript). To those familiar with CoffeeScript, RapydScript is like
92
+ CoffeeScript, but inspired by Python's readability rather than Ruby's
93
+ cleverness. To those familiar with Pyjamas, RapydScript brings many of the same
94
+ features and support for Python syntax without the same overhead. Don't worry
95
+ if you've never used either of the above-mentioned compilers, if you've ever
96
+ had to write your code in pure JavaScript you'll appreciate RapydScript.
97
+ RapydScript combines the best features of Python as well as JavaScript,
98
+ bringing you features most other Pythonic JavaScript replacements overlook.
99
+ Here are a few features of RapydScript:
100
+
101
+ - classes that work and feel similar to Python
102
+ - an import system for modules/packages that works just like Python's
103
+ - optional function arguments that work similar to Python
104
+ - inheritance system that's both, more powerful than Python and cleaner than JavaScript
105
+ - support for object literals with anonymous functions, like in JavaScript
106
+ - ability to invoke any JavaScript/DOM object/function/method as if it's part of the same framework, without the need for special syntax
107
+ - variable and object scoping that make sense (no need for repetitive 'var' or 'new' keywords)
108
+ - ability to use both, Python's methods/functions and JavaScript's alternatives
109
+ - similar to above, ability to use both, Python's and JavaScript's tutorials (as well as widgets)
110
+ - it's self-hosting, that means the compiler is itself written in RapydScript and compiles into JavaScript
111
+
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
+
116
+ Installation
117
+ ------------
118
+
119
+ [Try RapydScript-ng live via an in-browser REPL!](https://sw.kovidgoyal.net/rapydscript/repl/)
120
+
121
+ 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
+
123
+ From NPM for use as a command line app:
124
+
125
+ npm install rapydscript-ng -g
126
+
127
+ From NPM for use in your own node project:
128
+
129
+ npm install rapydscript-ng
130
+
131
+ From Git:
132
+
133
+ git clone https://github.com/kovidgoyal/rapydscript-ng.git
134
+ cd rapydscript-ng
135
+ sudo npm link .
136
+ npm install # This will automatically install the dependencies for RapydScript
137
+
138
+ If you're using OSX, you can probably use the same commands (let me know if
139
+ that's not the case). If you're using Windows, you should be able to follow
140
+ similar commands after installing node.js, npm and git on your system.
141
+
142
+
143
+ Compilation
144
+ -----------
145
+ Once you have installed RapydScript, compiling your application is as simple as
146
+ running the following command:
147
+
148
+ rapydscript [options] <location of main file>
149
+
150
+ By default this will dump the output to STDOUT, but you can specify the output
151
+ file using `--output` option. The generated file can then be referenced in your
152
+ html page the same way as you would with a typical JavaScript file. If you're
153
+ only using RapydScript for classes and functions, then you're all set. For more
154
+ help, use ```rapydscript -h```.
155
+
156
+ Getting Started
157
+ ---------------
158
+
159
+ RapydScript comes with its own Read-Eval-Print-Loop (REPL). Just run
160
+ ``rapydscript`` without any arguments to get started trying out the code
161
+ snippets below.
162
+
163
+ Like JavaScript, RapydScript can be used to create anything from a quick
164
+ function to a complex web-app. RapydScript can access anything regular
165
+ JavaScript can, in the same manner. Let's say we want to write a function that
166
+ greets us with a "Hello World" pop-up. The following code will do it:
167
+
168
+ ```python
169
+ def greet():
170
+ alert("Hello World!")
171
+ ```
172
+
173
+ Once compiled, the above code will turn into the following JavaScript:
174
+
175
+ ```javascript
176
+ function greet() {
177
+ alert("Hello World!");
178
+ }
179
+ ```
180
+
181
+ Now you can reference this function from other JavaScript or the page itself
182
+ (using "onclick", for example). For our next example, let's say you want a
183
+ function that computes factorial of a number:
184
+
185
+ ```python
186
+ def factorial(n):
187
+ if n == 0:
188
+ return 1
189
+ return n * factorial(n-1)
190
+ ```
191
+
192
+ Now all we need is to tie it into our page so that it's interactive. Let's add an input field to the page body and a cell for displaying the factorial of the number in the input once the input loses focus.
193
+
194
+ ```html
195
+ <input id="user-input" onblur="computeFactorial()"></input>
196
+ <div id="result"></div>
197
+ ```
198
+
199
+ **NOTE:** To complement RapydScript, I have also written RapydML (<https://bitbucket.org/pyjeon/rapydml>), which is a pre-compiler for HTML (just like RapydScript is a pre-compiler for JavaScript).
200
+
201
+ Now let's implement computeFactorial() function in RapydScript:
202
+
203
+ ```python
204
+ def computeFactorial():
205
+ n = document.getElementById("user-input").value
206
+ document.getElementById("result").innerHTML = factorial(n)
207
+ ```
208
+
209
+ Again, notice that we have access to everything JavaScript has access to, including direct DOM manipulation. Once compiled, this function will look like this:
210
+
211
+ ```javascript
212
+ function computeFactorial() {
213
+ var n;
214
+ n = document.getElementById("user-input").value;
215
+ document.getElementById("result").innerHTML = factorial(n);
216
+ }
217
+ ```
218
+
219
+ Notice that RapydScript automatically declares variables in local scope when
220
+ you try to assign to them. This not only makes your code shorter, but saves you
221
+ from making common JavaScript mistake of overwriting a global. For more
222
+ information on controlling variable scope, see `Scope Control` section.
223
+
224
+
225
+ Leveraging other APIs
226
+ ---------------------
227
+
228
+ Aside from Python-like stdlib, RapydScript does not have any of its own APIs.
229
+ Nor does it need to, there are already good options available that we can
230
+ leverage instead. If we wanted, for example, to rewrite the above factorial
231
+ logic using jQuery, we could easily do so:
232
+
233
+ ```python
234
+ def computeFactorial():
235
+ n = $("#user-input").val()
236
+ $("#result").text(factorial(n))
237
+ ```
238
+
239
+ Many of these external APIs, however, take object literals as input. Like with
240
+ JavaScript, you can easily create those with RapydScript, the same way you
241
+ would create a dictionary in Python:
242
+
243
+ ```javascript
244
+ styles = {
245
+ 'background-color': '#ffe',
246
+ 'border-left': '5px solid #ccc',
247
+ 'width': 50,
248
+ }
249
+ ```
250
+
251
+ Now you can pass it to jQuery:
252
+
253
+ ```python
254
+ $('#element').css(styles)
255
+ ```
256
+
257
+ Another feature of RapydScript is ability to have functions as part of your
258
+ object literal. JavaScript APIs often take callback/handler functions as part
259
+ of their input parameters, and RapydScript lets you create such object literal
260
+ without any quirks/hacks:
261
+
262
+ ```js
263
+ params = {
264
+ 'width': 50,
265
+ 'height': 30,
266
+ 'onclick': def(event):
267
+ alert("you clicked me"),
268
+ 'onmouseover': def(event):
269
+ $(this).css('background', 'red')
270
+ ,
271
+ 'onmouseout': def(event):
272
+ # reset the background
273
+ $(this).css('background', '')
274
+ }
275
+ ```
276
+
277
+ Note the comma on a new line following a function declaration, it needs to be
278
+ there to let the compiler know there are more attributes in this object
279
+ literal, yet it can't go on the same line as the function since it would get
280
+ parsed as part of the function block. Like Python, however, RapydScript
281
+ supports new-line shorthand using a `;`, which you could use to place the comma
282
+ on the same line:
283
+
284
+ ```js
285
+ hash = {
286
+ 'foo': def():
287
+ print('foo');,
288
+ 'bar': def():
289
+ print('bar')
290
+ }
291
+ ```
292
+
293
+ It is because of easy integration with JavaScript's native libraries that RapydScript keeps its own libraries to a minimum.
294
+
295
+ Anonymous Functions
296
+ -------------------
297
+
298
+ Like JavaScript, RapydScript allows the use of anonymous functions. In fact,
299
+ you've already seen the use of anonymous functions in previous section when
300
+ creating an object literal ('onmouseover' and 'onmouseout' assignments). Unlike
301
+ Python's `lambda`, anonymous functions created with `def` are not limited to a
302
+ single expression. The following two function declarations are equivalent:
303
+
304
+ ```js
305
+ def factorial(n):
306
+ if n == 0:
307
+ return 1
308
+ return n * factorial(n-1)
309
+
310
+ factorial = def(n):
311
+ if n == 0:
312
+ return 1
313
+ return n * factorial(n-1)
314
+ ```
315
+
316
+ This might not seem like much at first, but if you're familiar with JavaScript,
317
+ you know that this can be extremely useful to the programmer, especially when
318
+ dealing with nested functions, which are a bit syntactically awkward in Python
319
+ (it's not immediately obvious that those can be copied and assigned to other
320
+ objects). To illustrate the usefulness, let's create a method that creates and
321
+ returns an element that changes color while the user keeps the mouse pressed on
322
+ it.
323
+
324
+ ```js
325
+ def makeDivThatTurnsGreen():
326
+ div = $('<div></div>')
327
+ turnGreen = def(event):
328
+ div.css('background', 'green')
329
+ div.mousedown(turnGreen)
330
+ resetColor = def(event):
331
+ div.css('background', '')
332
+ div.mouseup(resetColor)
333
+ return div
334
+ ```
335
+
336
+ At first glance, anonymous functions might not seem that useful. We could have
337
+ easily created nested functions and assigned them instead. By using anonymous
338
+ functions, however, we can quickly identify that these functions will be bound
339
+ to a different object. They belong to the div, not the main function that
340
+ created them, nor the logic that invoked it. The best use case for these is
341
+ creating an element inside another function/object without getting confused
342
+ which object the function belongs to.
343
+
344
+ Additionally, as you already noticed in the previous section, anonymous
345
+ functions can be used to avoid creating excessive temporary variables and make
346
+ your code cleaner:
347
+
348
+ ```js
349
+ math_ops = {
350
+ 'add': def(a, b): return a+b;,
351
+ 'sub': def(a, b): return a-b;,
352
+ 'mul': def(a, b): return a*b;,
353
+ 'div': def(a, b): return a/b;,
354
+ 'roots': def(a, b, c):
355
+ r = Math.sqrt(b*b - 4*a*c)
356
+ d = 2*a
357
+ return (-b + r)/d, (-b - r)/d
358
+ }
359
+ ```
360
+
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
+ 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).
372
+
373
+
374
+ Lambda Expressions
375
+ ------------------
376
+
377
+ RapydScript supports Python's `lambda` keyword for creating single-expression
378
+ anonymous functions inline. The syntax is identical to Python:
379
+
380
+ ```
381
+ lambda arg1, arg2, ...: expression
382
+ ```
383
+
384
+ The body must be a single expression whose value is implicitly returned. For
385
+ multi-statement bodies, use `def` instead.
386
+
387
+ ```py
388
+ # Simple lambda assigned to a variable
389
+ double = lambda x: x * 2
390
+ double(5) # → 10
391
+
392
+ # Lambda with multiple arguments
393
+ add = lambda a, b: a + b
394
+ add(3, 4) # → 7
395
+
396
+ # Lambda with no arguments
397
+ forty_two = lambda: 42
398
+
399
+ # Lambda with a ternary body
400
+ abs_val = lambda x: x if x >= 0 else -x
401
+ abs_val(-5) # → 5
402
+
403
+ # Lambda used inline (e.g. as a sort key)
404
+ nums = [3, 1, 2]
405
+ nums.sort(lambda a, b: a - b)
406
+ # nums is now [1, 2, 3]
407
+
408
+ # Lambda with a default argument value
409
+ greet = lambda name='world': 'hello ' + name
410
+ greet() # → 'hello world'
411
+ greet('alice') # → 'hello alice'
412
+
413
+ # Lambda with *args
414
+ total = lambda *args: sum(args)
415
+ total(1, 2, 3) # → 6
416
+
417
+ # Closure: lambda captures variables from the enclosing scope
418
+ def make_adder(n):
419
+ return lambda x: x + n
420
+ add5 = make_adder(5)
421
+ add5(3) # → 8
422
+
423
+ # Nested lambdas
424
+ mult = lambda x: lambda y: x * y
425
+ mult(3)(4) # → 12
426
+ ```
427
+
428
+ `lambda` is purely syntactic sugar — `lambda x: expr` compiles to the same
429
+ JavaScript as `def(x): return expr`. Use `def` when the body spans multiple
430
+ lines or needs statements.
431
+
432
+ Decorators
433
+ ----------
434
+ Like Python, RapydScript supports decorators.
435
+
436
+ ```py
437
+ def makebold(fn):
438
+ def wrapped():
439
+ return "<b>" + fn() + "</b>"
440
+ return wrapped
441
+
442
+ def makeitalic(fn):
443
+ def wrapped():
444
+ return "<i>" + fn() + "</i>"
445
+ return wrapped
446
+
447
+ @makebold
448
+ @makeitalic
449
+ def hello():
450
+ return "hello world"
451
+
452
+ hello() # returns "<b><i>hello world</i></b>"
453
+ ```
454
+
455
+ Class decorators are also supported with the caveat that the class properties
456
+ must be accessed via the prototype property. For example:
457
+
458
+ ```py
459
+
460
+ def add_x(cls):
461
+ cls.prototype.x = 1
462
+
463
+ @add_x
464
+ class A:
465
+ pass
466
+
467
+ print(A.x) # will print 1
468
+ ```
469
+
470
+
471
+ Self-Executing Functions
472
+ ------------------------
473
+ RapydScript wouldn't be useful if it required work-arounds for things that
474
+ JavaScript handled easily. If you've worked with JavaScript or jQuery before,
475
+ you've probably seen the following syntax:
476
+
477
+ ```js
478
+ (function(args){
479
+ // some logic here
480
+ })(args)
481
+ ```
482
+
483
+ This code calls the function immediately after declaring it instead of
484
+ assigning it to a variable. Python doesn't have any way of doing this. The
485
+ closest work-around is this:
486
+
487
+ ```py
488
+ def tmp(args):
489
+ # some logic here
490
+ tmp.__call__(args)
491
+ ```
492
+
493
+ While it's not horrible, it did litter our namespace with a temporary variable.
494
+ If we have to do this repeatedly, this pattern does get annoying. This is where
495
+ RapydScript decided to be a little unorthodox and implement the JavaScript-like
496
+ solution:
497
+
498
+ ```js
499
+ (def(args):
500
+ # some logic here
501
+ )()
502
+ ```
503
+
504
+ A close cousin of the above is the following code (passing current scope to the function being called):
505
+
506
+ ```js
507
+ function(){
508
+ // some logic here
509
+ }.call(this);
510
+ ```
511
+
512
+ With RapydScript equivalent of:
513
+
514
+ ```js
515
+ def():
516
+ # some logic here
517
+ .call(this)
518
+ ```
519
+
520
+ There is also a third alternative, that will pass the arguments as an array:
521
+
522
+ ```js
523
+ def(a, b):
524
+ # some logic here
525
+ .apply(this, [a, b])
526
+ ```
527
+
528
+
529
+ Chaining Blocks
530
+ ---------------
531
+ As seen in previous section, RapydScript will bind any lines beginning with `.` to the outside of the block with the matching indentation. This logic isn't limited to the `.call()` method, you can use it with `.apply()` or any other method/property the function has assigned to it. This can be used for jQuery as well:
532
+
533
+ ```js
534
+ $(element)
535
+ .css('background-color', 'red')
536
+ .show()
537
+ ```
538
+
539
+ The only limitation is that the indentation has to match, if you prefer to indent your chained calls, you can still do so by using the `\` delimiter:
540
+
541
+ ```js
542
+ $(element)\
543
+ .css('background-color', 'red')\
544
+ .show()
545
+ ```
546
+
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:
548
+
549
+ ```js
550
+ a = 0
551
+ do:
552
+ print(a)
553
+ a += 1
554
+ .while a < 1
555
+ ```
556
+
557
+ Function calling with optional arguments
558
+ -------------------------------------------
559
+
560
+ RapydScript supports the same function calling format as Python. You can have
561
+ named optional arguments, create functions with variable numbers of arguments
562
+ and variable numbers of named arguments. Some examples will illustrate this
563
+ best:
564
+
565
+ ```py
566
+ def f1(a, b=2):
567
+ return [a, b]
568
+
569
+ f1(1, 3) == f1(1, b=3) == [1, 3]
570
+
571
+ def f2(a, *args):
572
+ return [a, args]
573
+
574
+ f2(1, 2, 3) == [1, [2, 3]]
575
+
576
+ def f3(a, b=2, **kwargs):
577
+ return [a, b, kwargs]
578
+
579
+ f3(1, b=3, c=4) == [1, 3, {c:4}]
580
+
581
+ def f4(*args, **kwargs):
582
+ return [args, kwargs]
583
+
584
+ f4(1, 2, 3, a=1, b=2):
585
+ return [[1, 2, 3], {a:1, b:2}]
586
+ ```
587
+
588
+ ### Positional-only and keyword-only parameters
589
+
590
+ RapydScript supports Python's ``/`` and ``*`` parameter separators:
591
+
592
+ - **``/`` (positional-only separator)**: parameters listed before ``/`` can only
593
+ be passed positionally — they cannot be named at the call site.
594
+ - **``*`` (keyword-only separator)**: parameters listed after a bare ``*`` can
595
+ only be passed by name — they cannot be passed positionally.
596
+
597
+ ```py
598
+ def greet(name, /, greeting="Hello", *, punctuation="!"):
599
+ return greeting + ", " + name + punctuation
600
+
601
+ greet("Alice") # Hello, Alice!
602
+ greet("Bob", greeting="Hi") # Hi, Bob!
603
+ greet("Carol", punctuation=".") # Hello, Carol.
604
+ greet("Dave", greeting="Hey", punctuation="?") # Hey, Dave?
605
+
606
+ # name is positional-only: greet(name="Alice") would silently ignore the kwarg
607
+ # punctuation is keyword-only: must be passed as punctuation="."
608
+ ```
609
+
610
+ The two separators can be combined, and each section can have its own default
611
+ values. All combinations supported by Python 3.8+ are accepted.
612
+
613
+ RapydScript is lenient: passing a positional-only parameter by keyword will not
614
+ raise a ``TypeError`` at runtime (the named value is silently ignored), and
615
+ passing a keyword-only parameter positionally will not raise an error either.
616
+ This is consistent with RapydScript's general approach of favouring
617
+ interoperability over strict enforcement.
618
+
619
+ The Monaco language service correctly shows ``/`` and ``*`` separators in
620
+ signature help and hover tooltips.
621
+
622
+ One difference between RapydScript and Python is that RapydScript is not as
623
+ strict as Python when it comes to validating function arguments. This is both
624
+ for performance and to make it easier to interoperate with other JavaScript
625
+ libraries. So if you do not pass enough arguments when calling a function, the
626
+ extra arguments will be set to undefined instead of raising a TypeError, as in
627
+ Python. Similarly, when mixing ``*args`` and optional arguments, RapydScript
628
+ will not complain if an optional argument is specified twice.
629
+
630
+ When creating callbacks to pass to other JavaScript libraries, it is often the
631
+ case that the external library expects a function that receives an *options
632
+ object* as its last argument. There is a convenient decorator in the standard
633
+ library that makes this easy:
634
+
635
+ ```py
636
+ @options_object
637
+ def callback(a, b, opt1=default1, opt2=default2):
638
+ console.log(opt1, opt2)
639
+
640
+ callback(1, 2, {'opt1':'x', 'opt2':'y'}) # will print x, y
641
+ ```
642
+
643
+ Now when you pass callback into the external library and it is called with an
644
+ object containing options, they will be automatically converted by RapydScript
645
+ into the names optional parameters you specified in the function definition.
646
+
647
+
648
+ Inferred Tuple Packing/Unpacking
649
+ --------------------------------
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:
651
+
652
+ ```py
653
+ a, b = b, a
654
+ ```
655
+
656
+ Likewise, if a function returns multiple variables, it's cleaner to say:
657
+
658
+ ```py
659
+ a, b, c = fun()
660
+ ```
661
+
662
+ rather than:
663
+
664
+ ```py
665
+ tmp = fun()
666
+ a = tmp[0]
667
+ b = tmp[1]
668
+ c = tmp[2]
669
+ ```
670
+
671
+ Since JavaScript doesn't have tuples, RapydScript uses arrays for tuple packing/unpacking behind the scenes, but the functionality stays the same. Note that unpacking only occurs when you're assigning to multiple arguments:
672
+
673
+ ```py
674
+ a, b, c = fun() # gets unpacked
675
+ tmp = fun() # no unpacking, tmp will store an array of length 3
676
+ ```
677
+
678
+ Unpacking can also be done in `for` loops (which you can read about in later section):
679
+
680
+ ```py
681
+ for index, value in enumerate(items):
682
+ print(index+': '+value)
683
+ ```
684
+
685
+ Tuple packing is the reverse operation, and is done to the variables being assigned, rather than the ones being assigned to. This can occur during assignment or function return:
686
+
687
+ ```py
688
+ def fun():
689
+ return 1, 2, 3
690
+ ```
691
+
692
+ To summarize packing and unpacking, it's basically just syntax sugar to remove obvious assignment logic that would just litter the code. For example, the swap operation shown in the beginning of this section is equivalent to the following code:
693
+
694
+ ```py
695
+ tmp = [b, a]
696
+ a = tmp[0]
697
+ b = tmp[1]
698
+ ```
699
+
700
+ RapydScript also supports Python's **starred assignment** (`a, *b, c = iterable`), which collects the remaining elements into a list. The starred variable can appear at any position — front, middle, or end:
701
+
702
+ ```py
703
+ first, *rest = [1, 2, 3, 4] # first=1, rest=[2, 3, 4]
704
+ *init, last = [1, 2, 3, 4] # init=[1, 2, 3], last=4
705
+ head, *mid, tail = [1, 2, 3, 4, 5] # head=1, mid=[2, 3, 4], tail=5
706
+ ```
707
+
708
+ Starred assignment works with any iterable, including generators and strings (which are unpacked character by character). The starred variable always receives a list, even if it captures zero elements.
709
+
710
+ Operators and keywords
711
+ ------------------------
712
+
713
+ RapydScript uses the python form for operators and keywords. Below is the
714
+ mapping from RapydScript to JavaScript.
715
+
716
+ Keywords:
717
+
718
+ RapydScript JavaScript
719
+
720
+ None null
721
+ False false
722
+ True true
723
+ ... ρσ_Ellipsis (the Ellipsis singleton)
724
+ undefined undefined
725
+ this this
726
+
727
+ Operators:
728
+
729
+ RapydScript JavaScript
730
+
731
+ and &&
732
+ or ||
733
+ not !
734
+ is ===
735
+ is not !==
736
+ +=1 ++
737
+ -=1 --
738
+ ** Math.pow()
739
+
740
+ 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
+
742
+
743
+ Literal JavaScript
744
+ -----------------------
745
+
746
+ In rare cases RapydScript might not allow you to do what you need to, and you
747
+ need access to pure JavaScript, this is particularly useful for performance
748
+ optimizations in inner loops. When that's the case, you can use a *verbatim
749
+ string literal*. That is simply a normal RapydScript string prefixed with the
750
+ ```v``` character. Code inside a verbatim string literal is not a sandbox, you
751
+ can still interact with it from normal RapydScript:
752
+
753
+ ```py
754
+ v'a = {foo: "bar", baz: 1};'
755
+ print(a.foo) # prints "bar"
756
+
757
+ for v'i = 0; i < arr.length; i++':
758
+ print (arr[i])
759
+ ```
760
+
761
+ Containers (lists/sets/dicts)
762
+ ------------------------------
763
+
764
+ ### Lists
765
+
766
+ 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:
775
+
776
+ ```py
777
+ a = v'[1, 2]'
778
+ pya = list_wrap(a)
779
+ # Now pya is a python like list object that satisfies pya === a
780
+ ```
781
+
782
+ ### Sets
783
+
784
+ Sets in RapydScript are identical to those in python. You can create them using
785
+ set literals or comprehensions and all set operations are supported. You can
786
+ store any object in a set, the only caveat is that RapydScript does not support
787
+ the ``__hash__()`` method, so if you store an arbitrary object as opposed to a
788
+ primitive type, object equality will be via the ``is`` operator.
789
+
790
+ Note that sets are not a subclass of the ES 6 JavaScript Set object, however,
791
+ they do use this object as a backend, when available. You can create a set from
792
+ any enumerable container, like you would in python
793
+
794
+ ```py
795
+ s = set(list or other set or string)
796
+ ```
797
+
798
+ You can also wrap an existing JavaScript Set object efficiently, without
799
+ creating a copy with:
800
+
801
+ ```py
802
+ js_set = Set()
803
+ py_set = set_wrap(js_set)
804
+ ```
805
+
806
+ Note that using non-primitive objects as set members does not behave the
807
+ same way as in Python. For example:
808
+
809
+ ```py
810
+ a = [1, 2]
811
+ s = {a}
812
+ a in s # True
813
+ [1, 2] in s # False
814
+ ```
815
+
816
+ This is because, as noted above, object equality is via the ```is```
817
+ operator, not hashes.
818
+
819
+ ### Dicts
820
+
821
+ dicts are the most different in RapydScript, from Python. This is because
822
+ RapydScript uses the JavaScript Object as a dict, for compatibility with
823
+ external JavaScript libraries and performance. This means there are several
824
+ differences between RapydScript dicts and Python dicts.
825
+
826
+ - You can only use primitive types (strings/numbers) as keys in the dict
827
+ - If you use numbers as keys, they are auto-converted to strings
828
+ - You can access the keys of the dict as attributes of the dict object
829
+ - Trying to access a non-existent key returns ``undefined`` instead of
830
+ raising a KeyError
831
+ - dict objects do not have the same methods as python dict objects:
832
+ ``items(), keys(), values(), get(), pop(), etc.`` You can however use
833
+ RapydScript dict objects in ```for..in``` loops.
834
+
835
+ Fortunately, there is a builtin ```dict``` type that behaves just like Python's
836
+ ```dict``` with all the same methods. The only caveat is that you have to add
837
+ a special line to your RapydScript code to use these dicts, as shown below:
838
+
839
+ ```py
840
+ from __python__ import dict_literals, overload_getitem
841
+ a = {1:1, 2:2}
842
+ a[1] # == 1
843
+ a[3] = 3
844
+ list(a.keys()) == [1, 2, 3]
845
+ a['3'] # raises a KeyError as this is a proper python dict, not a JavaScript object
846
+ ```
847
+
848
+ The special line, called a *scoped flag* tells the compiler that from
849
+ that point on, you want it to treat dict literals and the getitem operator `[]`
850
+ as they are treated in python, not JavaScript.
851
+
852
+ The scoped flags are local to each scope, that means that if you use it in a
853
+ module, it will only affect code in that module, it you use it in a function,
854
+ it will only affect code in that function. In fact, you can even use it to
855
+ surround a few lines of code, like this:
856
+
857
+ ```py
858
+ from __python__ import dict_literals, overload_getitem
859
+ a = {1:1, 2:2}
860
+ isinstance(a, dict) == True
861
+ from __python__ import no_dict_literals, no_overload_getitem
862
+ a = {1:1, 2:2}
863
+ isinstance(a, dict) == False # a is a normal JavaScript object
864
+ ```
865
+
866
+ ### Dict merge literals
867
+
868
+ RapydScript supports Python's `{**d1, **d2}` dict merge (spread) syntax.
869
+ One or more `**expr` items can appear anywhere inside a `{...}` literal,
870
+ interleaved with ordinary `key: value` pairs:
871
+
872
+ ```py
873
+ defaults = {'color': 'blue', 'size': 10}
874
+ overrides = {'size': 20}
875
+
876
+ # Merge two dicts — later items win
877
+ merged = {**defaults, **overrides}
878
+ # merged == {'color': 'blue', 'size': 20}
879
+
880
+ # Mix spread with literal key-value pairs
881
+ result = {**defaults, 'weight': 5}
882
+ # result == {'color': 'blue', 'size': 10, 'weight': 5}
883
+ ```
884
+
885
+ This works for both plain JavaScript-object dicts (the default) and Python
886
+ `dict` objects (enabled via `from __python__ import dict_literals`):
887
+
888
+ ```py
889
+ from __python__ import dict_literals, overload_getitem
890
+ pd1 = {'a': 1}
891
+ pd2 = {'b': 2}
892
+ merged = {**pd1, **pd2} # isinstance(merged, dict) == True
893
+ ```
894
+
895
+ The spread items are translated using `Object.assign` for plain JS objects
896
+ and `dict.update()` for Python dicts.
897
+
898
+
899
+ ### Arithmetic operator overloading
900
+
901
+ RapydScript supports Python-style arithmetic operator overloading via the
902
+ ``overload_operators`` scoped flag:
903
+
904
+ ```py
905
+ from __python__ import overload_operators
906
+
907
+ class Vector:
908
+ def __init__(self, x, y):
909
+ self.x = x
910
+ self.y = y
911
+
912
+ def __add__(self, other):
913
+ return Vector(self.x + other.x, self.y + other.y)
914
+
915
+ def __neg__(self):
916
+ return Vector(-self.x, -self.y)
917
+
918
+ a = Vector(1, 2)
919
+ b = Vector(3, 4)
920
+ c = a + b # calls a.__add__(b) → Vector(4, 6)
921
+ d = -a # calls a.__neg__() → Vector(-1, -2)
922
+ ```
923
+
924
+ The supported dunder methods are:
925
+
926
+ | Expression | Forward method | Reflected method |
927
+ |------------|-----------------|-------------------|
928
+ | `a + b` | `__add__` | `__radd__` |
929
+ | `a - b` | `__sub__` | `__rsub__` |
930
+ | `a * b` | `__mul__` | `__rmul__` |
931
+ | `a / b` | `__truediv__` | `__rtruediv__` |
932
+ | `a // b` | `__floordiv__` | `__rfloordiv__` |
933
+ | `a % b` | `__mod__` | `__rmod__` |
934
+ | `a ** b` | `__pow__` | `__rpow__` |
935
+ | `a & b` | `__and__` | `__rand__` |
936
+ | `a \| b` | `__or__` | `__ror__` |
937
+ | `a ^ b` | `__xor__` | `__rxor__` |
938
+ | `a << b` | `__lshift__` | `__rlshift__` |
939
+ | `a >> b` | `__rshift__` | `__rrshift__` |
940
+ | `-a` | `__neg__` | |
941
+ | `+a` | `__pos__` | |
942
+ | `~a` | `__invert__` | |
943
+
944
+ Augmented assignment (``+=``, ``-=``, etc.) first tries the in-place method
945
+ (``__iadd__``, ``__isub__``, …) and then falls back to the binary method.
946
+
947
+ If neither operand defines the relevant dunder method the operation falls back
948
+ to the native JavaScript operator, so plain numbers, strings, and booleans
949
+ continue to work as expected with no performance penalty when no dunder method
950
+ is defined.
951
+
952
+ Because the dispatch adds one or two property lookups per operation, the flag
953
+ is **opt-in** rather than always-on. Enable it only in the files or scopes
954
+ where you need it.
955
+
956
+ The ``collections.Counter`` class defines ``__add__``, ``__sub__``, ``__or__``,
957
+ and ``__and__``. With ``overload_operators`` you can use the natural operator
958
+ syntax:
959
+
960
+ ```py
961
+ from __python__ import overload_getitem, overload_operators
962
+ from collections import Counter
963
+
964
+ c1 = Counter('aab')
965
+ c2 = Counter('ab')
966
+ c3 = c1 + c2 # {'a': 3, 'b': 2}
967
+ c4 = c1 - c2 # {'a': 1}
968
+ c5 = c1 | c2 # union (max) → {'a': 2, 'b': 1}
969
+ c6 = c1 & c2 # intersection (min) → {'a': 1, 'b': 1}
970
+ ```
971
+
972
+ ### Container comparisons
973
+
974
+ Container equality (the `==` and `!=` operators) work for lists and sets and
975
+ RapydScript dicts (but not arbitrary javascript objects). You can also define
976
+ the ``__eq__(self, other)`` method in your classes to have these operators work
977
+ for your own types.
978
+
979
+ RapydScript does not overload the ordering operators ```(>, <, >=,
980
+ <=)``` as doing so would be a big performance impact (function calls in
981
+ JavaScript are very slow). So using them on containers is useless.
982
+
983
+ Loops
984
+ -----
985
+ RapydScript's loops work like Python, not JavaScript. You can't, for example
986
+ use ```for(i=0;i<max;i++)``` syntax. You can, however, loop through arrays
987
+ using 'for ... in' syntax without worrying about the extra irrelevant
988
+ attributes regular JavaScript returns.
989
+
990
+ ```py
991
+ animals = ['cat', 'dog', 'mouse', 'horse']
992
+ for animal in animals:
993
+ print('I have a '+animal)
994
+ ```
995
+
996
+ If you need to use the index in the loop as well, you can do so by using enumerate():
997
+
998
+ ```py
999
+ for index, animal in enumerate(animals):
1000
+ print("index:"+index, "animal:"+animal)
1001
+ ```
1002
+
1003
+ Like in Python, if you just want the index, you can use range:
1004
+
1005
+ ```py
1006
+ for index in range(len(animals)): # or range(animals.length)
1007
+ print("animal "+index+" is a "+animals[index])
1008
+ ```
1009
+
1010
+ When possible, RapydScript will automatically optimize the loop for you into
1011
+ JavaScript's basic syntax, so you're not missing much by not being able to call
1012
+ it directly.
1013
+
1014
+
1015
+ List/Set/Dict Comprehensions
1016
+ -------------------------------
1017
+
1018
+ RapydScript also supports comprehensions, using Python syntax. Instead of the following, for example:
1019
+
1020
+ ```py
1021
+ myArray = []
1022
+ for index in range(1,20):
1023
+ if index*index % 3 == 0:
1024
+ myArray.append(index*index)
1025
+ ```
1026
+
1027
+ You could write this:
1028
+
1029
+ ```py
1030
+ myArray = [i*i for i in range(1,20) if i*i%3 == 0]
1031
+ ```
1032
+
1033
+ Similarly for set and dict comprehensions:
1034
+
1035
+ ```py
1036
+ myDict = {x:x+1 for x in range(20) if x > 2}
1037
+ mySet = {i*i for i in range(1,20) if i*i%3 == 0}
1038
+ ```
1039
+
1040
+ Nested comprehensions (multiple `for` and `if` clauses) are also supported,
1041
+ using the same syntax as Python:
1042
+
1043
+ ```py
1044
+ # Flatten a 2-D list
1045
+ flat = [x for row in matrix for x in row]
1046
+
1047
+ # Cartesian product of two ranges
1048
+ coords = [[i, j] for i in range(3) for j in range(3)]
1049
+
1050
+ # Filter across nested loops
1051
+ evens = [x for row in [[1,2,3],[4,5,6]] for x in row if x % 2 == 0]
1052
+
1053
+ # Nested set/dict comprehensions work too
1054
+ unique_sums = {x + y for x in range(4) for y in range(4)}
1055
+ ```
1056
+
1057
+ Any number of `for` clauses may be combined, each optionally followed by one or
1058
+ more `if` conditions.
1059
+
1060
+ Builtin iteration functions: any() and all()
1061
+ ---------------------------------------------
1062
+
1063
+ RapydScript supports Python's `any()` and `all()` built-in functions with
1064
+ identical semantics. Both work with arrays, strings, iterators, `range()` objects,
1065
+ and any other iterable.
1066
+
1067
+ `any(iterable)` returns `True` if at least one element of the iterable is
1068
+ truthy, and `False` if all elements are falsy or the iterable is empty:
1069
+
1070
+ ```py
1071
+ any([False, 0, '', 1]) # True
1072
+ any([False, 0, None]) # False
1073
+ any([]) # False
1074
+ any(range(3)) # True (range produces 0, 1, 2 — 1 and 2 are truthy)
1075
+ any(range(0)) # False (empty range)
1076
+ ```
1077
+
1078
+ `all(iterable)` returns `True` only if every element is truthy (or the iterable
1079
+ is empty):
1080
+
1081
+ ```py
1082
+ all([1, 2, 3]) # True
1083
+ all([1, 0, 3]) # False
1084
+ all([]) # True (vacuously true)
1085
+ all(range(1, 4)) # True (1, 2, 3 — all truthy)
1086
+ all(range(0, 3)) # False (range starts at 0, which is falsy)
1087
+ ```
1088
+
1089
+ Both functions short-circuit: `any()` stops as soon as it finds a truthy
1090
+ element, and `all()` stops as soon as it finds a falsy element. This makes
1091
+ them efficient even with large or lazy iterables.
1092
+
1093
+ They work naturally with list comprehensions for expressive one-liners:
1094
+
1095
+ ```py
1096
+ nums = [2, 4, 6, 8]
1097
+ all([x > 0 for x in nums]) # True — all positive
1098
+ any([x > 5 for x in nums]) # True — some greater than 5
1099
+ all([x > 5 for x in nums]) # False — not all greater than 5
1100
+ ```
1101
+
1102
+ Both `any()` and `all()` compile to plain JavaScript function calls and are
1103
+ always available without any import.
1104
+
1105
+ Strings
1106
+ ---------
1107
+
1108
+ For reasons of compatibility with external JavaScript and performance,
1109
+ RapydScript does not make any changes to the native JavaScript string type.
1110
+ However, all the useful Python string methods are available on the builtin
1111
+ ``str`` object. This is analogous to how the functions are available in the
1112
+ ``string`` module in Python 2.x. For example,
1113
+
1114
+ ```py
1115
+ str.strip(' a ') == 'a'
1116
+ str.split('a b') == ['a', 'b']
1117
+ str.format('{0:02d} {n}', 1, n=2) == '01 2'
1118
+ ...
1119
+ ```
1120
+
1121
+ However, if you want to make the python string methods available on string
1122
+ objects, there is a convenience method in the standard library to do so. Use
1123
+ the following code:
1124
+
1125
+ ```py
1126
+ from pythonize import strings
1127
+ strings()
1128
+ ```
1129
+
1130
+ After you call the `strings()` function, all python string methods will be
1131
+ available on string objects, just as in python. The only caveat is that two
1132
+ methods: `split()` and `replace()` are left as the native JavaScript versions,
1133
+ as their behavior is not compatible with that of the python versions. You can
1134
+ control which methods are not copied to the JavaScript String object by passing
1135
+ their names to the `strings()` function, like this:
1136
+
1137
+ ```py
1138
+ strings('split', 'replace', 'find', ...)
1139
+ # or
1140
+ strings(None) # no methods are excluded
1141
+ ```
1142
+
1143
+ One thing to keep in mind is that in JavaScript string are UTF-16, so they
1144
+ behave like strings in narrow builds of Python 2.x. This means that non-BMP
1145
+ unicode characters are represented as surrogate pairs. RapydScript includes
1146
+ some functions to make dealing with non-BMP unicode characters easier:
1147
+
1148
+ - ``str.uchrs(string, [with_positions])`` -- iterate over unicode characters in string, so, for example:
1149
+
1150
+ ```py
1151
+ list(str.uchrs('s🐱a')) == ['s', "🐱", 'a']
1152
+ ```
1153
+
1154
+ You can also get positions of individual characters:
1155
+
1156
+ ```py
1157
+ list(str.uchrs('s🐱a', True)) == [[0, 's'], [1, "🐱"], [3, 'a']]
1158
+ ```
1159
+ Note that any broken surrogate pairs in the underlying string are returned
1160
+ as the unicode replacement character U+FFFD
1161
+
1162
+ - ``str.uslice(string, [start, [stop]])`` -- get a slice based on unicode character positions, for example:
1163
+
1164
+ ```py
1165
+ str.uslice('s🐱a', 2') == 'a' # even though a is at index 3 in the native string object
1166
+ ```
1167
+
1168
+ - ``str.ulen(string)`` -- return the number of unicode characters in the string
1169
+
1170
+ The Existential Operator
1171
+ ---------------------------
1172
+
1173
+ One of the annoying warts of JavaScript is that there are two "null-like"
1174
+ values: `undefined` and `null`. So if you want to test if a variable is not
1175
+ null you often have to write a lengthy expression that looks like
1176
+
1177
+ ```py
1178
+ (var !== undefined and var !== None)
1179
+ ```
1180
+
1181
+ Simply doing `bool(var)` will not work because zero and empty strings are also
1182
+ False.
1183
+
1184
+ Similarly, if you need to access a chain of properties/keys and dont want a
1185
+ `TypeError` to be raised, if one of them is undefined/null then you have
1186
+ to do something like:
1187
+
1188
+ ```py
1189
+ if a and a.b and a.b.c:
1190
+ ans = a.b.c()
1191
+ else:
1192
+ ans = undefined
1193
+ ```
1194
+
1195
+ To ease these irritations, RapydScript borrows the *Existential operator* from
1196
+ CoffeeScript. This can be used to test if a variable is null-like, with a
1197
+ single character, like this:
1198
+
1199
+ ```py
1200
+ yes = True if no? else False
1201
+ # Which, without the ? operator becomes
1202
+ yes = True if no is not undefined and no is not None else False
1203
+ ```
1204
+
1205
+ When it comes to long chains, the `?` operator will return the expected value
1206
+ if all parts of the chain are ok, but cause the entire chaning to result in
1207
+ `undefined` if any of its links are null-like. For example:
1208
+
1209
+ ```py
1210
+ ans = a?.b?[1]?()
1211
+ # Which, without the ? operator becomes
1212
+ ans = undefined
1213
+ if a is not undefined and a is not None and a.b is not undefined and a.b is not None and jstype(a.b[1]) is 'function':
1214
+ ans = a.b[1]()
1215
+ ```
1216
+
1217
+ Finally, you can also use the existential operator as shorthand for the
1218
+ conditional ternary operator, like this:
1219
+
1220
+ ```py
1221
+ a = b ? c
1222
+ # is the same as
1223
+ a = c if (b is undefined or b is None) else b
1224
+ ```
1225
+
1226
+ Walrus Operator
1227
+ ---------------
1228
+
1229
+ RapydScript supports the walrus (assignment expression) operator `:=` from
1230
+ Python 3.8 (PEP 572). It assigns a value and returns it as an expression,
1231
+ allowing you to avoid repeating a computation:
1232
+
1233
+ ```python
1234
+ # assign and test in a single expression
1235
+ if m := re.match(r'\d+', line):
1236
+ print(m.group())
1237
+
1238
+ # drain an iterable in a while loop
1239
+ while chunk := file.read(8192):
1240
+ process(chunk)
1241
+
1242
+ # filter and capture in a comprehension
1243
+ results = [y for x in data if (y := transform(x)) is not None]
1244
+ ```
1245
+
1246
+ The walrus operator binds to the nearest enclosing function or module scope
1247
+ (not the comprehension scope), matching Python semantics.
1248
+
1249
+ Ellipsis Literal
1250
+ -----------------
1251
+
1252
+ RapydScript supports the Python `...` (Ellipsis) literal. It compiles to a
1253
+ frozen singleton object `ρσ_Ellipsis` and is also accessible as `Ellipsis`
1254
+ (the global name), matching Python behaviour.
1255
+
1256
+ Common uses:
1257
+
1258
+ ```py
1259
+ # As a placeholder body (like pass)
1260
+ def stub():
1261
+ ...
1262
+
1263
+ # As a sentinel / marker value
1264
+ def process(data, mask=...):
1265
+ if mask is ...:
1266
+ mask = default_mask()
1267
+
1268
+ # In type annotations
1269
+ from typing import Callable
1270
+ f: Callable[..., int]
1271
+
1272
+ # In numpy-style array indexing
1273
+ arr[..., 0] # Ellipsis selects all leading dimensions
1274
+ ```
1275
+
1276
+ `str(...)` returns `'Ellipsis'`, and `... is ...` is `True` (singleton
1277
+ identity). `...` is truthy.
1278
+
1279
+ Extended Subscript Syntax
1280
+ --------------------------
1281
+
1282
+ RapydScript supports Python's extended subscript syntax, where **commas
1283
+ inside `[]` implicitly form a tuple**. This is the same syntax Python uses for
1284
+ multi-dimensional indexing (e.g. NumPy arrays, custom `__getitem__`
1285
+ implementations).
1286
+
1287
+ ```python
1288
+ # a[i, j] is equivalent to a[(i, j)] in Python
1289
+ # RapydScript compiles it to a[[i, j]] in JavaScript
1290
+
1291
+ # Multi-dimensional dict key
1292
+ d = {}
1293
+ d[0, 1] = "origin"
1294
+ print(d[0, 1]) # → "origin"
1295
+
1296
+ # Three or more indices
1297
+ d[1, 2, 3] = "cube"
1298
+ print(d[1, 2, 3]) # → "cube"
1299
+
1300
+ # Works with variables
1301
+ row, col = 2, 5
1302
+ matrix = {}
1303
+ matrix[row, col] = 42
1304
+ print(matrix[row, col]) # → 42
1305
+ ```
1306
+
1307
+ The tuple is represented as a plain JavaScript array in the generated output:
1308
+
1309
+ ```python
1310
+ d[0, 1] # → d[[0, 1]]
1311
+ d[x, y] # → d[[x, y]]
1312
+ ```
1313
+
1314
+ This works in both plain subscript access and with `overload_getitem` (where
1315
+ the tuple is passed as a RapydScript list to `__getitem__`/`__setitem__`).
1316
+
1317
+ Variable Type Annotations
1318
+ --------------------------
1319
+
1320
+ RapydScript supports Python's variable type annotation syntax (PEP 526). You
1321
+ can annotate a variable with a type hint, with or without an initial value:
1322
+
1323
+ ```python
1324
+ # Annotated assignment: declares and assigns the variable
1325
+ x: int = 42
1326
+ name: str = "Alice"
1327
+ items: list = [1, 2, 3]
1328
+
1329
+ # Annotation only: declares the type without assigning a value
1330
+ count: int
1331
+ ```
1332
+
1333
+ Annotations follow the same syntax as Python: the variable name, a colon, the
1334
+ type expression, and optionally `= value`. The type expression can be any valid
1335
+ RapydScript expression:
1336
+
1337
+ ```python
1338
+ # Complex annotation expressions
1339
+ data: Optional[str] = None
1340
+ mapping: dict = {}
1341
+ result: int = len(items) * 2
1342
+ ```
1343
+
1344
+ Annotations work at module scope, inside functions, and inside class bodies:
1345
+
1346
+ ```python
1347
+ class Point:
1348
+ x: float = 0.0
1349
+ y: float = 0.0
1350
+
1351
+ def move(self, dx: float, dy: float) -> None:
1352
+ self.x += dx
1353
+ self.y += dy
1354
+
1355
+ def distance(p: Point) -> float:
1356
+ dx: float = p.x
1357
+ dy: float = p.y
1358
+ return (dx * dx + dy * dy) ** 0.5
1359
+ ```
1360
+
1361
+ In the compiled JavaScript, the type annotation is erased — only the
1362
+ assignment (if present) is emitted. This matches Python's runtime behaviour
1363
+ where annotations are metadata and do not affect execution. The existing
1364
+ function parameter annotation support (`:` on parameters, `->` for return
1365
+ types) is unaffected and continues to emit `__annotations__` metadata on the
1366
+ function object.
1367
+
1368
+ Regular Expressions
1369
+ ----------------------
1370
+
1371
+ RapydScript includes a ```re``` module that mimics the interface of the Python
1372
+ re module. However, it uses the JavaScript regular expression functionality
1373
+ under the hood, which has several differences from the Python regular
1374
+ expression engine. Most importantly:
1375
+
1376
+ - it does not support lookbehind and group existence assertions
1377
+ - it does not support unicode (on ES 6 runtimes, unicode is supported, but
1378
+ with a different syntax). You can test for the presence of unicode support with
1379
+ ```re.supports_unicode```.
1380
+ - The ``MatchObject``'s ``start()`` and ``end()`` method cannot return correct values
1381
+ for subgroups for some kinds of regular expressions, for example, those
1382
+ with nested captures. This is because the JavaScript regex API does not expose
1383
+ this information, so it has to be guessed via a heuristic.
1384
+
1385
+ You can use the JavaScript regex literal syntax, including verbose regex
1386
+ literals, as shown below. In verbose mode, whitespace is ignored and # comments
1387
+ are allowed (except inside character classes -- verbose mode works in the same
1388
+ way as in python, except you use the JavaScript Regex literal syntax).
1389
+
1390
+ ```py
1391
+ import re
1392
+ re.match(/a(b)/, 'ab') == re.match('a(b)', 'ab')
1393
+
1394
+ re.match(///
1395
+ a # a comment
1396
+ b # Another comment
1397
+ ///, 'ab')
1398
+ ```
1399
+
1400
+ Creating DOM trees easily
1401
+ ---------------------------------
1402
+
1403
+ RapydScript includes a small module in its standard library to create DOM tress
1404
+ efficiently. It leverages the powerful support for python style function
1405
+ calling. Best illustrated with an example:
1406
+
1407
+ ```py
1408
+ from elementmaker import E
1409
+
1410
+ E.div(id="container", class_="xxx",
1411
+ E.div('The Heading', data_heading="1"),
1412
+ E.p('Some text ',
1413
+ E.i('with italics'),
1414
+ E('custom', ' and a csutom tag'),
1415
+ )
1416
+ )
1417
+ ```
1418
+
1419
+ This is equivalent to:
1420
+
1421
+ ```html
1422
+ <div id="container" class="xxx">
1423
+ <div data-heading="1">The Heading</div>
1424
+ <p>Some text <i>with italics</i><custom> and a custom tag</custom></p>
1425
+ </div>
1426
+ ```
1427
+
1428
+ Basically, you create text nodes and children as positional arguments and
1429
+ attributes as keyword arguments. Note that if an attribute name is a reserved
1430
+ keyword in RapydScript, you can postfix it with an underscore. So ```class_```
1431
+ becomes ```class```. Also, underscores are automatically replaced by hyphens,
1432
+ so ```data-*``` attributes can be created. Finally, if you need a non-standard
1433
+ tag, you simply use the ```E()``` function by itself with the first argument
1434
+ being the tag name.
1435
+
1436
+ Another great feature is that you can pass functions as event handlers
1437
+ directly, so for example:
1438
+
1439
+ ```py
1440
+ E.a(onclick=def():
1441
+ pass # do something on the click event
1442
+ )
1443
+ ```
1444
+
1445
+ Classes
1446
+ -------
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.
1448
+
1449
+ ```js
1450
+ class ColorfulTextField:
1451
+ def __init__(self):
1452
+ field = $('<input></input>')
1453
+ changeColor = def(event):
1454
+ field.css('backround', field.val())
1455
+ field.keydown(changeColor)
1456
+ self.widget = field
1457
+ ```
1458
+
1459
+ This class abuses DOM's tolerant behavior, where it will default to the original setting when the passed-in color is invalid (saving us the extra error-checking logic). To append this field to our page we can run the following code:
1460
+
1461
+ ```py
1462
+ textfield = ColorfulTextField()
1463
+ $('body').append(textfield.widget)
1464
+ ```
1465
+
1466
+ If you're used to JavaScript, the code above probably set off a few red flags in your head. In pure JavaScript, you can't create an object without using a 'new' operator. Don't worry, the above code will compile to the following:
1467
+
1468
+ ```js
1469
+ var textfield;
1470
+ textfield = new ColorfulTextField()
1471
+ $('body').append(textfield.widget);
1472
+ ```
1473
+
1474
+ RapydScript will automatically handle appending the 'new' keyword for you, assuming you used 'class' to create the class for your object. This also holds when creating an object inside a list or returning it as well. You could easily do the following, for example:
1475
+
1476
+ ```
1477
+ fields = [ColorfulTextField(), ColorfulTextField(), ColorfulTextField()]
1478
+ ```
1479
+
1480
+ This is very useful for avoiding a common JavaScript error of creating 'undefined' objects by forgetting this keyword. One other point to note here is that regular DOM/JavaScript objects are also covered by this. So if you want to create a DOM image element, you should not use the 'new' keyword either:
1481
+
1482
+ ```py
1483
+ myImage = Image()
1484
+ ```
1485
+
1486
+ But RapydScript's capability doesn't end here. Like Python, RapydScript allows inheritance. Let's say, for example, we want a new field, which works similar to the one above. But in addition to changing color of the field, it allows us to change the color of a different item, with ID of 'target' after we press the 'apply' button, located right next to it. Not a problem, let's implement this guy:
1487
+
1488
+ ```js
1489
+
1490
+ class TextFieldAffectingOthers(ColorfulTextField):
1491
+ def __init__(self):
1492
+ ColorfulTextField.__init__(self)
1493
+ field = self.widget
1494
+ submit = $('<button type="button">apply</button>')
1495
+ applyColor = def(event):
1496
+ $('#target').css('background', field.val())
1497
+ submit.click(applyColor)
1498
+ self.widget = $('<div></div>')\
1499
+ .append(field)\
1500
+ .append(submit)
1501
+ ```
1502
+
1503
+ A couple of things to note here. We can invoke methods from the parent class
1504
+ the same way we would in Python, by using `Parent.method(self, ...)` syntax.
1505
+ This allows us to control when and how (assuming it requires additional
1506
+ arguments) the parent method gets executed. Also note the use of `\` operator
1507
+ to break up a line. This is something Python allows for keeping each line short
1508
+ and legible. Likewise, RapydScript, being indentation-based, allows the same.
1509
+
1510
+ RapydScript also supports Python 3's `super()` built-in for calling parent
1511
+ class methods without naming the parent explicitly:
1512
+
1513
+ ```py
1514
+ class Animal:
1515
+ def __init__(self, name):
1516
+ self.name = name
1517
+
1518
+ def speak(self):
1519
+ return self.name + ' says something'
1520
+
1521
+ class Dog(Animal):
1522
+ def __init__(self, name, breed):
1523
+ super().__init__(name) # calls Animal.__init__
1524
+ self.breed = breed
1525
+
1526
+ def speak(self):
1527
+ return super().speak() + ' (woof!)'
1528
+ ```
1529
+
1530
+ Both the zero-argument form `super()` (Python 3 style) and the two-argument
1531
+ form `super(ClassName, self)` (Python 2 style) are supported. The call
1532
+ `super().method(args)` compiles to `ParentClass.prototype.method.call(this,
1533
+ args)` in JavaScript. The Monaco language service also recognises `super` for
1534
+ completions, hover documentation, and diagnostics.
1535
+
1536
+ Like Python, RapydScript allows multiple inheritance. The only caveat is that
1537
+ the internal semantics of how it works are pretty different from python, since
1538
+ it is built on JavaScript's prototypical inheritance. For the most part you
1539
+ wont notice any differences from python, except, if you have a very complex
1540
+ inheritance hierarchy, especially, one with cycles. In this (rare) case you may
1541
+ find that the method-resolution-order in RapydScript is different from Python.
1542
+
1543
+ Like Python, RapydScript allows static methods. Marking the method static with
1544
+ `@staticmethod` decorator will compile that method such that it's not bound to
1545
+ the object instance, and ensure all calls to this method compile into static
1546
+ method calls:
1547
+
1548
+ ```py
1549
+ class Test:
1550
+ def normalMethod(self):
1551
+ return 1
1552
+
1553
+ @staticmethod
1554
+ def staticMethod(a):
1555
+ return a+1
1556
+ ```
1557
+
1558
+ ### Class Identity Properties
1559
+
1560
+ Every RapydScript class automatically gets the following Python-compatible identity properties:
1561
+
1562
+ | Property | Value | Notes |
1563
+ |----------|-------|-------|
1564
+ | `MyClass.__name__` | `"MyClass"` | The class name as a string |
1565
+ | `MyClass.__qualname__` | `"MyClass"` | Qualified name (same as `__name__` for top-level classes) |
1566
+ | `MyClass.__module__` | module ID string | Module where the class is defined |
1567
+ | `instance.__class__` | `MyClass` | The class of the instance (same as `type(instance)`) |
1568
+
1569
+ ```py
1570
+ class Animal:
1571
+ def __init__(self, name):
1572
+ self.name = name
1573
+
1574
+ print(Animal.__name__) # "Animal"
1575
+ print(Animal.__qualname__) # "Animal"
1576
+
1577
+ a = Animal("Rex")
1578
+ print(a.__class__ is Animal) # True
1579
+ print(type(a).__name__) # "Animal"
1580
+ ```
1581
+
1582
+ This makes patterns like `type(obj).__name__` and `obj.__class__` work identically to Python.
1583
+
1584
+ ### Class Methods
1585
+
1586
+ RapydScript supports the `@classmethod` decorator, which works just like Python's `classmethod`. The first argument (conventionally named `cls`) receives the class itself rather than an instance. Class methods can be called on the class directly or on an instance (which delegates to the class with the correct type):
1587
+
1588
+ ```py
1589
+ class Shape:
1590
+ def __init__(self, kind, size):
1591
+ self.kind = kind
1592
+ self.size = size
1593
+
1594
+ @classmethod
1595
+ def circle(cls, size):
1596
+ return cls('circle', size)
1597
+
1598
+ @classmethod
1599
+ def square(cls, size):
1600
+ return cls('square', size)
1601
+
1602
+ s1 = Shape.circle(10) # s1.kind == 'circle', s1.size == 10
1603
+ s2 = Shape.square(5) # s2.kind == 'square', s2.size == 5
1604
+
1605
+ # Can also be called on an instance (delegates to the class)
1606
+ s3 = s1.circle(7) # equivalent to Shape.circle(7)
1607
+ ```
1608
+
1609
+ Class variables declared in the class body are accessible via `cls.varname` inside a classmethod, just as in Python:
1610
+
1611
+ ```py
1612
+ class Counter:
1613
+ count = 0
1614
+
1615
+ @classmethod
1616
+ def increment(cls):
1617
+ cls.count += 1
1618
+
1619
+ @classmethod
1620
+ def get_count(cls):
1621
+ return cls.count
1622
+
1623
+ Counter.increment()
1624
+ Counter.increment()
1625
+ print(Counter.get_count()) # 2
1626
+ ```
1627
+
1628
+ 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
+
1630
+ ### External Classes
1631
+
1632
+ 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.
1633
+
1634
+ Marking a class as external is done via `external` decorator. You do not need to fill in the contents of the class, a simple `pass` statement will do:
1635
+
1636
+ ```py
1637
+ @external
1638
+ class Alpha:
1639
+ pass
1640
+ ```
1641
+
1642
+ RapydScript will now treat `Alpha` as if it was declared within the same scope, auto-prepending the `new` keyword when needed and using `prototype` to access its methods (see `casperjs` example in next section to see how this can be used in practice). You don't need to pre-declare the methods of this class (unless you decide to for personal reference, the compiler will simply ignore them) unless you want to mark certain methods as static:
1643
+
1644
+ ```py
1645
+ @external
1646
+ class Alpha:
1647
+ @staticmethod
1648
+ def one():
1649
+ pass
1650
+ ```
1651
+
1652
+ `Alpha.one` is now a static method, every other method invoked on `Alpha` will still be treated as a regular class method. While not mandatory, you could pre-declare other methods you plan to use from `Alpha` class as well, to make your code easier to read for other developers, in which case this `external` declaration would also serve as a table of contents for `Alpha`:
1653
+
1654
+ ```py
1655
+ @external
1656
+ class Alpha:
1657
+ def two(): pass
1658
+ def three(): pass
1659
+
1660
+ @staticmethod
1661
+ def one(): pass
1662
+ ```
1663
+
1664
+ As mentioned earlier, this is simply for making your code easier to read. The compiler itself will ignore all method declarations except ones marked with `staticmethod` decorator.
1665
+
1666
+ You could also use `external` decorator to bypass improperly imported RapydScript modules. However, if you actually have control of these modules, the better solution would be to fix those imports.
1667
+
1668
+
1669
+ ### Method Binding
1670
+
1671
+ By default, RapydScript does not bind methods to the classes they're declared under. This behavior is unlike Python, but very much like the rest of JavaScript. For example, consider this code:
1672
+
1673
+ ```py
1674
+ class Boy:
1675
+ def __init__(self, name):
1676
+ self.name = name
1677
+
1678
+ def greet(self):
1679
+ if self:
1680
+ print('My name is' + self.name)
1681
+
1682
+ tod = Boy('Tod')
1683
+ tod.greet() # Hello, my name is Tod
1684
+ getattr(tod, 'greet')() # prints nothing
1685
+ ```
1686
+
1687
+ In some cases, however, you may wish for the functions in the class to be
1688
+ automatically bound when the objects of that class are instantiated. In order
1689
+ to do that, use a *scoped flag*, which is a simple instruction to the compiler
1690
+ telling it to auto-bind methods, as shown below:
1691
+
1692
+ ```py
1693
+
1694
+ class AutoBound:
1695
+ from __python__ import bound_methods
1696
+
1697
+ def __init__(self):
1698
+ self.a = 3
1699
+
1700
+ def val(self):
1701
+ return self.a
1702
+
1703
+ getattr(AutoBound(), 'val')() == 3
1704
+ ```
1705
+
1706
+ If you want all classes in a module to be auto-bound simply put the scoped flag
1707
+ at the top of the module. You can even choose to have only a few methods of the
1708
+ class auto-bound, like this:
1709
+
1710
+ ```py
1711
+ class C:
1712
+
1713
+ def unbound1(self):
1714
+ pass # this method will not be auto-bound
1715
+
1716
+ from __python__ import bound_methods
1717
+ # Methods below this line will be auto-bound
1718
+
1719
+ def bound(self):
1720
+ pass # This method will be auto-bound
1721
+
1722
+ from __python__ import no_bound_methods
1723
+ # Methods below this line will not be auto-bound
1724
+
1725
+ def unbound2(self):
1726
+ pass # this method will be unbound
1727
+ ```
1728
+
1729
+ Scoped flags apply only to the scope they are defined in, so if you define them
1730
+ inside a class declaration, they only apply to that class. If you define it at
1731
+ the module level, it will only apply to all classes in the module that occur
1732
+ below that line, and so on.
1733
+
1734
+ Iterators
1735
+ ----------
1736
+
1737
+ RapydScript supports iterators, just like python, with a few differences to
1738
+ make interoperating with other JavaScript code nicer. You can make an iterator
1739
+ from an array or object by simply calling the builtin ``iter()`` function, just
1740
+ as you would in python. The result of the function is a javascript iterator
1741
+ object, that works both in RapydScript's for..in loops and ES6 JavaScript
1742
+ for..of loops. Indeed they will work with any vanilla JavaScript code that
1743
+ expects an iterable object. You can make your own classes iterable by defining
1744
+ an ``__iter__`` method, just as you would in python. For example:
1745
+
1746
+ ```python
1747
+ class A:
1748
+
1749
+ def __init__(self):
1750
+ self.items = [1, 2, 3]
1751
+
1752
+ def __iter__(self):
1753
+ return iter(self.items)
1754
+
1755
+ for x in A():
1756
+ print (x) # Will print 1, 2, 3
1757
+ ```
1758
+
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.
1764
+
1765
+ Generators
1766
+ ------------
1767
+
1768
+ RapydScript supports generators (the python yield keyword). For example:
1769
+
1770
+ ```py
1771
+ def f():
1772
+ for i in range(3):
1773
+ yield i
1774
+
1775
+ [x for x in f()] == [1, 2, 3]
1776
+ ```
1777
+
1778
+ There is full support for generators including the Python 3, ```yield from```
1779
+ syntax.
1780
+
1781
+ Generators create JavaScript iterator objects. For differences between python
1782
+ and JavaScript iterators, see the section on iterators above.
1783
+
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.
1787
+
1788
+ Modules
1789
+ -------
1790
+
1791
+ RapydScript's module system works almost exactly like Python's. Modules are
1792
+ files ending with the suffix ```.pyj``` and packages are directories containing
1793
+ an ```__init__.pyj``` file. The only caveat is that star imports are not
1794
+ currently supported (this is by design, star imports are easily abused).
1795
+ You can import things from modules, just like you would in python:
1796
+
1797
+ ```py
1798
+ from mypackage.mymodule import something, something_else
1799
+ ```
1800
+
1801
+ When you import modules, the RapydScript compiler automatically generates a
1802
+ single large JavaScript file containing all the imported packages/modules and
1803
+ their dependencies, recursively. This makes it very easy to integrate the
1804
+ output of RapydScript into your website.
1805
+
1806
+ Modules are searched for by default in the rapydscript builtin modules
1807
+ directory and the directory of the rapydscript file that you are
1808
+ compiling. You can add additional directories to the searched locations via
1809
+ the RAPYDSCRIPT_IMPORT_PATH environment variable or the --import-path option
1810
+ to the RapydScript compiler. See the documentation of the option for details.
1811
+
1812
+ Structural Pattern Matching
1813
+ ---------------------------
1814
+
1815
+ RapydScript supports Python's `match/case` statement (PEP 634). The syntax is
1816
+ identical to Python's and transpiles to efficient JavaScript using a labeled
1817
+ `do { … } while (false)` block.
1818
+
1819
+ `match` and `case` are **soft keywords** — they remain valid variable names
1820
+ when not used as a statement head, so existing code is unaffected.
1821
+
1822
+ **Literal patterns**
1823
+
1824
+ ```python
1825
+ match command:
1826
+ case "quit":
1827
+ quit()
1828
+ case "go north":
1829
+ go_north()
1830
+ case _:
1831
+ print("unknown command")
1832
+ ```
1833
+
1834
+ **Capture patterns**
1835
+
1836
+ ```python
1837
+ match value:
1838
+ case x:
1839
+ print(x) # x is bound to value
1840
+ ```
1841
+
1842
+ **OR patterns**
1843
+
1844
+ ```python
1845
+ match status:
1846
+ case 200 | 201 | 202:
1847
+ print("success")
1848
+ case 404:
1849
+ print("not found")
1850
+ ```
1851
+
1852
+ **Guards**
1853
+
1854
+ ```python
1855
+ match point:
1856
+ case [x, y] if x == y:
1857
+ print("on diagonal")
1858
+ case [x, y]:
1859
+ print("off diagonal")
1860
+ ```
1861
+
1862
+ **Sequence patterns**
1863
+
1864
+ ```python
1865
+ match items:
1866
+ case []:
1867
+ print("empty")
1868
+ case [head, *tail]:
1869
+ print(head, tail)
1870
+ case [a, b]:
1871
+ print(a, b)
1872
+ ```
1873
+
1874
+ **Mapping patterns**
1875
+
1876
+ ```python
1877
+ match data:
1878
+ case {"action": "move", "x": x, "y": y}:
1879
+ move(x, y)
1880
+ case {"action": "quit"}:
1881
+ quit()
1882
+ ```
1883
+
1884
+ **Class patterns**
1885
+
1886
+ ```python
1887
+ class Point:
1888
+ __match_args__ = ["x", "y"]
1889
+ def __init__(self, x, y):
1890
+ self.x, self.y = x, y
1891
+
1892
+ match shape:
1893
+ case Point(x=0, y=0):
1894
+ print("origin")
1895
+ case Point(x=px, y=py):
1896
+ print(px, py)
1897
+ ```
1898
+
1899
+ **AS patterns**
1900
+
1901
+ ```python
1902
+ match val:
1903
+ case [x, y] as point:
1904
+ print(point, x, y)
1905
+ ```
1906
+
1907
+ Exception Handling
1908
+ ------------------
1909
+
1910
+ Exception handling in RapydScript works just like it does in python.
1911
+
1912
+ An example:
1913
+
1914
+ ```py
1915
+ try:
1916
+ somefunc()
1917
+ except Exception as e:
1918
+ import traceback
1919
+ traceback.print_exc()
1920
+ else:
1921
+ print('no exception occurred')
1922
+ finally:
1923
+ cleanup()
1924
+ ```
1925
+
1926
+ 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].
1928
+
1929
+ ```py
1930
+ class MyError(Exception):
1931
+ def __init__(self, message):
1932
+ self.name = 'MyError'
1933
+ self.message = message
1934
+
1935
+ raise MyError('This is a custom error!')
1936
+ ```
1937
+
1938
+ You can lump multiple errors in the same except block as well:
1939
+
1940
+ ```py
1941
+ try:
1942
+ print(foo)
1943
+ except ReferenceError, TypeError as e:
1944
+ print(e.name + ':' + e.message)
1945
+ raise # re-raise the exception
1946
+ ```
1947
+
1948
+ Basically, `try/except/finally` in RapydScript works very similar to the way it does in Python 3.
1949
+
1950
+ Scope Control
1951
+ -------------
1952
+
1953
+ Scope refers to the context of a variable. For example, a variable declared inside a function is not seen by the code outside the function. This variable's scope is local to the function. JavaScript controls scope via `var` keyword. Any variable that you start using without declaring with a `var` first will try to reference inner-most variable with the same name, if one doesn't exist, it will create a global variable with that name. For example, the following JavaScript code will not only return `a` incremented by 1, but also overwrite the global variable `a` with `a+1`:
1954
+
1955
+ ```py
1956
+ a = 1;
1957
+ a_plus_1 = function() {
1958
+ return ++a;
1959
+ };
1960
+ ```
1961
+
1962
+ Basically, JavaScript defaults to outer or global scope if you omit `var`. This behavior can introduce some very frustrating bugs in large applications. To avoid this problem, RapydScript's scope preference works in reverse (same as Python's). RapydScript will prefer local-most scope, always creating a local variable if you perform any sort of assignment on it in a function (this is called variable shadowing). Shadowing can create another annoyance, however, of function's variable changes getting discarded. For example, at first, it looks like the following code will set `a` to 2:
1963
+
1964
+ ```py
1965
+ a = 1
1966
+ b = 1
1967
+ increment = def():
1968
+ a += b
1969
+ increment()
1970
+ ```
1971
+
1972
+ When executed, however, increment() function will discard any changes to `a`. This is because, like Python, RapydScript will not allow you to edit variables declared in outer scope. As soon as you use any sort of assignment with `a` in the inner scope, RapydScript will declare it as an internal variable, shadowing `a` in the outer scope. One way around this is to use the `global` keyword, declaring `a` as a global variable. This, however, must be done in every function that edits `a`. It also litters global scope, which it frowned upon because it can accidentally overwrite an unrelated variable with the same name (declared by someone else or another library). RapydScript solves this by introducing `nonlocal` keyword (just like Python 3):
1973
+
1974
+ ```py
1975
+ a = 1
1976
+ b = 1
1977
+ increment = def():
1978
+ nonlocal a
1979
+ a += b
1980
+ increment()
1981
+ ```
1982
+
1983
+ Note that `b` is not affected by shadowing. It's the assignment operator that triggers shadowing, you can read outer-scope variables without having to use `nonlocal`. You can combine multiple non-local arguments by separating them with a comma: `nonlocal a, b, c`. You can also chain `nonlocal` declarations to escape multiple scopes:
1984
+
1985
+ ```py
1986
+ def fun1():
1987
+ a = 5
1988
+ b = fun2():
1989
+ nonlocal a
1990
+ a *= 2
1991
+ c = fun3():
1992
+ nonlocal a
1993
+ a += 1
1994
+ ```
1995
+
1996
+ Shadowing is preferred in most cases, since it can't accidentally damage outside logic, and if you want to edit an external variable, you're usually better off assigning function's return value to it. There are cases, however, when using `nonlocal` makes the code cleaner. For compatibility with Python code, RapydScript also supports the `global` keyword, with the exception that if a variable is present both in the outer scope and the global scope, the variable from the outer scope will be used, rather than the variable from the global scope. This situation is rare in practice, and implementing it in RapydScript would require significant work, so RapydScript `global` remains a little incompatible with Python `global`.
1997
+
1998
+
1999
+ Available Libraries
2000
+ -------------------
2001
+
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.
2003
+
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:
2005
+
2006
+ math # replicates almost all of the functionality from Python's math library
2007
+ re # replicates almost all of the functionality from Python's re library
2008
+ random # replicates most of the functionality from Python's random library
2009
+ elementmaker # easily construct DOM trees
2010
+ aes # Implement AES symmetric encryption
2011
+ encodings # Convert to/from UTF-8 bytearrays, base64 strings and native strings
2012
+ gettext # Support for internationalization of your RapydScript app
2013
+ operator # a subset of python;s operator module
2014
+ functools # reduce, partial, wraps, lru_cache, cache, total_ordering, cmp_to_key
2015
+ collections # namedtuple, deque, Counter, OrderedDict, defaultdict
2016
+ itertools # count, cycle, repeat, accumulate, chain, compress, dropwhile, filterfalse,
2017
+ # groupby, islice, pairwise, starmap, takewhile, zip_longest,
2018
+ # product, permutations, combinations, combinations_with_replacement
2019
+
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.
2021
+
2022
+ Linter
2023
+ ---------
2024
+
2025
+ The RapydScript compiler includes its own, built in linter. The linter is
2026
+ modeled on pyflakes, it catches instances of unused/undefined variables,
2027
+ functions, symbols, etc. While this sounds simple, it is surprisingly effective
2028
+ in practice. To run the linter:
2029
+
2030
+ rapydscript lint file.pyj
2031
+
2032
+ It will catch many errors, for example,
2033
+
2034
+ ```py
2035
+ def f():
2036
+ somevar = 1
2037
+ return someva
2038
+ ```
2039
+
2040
+ The linter will catch the typo above, saving you from having to discover it at
2041
+ runtime. Another example:
2042
+
2043
+ ```py
2044
+ def f(somevar1):
2045
+ somevar2 = somevar1 * 2
2046
+ return somevar1
2047
+ ```
2048
+
2049
+ Here, you probably meant to return ``somevar2`` not ``somevar1``. The linter
2050
+ will detect that somevar2 is defined but not used and warn you about it.
2051
+
2052
+ The linter is highly configurable, you can add to the list of built-in names
2053
+ that the linter will not raise undefined errors for. You can turn off
2054
+ individual checks that you do not find useful. See ``rapydscript lint -h`` for
2055
+ details.
2056
+
2057
+ Making RapydScript even more pythonic
2058
+ ---------------------------------------
2059
+
2060
+ RapydScript has three main goals: To be as fast as possible, to be as close to
2061
+ python as possible, to interoperate with external javascript libraries.
2062
+ Sometimes these goals conflict and RapydScript chooses to be less pythonic in
2063
+ service to the other two goals. Fortunately, there are many optional flags you
2064
+ can use to reverse these compromises. The most important of these are called
2065
+ *scoped flags*.
2066
+
2067
+ The scoped flags are local to each scope, that means that if you use it in a
2068
+ module, it will only affect code in that module, it you use it in a function,
2069
+ it will only affect code in that function. In fact, you can even use it to
2070
+ surround a few lines of code. There are many scoped flags, described else where
2071
+ in this document, see the sections on Method Auto-binding and the section on Dicts
2072
+ in this document.
2073
+
2074
+ Another common complaint is that in RapydScript strings dont have all the
2075
+ string methods that python strings do. Fortunately, there is solution for that
2076
+ as well, described in the section on strings in this document.
2077
+
2078
+
2079
+ Advanced Usage Topics
2080
+ ---------------------
2081
+
2082
+ #### Browser Compatibility
2083
+
2084
+ RapydScript compiles your code such that it will work on browsers that are
2085
+ compatible with the ES 5 JavaScript standard. The compiler has a
2086
+ ``--js-version`` option that can also be used to output ES 6 only code. This
2087
+ code is smaller and faster than the ES 5 version, but is not as widely
2088
+ compatible.
2089
+
2090
+ #### Tabs vs Spaces
2091
+
2092
+ This seems to be a very old debate. Python code conventions suggest 4-space
2093
+ indent. The old version of RapydScript relied on tabs, new one uses spaces
2094
+ since that seems to be more consistent in both Python and JavaScript
2095
+ communities. Use whichever one you prefer, as long as you stay consistent. If
2096
+ you intend to submit your code to RapydScript, it must use spaces to be
2097
+ consistent with the rest of the code in the repository.
2098
+
2099
+ #### External Libraries and Classes
2100
+
2101
+ RapydScript will pick up any classes you declare yourself as well as native
2102
+ JavaScript classes. It will not, however, pick up class-like objects created by
2103
+ outside frameworks. There are two approaches for dealing with those. One is via
2104
+ `@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:
2108
+
2109
+ - `@external` decorator makes classes declared externally obvious to anyone looking at your code
2110
+ - 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
2112
+
2113
+ #### Embedding the RapydScript compiler in your webpage
2114
+
2115
+ You can embed the RapydScript compiler in your webpage so that you can have
2116
+ 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)
2118
+ in your page, and use it to compile arbitrary RapydScript code.
2119
+
2120
+ You create the compiler by calling: `RapydScript.create_embedded_compiler()` and compile
2121
+ code with `compiler.compile(code)`. You can execute the resulting JavaScript
2122
+ using the standard `eval()` function. See the sample
2123
+ HTML below for an example.
2124
+
2125
+ ```html
2126
+ <!DOCTYPE html>
2127
+ <html>
2128
+ <head>
2129
+ <meta charset="UTF-8">
2130
+ <title>Test embedded RapydScript</title>
2131
+ <script charset="UTF-8" src="https://sw.kovidgoyal.net/rapydscript/repl/rapydscript.js"></script>
2132
+ <script>
2133
+ var compiler = RapydScript.create_embedded_compiler();
2134
+ var js = compiler.compile("def hello_world():\n a='RapydScript is cool!'\n print(a)\n alert(a)");
2135
+ window.onload = function() {
2136
+ document.body.textContent = js;
2137
+ eval(js);
2138
+ eval('hello_world()');
2139
+ };
2140
+ </script>
2141
+ </head>
2142
+ <body style="white-space:pre-wrap"></body>
2143
+ </html>
2144
+ ```
2145
+
2146
+ There are a couple of caveats when using the embedded compiler:
2147
+
2148
+ * It only works when run in a modern browser (one that supports ES6) so no
2149
+ Internet Explorer. You can have it work in an ES 5 runtime by passing
2150
+ an option to the compile() method, like this:
2151
+ ```
2152
+ compiler.compile(code, {js_version:5})
2153
+ ```
2154
+ Note that doing this means that you cannot use generators and the
2155
+ yield keyword in your RapydScript code.
2156
+
2157
+ * Importing of modules only works with the standard library modules. There is
2158
+ currently no way to make your own modules importable.
2159
+
2160
+ * To generate the embedded compiler yourself (rapydscript.js) from a source
2161
+ checkout of rapydscript, follow the instructions above for installing from
2162
+ source, then run `bin/web-repl-export /path/to/export/directory`
2163
+
2164
+ Internationalization
2165
+ -------------------------
2166
+
2167
+ RapydScript includes support for internationalization -- i.e. the translation
2168
+ of user interface strings defined in the RapydScript source code. The interface
2169
+ for this is very similar to Python's gettext module. Suppose you have some
2170
+ code that needs internalization support, the first step is to mark all
2171
+ user-viewable strings as translatable:
2172
+
2173
+ ```py
2174
+ from gettext import gettext as _
2175
+ create_button(_('My Button'))
2176
+ create_button(_('Another Button'))
2177
+ ```
2178
+
2179
+ Now we need to extract these string from the source code into a .pot file which
2180
+ can be used to create translations. To do that, run:
2181
+
2182
+ ```
2183
+ rapydscript gettext file.pyj > messages.pot
2184
+ ```
2185
+
2186
+ Now send the `messages.pot` file to your translators. Suppose you get back a
2187
+ `de.po` file from the translators with German translations. You now need to
2188
+ compile this into a format that can be used by RapydScript (RapydScript uses a
2189
+ JSON based format for easy operation over HTTP). Simply run:
2190
+
2191
+ ```
2192
+ rapydscript msgfmt < messages.pot > messages.json
2193
+ ```
2194
+
2195
+ Now, suppose you load up the translation data in your application. Exactly how
2196
+ you do that is upto you. You can load it via Ajax or using a `<script>` tag. To
2197
+ activate the loaded data, simply run:
2198
+
2199
+ ```py
2200
+ from gettext import install
2201
+
2202
+ install(translation_data)
2203
+ ```
2204
+
2205
+ Now everywhere in your program that you have calls to the `_()` function, you
2206
+ will get translated output. So make sure you install the translation data
2207
+ before building the rest of your user-interface.
2208
+
2209
+ Just as in python, you also have a `ngettext()` function for translating
2210
+ strings that depend on a count.
2211
+
2212
+
2213
+ Gotchas
2214
+ ---------
2215
+
2216
+ RapydScript has a couple of mutually conflicting goals: Be as close to python
2217
+ as possible, while also generating clean, performant JavaScript and making
2218
+ interop with external JavaScript libraries easy.
2219
+
2220
+ As a result, there are some things in RapydScript that might come as surprises
2221
+ to an experienced Python developer. The most important such gotchas are listed
2222
+ below:
2223
+
2224
+ - 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.
2228
+
2229
+ - Operators in JavaScript are very different from Python. ``1 + '1'`` would be
2230
+ an error in Python, but results in ``'11'`` in JavaScript. Similarly, ``[1] +
2231
+ [1]`` is a new list in Python, but a string in JavaScript. Keep that in mind
2232
+ as you write code. By default, RapydScript does not implement operator
2233
+ overloading for performance reasons. You can opt in via the
2234
+ ``overload_operators`` scoped flag (see below).
2235
+
2236
+ - There are many more keywords than in Python. Because RapydScript compiles
2237
+ down to JavaScript, the set of keywords is all the keywords of Python + all
2238
+ the keywords of JavaScript. For example, ``default`` and ``switch`` are
2239
+ keywords in RapydScript. See [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Keywords)
2240
+ for a list of JavaScript keywords.
2241
+
2242
+ - Method binding in RS is not automatic. So ``someobj.somemethod()`` will do the
2243
+ right thing, but ``x = someobj.somethod; x()`` will not. You can turn method binding on
2244
+ via a scoped flag. See the section above on method binding for details.
2245
+
2246
+ - RapydScript automatically appends 'new' keyword when using classes generated
2247
+ by it, native JavaScript objects like `Image` and `RegExp` and classes from
2248
+ other libraries marked as external. However, automatic new insertion depends
2249
+ on the compiler being able to detect that a symbol resolves to a class.
2250
+ Because of the dynamic nature of JavaScript this is not possible to do with
2251
+ 100% accuracy. So it is best to get in the habit of using the `new` keyword
2252
+ yourself. Similarly, the compiler will try to convert SomeClass.method() into
2253
+ SomeClass.prototype.method() for you, but again, this is not 100% reliable.
2254
+
2255
+ - The {"a":b} syntax is used to create JavaScript hashes. These do not behave
2256
+ like python dictionaries. To create python like dictionary objects, you
2257
+ should use a scoped flag. See the section on dictionaries above for details.
2258
+
2259
+
2260
+ Monaco Language Service
2261
+ -----------------------
2262
+
2263
+ The `src/monaco-language-service/` directory contains a browser-native language
2264
+ service for [Monaco Editor](https://microsoft.github.io/monaco-editor/) (the
2265
+ editor that powers VS Code). Once registered it provides:
2266
+
2267
+ - **Diagnostics** — syntax errors and lint warnings underlined as you type
2268
+ - **Completions** — Ctrl+Space completions for local variables, functions,
2269
+ classes, module imports, built-in functions, and dot-access on user-defined
2270
+ and `.d.ts`-typed objects
2271
+ - **Signature help** — parameter hints triggered by `(` and `,`
2272
+ - **Hover** — type signature and documentation popup on hover
2273
+ - **Built-in stubs** — signatures and docs for Python and JavaScript built-ins
2274
+ (`len`, `range`, `print`, `sorted`, `any`, `all`, `setTimeout`, …)
2275
+ - **TypeScript declaration support** — load `.d.ts` files to register external
2276
+ globals for completions and hover (e.g. DOM APIs, third-party libraries)
2277
+
2278
+ ### Building the bundle
2279
+
2280
+ The service is distributed as a single self-contained file. Build it with:
2281
+
2282
+ ```bash
2283
+ npm run build:ls
2284
+ # or with a custom output path:
2285
+ node tools/build-language-service.js --out path/to/language-service.js
2286
+ ```
2287
+
2288
+ The output is written to `web-repl/language-service.js` by default.
2289
+
2290
+ ### Basic setup
2291
+
2292
+ Load the compiler bundle and the language-service bundle, then call
2293
+ `registerRapydScript` after Monaco is initialised:
2294
+
2295
+ ```html
2296
+ <script src="rapydscript.js"></script>
2297
+ <script src="language-service.js"></script>
2298
+ <script>
2299
+ require(['vs/editor/editor.main'], function () {
2300
+ var editor = monaco.editor.create(document.getElementById('container'), {
2301
+ language: 'rapydscript',
2302
+ });
2303
+ var service = registerRapydScript(monaco, {
2304
+ compiler: window.RapydScript,
2305
+ });
2306
+ });
2307
+ </script>
2308
+ ```
2309
+
2310
+ `registerRapydScript(monaco, options)` registers all Monaco providers and
2311
+ returns a service handle. Call `service.dispose()` to remove all providers
2312
+ when the editor is torn down.
2313
+
2314
+ ### Options
2315
+
2316
+ | Option | Type | Default | Description |
2317
+ |---|---|---|---|
2318
+ | `compiler` | object | — | **Required.** The `window.RapydScript` compiler bundle. |
2319
+ | `parseDelay` | number | `300` | Debounce delay (ms) before re-checking after an edit. |
2320
+ | `virtualFiles` | `{name: source}` | `{}` | Virtual modules available to `import` statements. |
2321
+ | `dtsFiles` | `[{name, content}]` | `[]` | TypeScript `.d.ts` files loaded at startup. |
2322
+ | `loadDts` | `(name) => Promise<string>` | — | Async callback for lazy-loading `.d.ts` content on demand. |
2323
+ | `extraBuiltins` | `{name: true}` | `{}` | Extra global names that suppress undefined-symbol warnings. |
2324
+
2325
+ ### Runtime API
2326
+
2327
+ ```js
2328
+ // Add or replace virtual modules (triggers a re-check of all open models)
2329
+ service.setVirtualFiles({ mymod: 'def helper(): pass' });
2330
+
2331
+ // Remove a virtual module
2332
+ service.removeVirtualFile('mymod');
2333
+
2334
+ // Register a .d.ts string at runtime (updates completions, hover, and diagnostics)
2335
+ service.addDts('lib.dom', dtsText);
2336
+
2337
+ // Lazily fetch and register a .d.ts file via the loadDts callback
2338
+ service.loadDts('lib.dom').then(function () { console.log('DOM types loaded'); });
2339
+
2340
+ // Suppress undefined-symbol warnings for additional global names
2341
+ service.addGlobals(['myFrameworkGlobal', '$']);
2342
+
2343
+ // Tear down all Monaco providers and event listeners
2344
+ service.dispose();
2345
+ ```
2346
+
2347
+ ### TypeScript declaration files
2348
+
2349
+ Pass `.d.ts` content to register external globals for completions and hover.
2350
+ Supported syntax: `declare var/let/const`, `declare function`,
2351
+ `declare class`, `interface`, `declare namespace`, and `type` aliases.
2352
+
2353
+ ```js
2354
+ var service = registerRapydScript(monaco, {
2355
+ compiler: window.RapydScript,
2356
+ dtsFiles: [
2357
+ {
2358
+ name: 'lib.myapi',
2359
+ content: [
2360
+ 'declare namespace MyAPI {',
2361
+ ' function fetch(url: string): Promise<any>;',
2362
+ ' const version: string;',
2363
+ '}',
2364
+ 'declare var myGlobal: string;',
2365
+ ].join('\n'),
2366
+ },
2367
+ ],
2368
+ });
2369
+ ```
2370
+
2371
+ For large declaration files (e.g. the full DOM lib), use `loadDts` to
2372
+ fetch them on demand rather than inlining them at startup:
2373
+
2374
+ ```js
2375
+ var service = registerRapydScript(monaco, {
2376
+ compiler: window.RapydScript,
2377
+ loadDts: function (name) {
2378
+ return fetch('/dts/' + name + '.d.ts').then(function (r) { return r.text(); });
2379
+ },
2380
+ });
2381
+
2382
+ // Trigger loading whenever needed:
2383
+ service.loadDts('lib.dom');
2384
+ service.loadDts('lib.es2020');
2385
+ ```
2386
+
2387
+ ### Virtual modules
2388
+
2389
+ Virtual modules let the editor resolve `import` statements without a server.
2390
+ The completion provider uses them to suggest exported names in
2391
+ `from X import …` completions.
2392
+
2393
+ ```js
2394
+ var service = registerRapydScript(monaco, {
2395
+ compiler: window.RapydScript,
2396
+ virtualFiles: {
2397
+ utils: 'def format_date(d): pass\ndef clamp(x, lo, hi): pass',
2398
+ models: 'class User:\n def __init__(self, name): self.name = name',
2399
+ },
2400
+ });
2401
+
2402
+ // Live-update a module as the user edits it in another tab:
2403
+ service.setVirtualFiles({ utils: updatedSource });
2404
+ ```
2405
+
2406
+ ### Source maps
2407
+
2408
+ The object returned by `RapydScript.web_repl()` exposes a `compile_mapped`
2409
+ method alongside the standard `compile` method. `compile_mapped` compiles
2410
+ RapydScript to JavaScript and simultaneously produces a
2411
+ [Source Map v3](https://tc39.es/source-map/) JSON string that maps every
2412
+ position in the generated JavaScript back to the corresponding position in the
2413
+ original `.py` source.
2414
+
2415
+ ```js
2416
+ var repl = RapydScript.web_repl();
2417
+ var result = repl.compile_mapped(source, opts);
2418
+ // result.code — compiled JavaScript string
2419
+ // result.sourceMap — Source Map v3 JSON string
2420
+ ```
2421
+
2422
+ | Field | Type | Description |
2423
+ |---|---|---|
2424
+ | `code` | string | The compiled JavaScript. |
2425
+ | `sourceMap` | string | Source Map v3 JSON string. `sources` defaults to `['<input>']`; override before use. `sourcesContent` holds the original `.py` source inline so no server fetch is needed. |
2426
+
2427
+ **Using with the browser debugger**
2428
+
2429
+ Append the source map as an inline data-URL comment. The browser DevTools then
2430
+ map runtime errors and breakpoints back to the original `.py` file:
2431
+
2432
+ ```js
2433
+ var repl = RapydScript.web_repl();
2434
+ var result = repl.compile_mapped(pySource, { export_main: true });
2435
+
2436
+ // Set the display name shown in DevTools
2437
+ var map = JSON.parse(result.sourceMap);
2438
+ map.sources = ['home/user/myscript.py'];
2439
+ map.sourceRoot = '/';
2440
+
2441
+ var b64 = btoa(unescape(encodeURIComponent(JSON.stringify(map))));
2442
+ var code = result.code
2443
+ + '\n//# sourceURL=home/user/myscript.py'
2444
+ + '\n//# sourceMappingURL=data:application/json;base64,' + b64;
2445
+
2446
+ // Load as a module blob so DevTools associates the source map with the file
2447
+ var url = URL.createObjectURL(new Blob([code], { type: 'text/javascript' }));
2448
+ import(url);
2449
+ ```
2450
+
2451
+ When the browser loads that blob the DevTools **Sources** panel shows your
2452
+ original `.py` file with working breakpoints and correct error stack frames.
2453
+
2454
+ **Accepted options** (`opts`)
2455
+
2456
+ `compile_mapped` accepts the same options as `compile`:
2457
+
2458
+ | Option | Type | Default | Description |
2459
+ |---|---|---|---|
2460
+ | `export_main` | bool | `false` | Add `export` to the top-level `main` function. |
2461
+ | `pythonize_strings` | bool | `false` | Add Python-style string methods to string literals. |
2462
+ | `keep_baselib` | bool | `false` | Embed the base library in the output. |
2463
+ | `keep_docstrings` | bool | `false` | Keep docstrings in the output. |
2464
+ | `js_version` | number | `6` | Target ECMAScript version (5 or 6). |
2465
+ | `private_scope` | bool | `false` | Wrap the output in an IIFE. |
2466
+
2467
+ **How it works**
2468
+
2469
+ The compiler's `OutputStream` tracks the current generated line and column as
2470
+ it walks the AST. `compile_mapped` installs a per-compilation hook on
2471
+ `push_node` — called once per AST node just before its code is emitted — that
2472
+ records `(generated_line, generated_col) → (source_line, source_col)` pairs.
2473
+ After code generation those pairs are delta-encoded into Base64-VLQ segments
2474
+ and assembled into the standard `mappings` field. The implementation lives in
2475
+ `tools/embedded_compiler.js` (`vlq_encode`, `build_source_map`,
2476
+ `print_ast_with_sourcemap`, `compile_with_sourcemap`) and is exposed via
2477
+ `tools/web_repl.js` (`compile_mapped`).
2478
+
2479
+ **Rebuilding the bundle after changes**
2480
+
2481
+ ```bash
2482
+ 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
2484
+ ```
2485
+
2486
+ ### Running the tests
2487
+
2488
+ ```bash
2489
+ npm run test:ls
2490
+ ```
2491
+
2492
+ This runs all seven language-service test suites (diagnostics, scope analysis,
2493
+ completions, signature help, hover, DTS registry, and built-in stubs).
2494
+
2495
+
2496
+ Reasons for the fork
2497
+ ----------------------
2498
+
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.
2507
+
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.
2510
+
2511
+ For some discussion surrounding the fork, see
2512
+ [this bug report](https://github.com/kovidgoyal/rapydscript-ng/issues/15)