rapydscript-ns 0.9.1 → 0.9.2

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 (89) hide show
  1. package/.agignore +1 -1
  2. package/.github/workflows/ci.yml +38 -38
  3. package/=template.pyj +5 -5
  4. package/CHANGELOG.md +3 -1
  5. package/HACKING.md +103 -103
  6. package/LICENSE +24 -24
  7. package/README.md +1 -1
  8. package/TODO.md +117 -0
  9. package/add-toc-to-readme +2 -2
  10. package/bin/export +75 -75
  11. package/bin/rapydscript +70 -70
  12. package/bin/web-repl-export +102 -102
  13. package/build +2 -2
  14. package/language-service/index.js +7 -7
  15. package/language-service/language-service.d.ts +1 -1
  16. package/package.json +6 -2
  17. package/publish.py +37 -37
  18. package/release/compiler.js +245 -230
  19. package/release/signatures.json +22 -22
  20. package/session.vim +4 -4
  21. package/setup.cfg +2 -2
  22. package/src/compiler.pyj +36 -36
  23. package/src/errors.pyj +30 -30
  24. package/src/lib/aes.pyj +646 -646
  25. package/src/lib/copy.pyj +120 -120
  26. package/src/lib/elementmaker.pyj +83 -83
  27. package/src/lib/encodings.pyj +126 -126
  28. package/src/lib/gettext.pyj +569 -569
  29. package/src/lib/itertools.pyj +580 -580
  30. package/src/lib/math.pyj +193 -193
  31. package/src/lib/operator.pyj +11 -11
  32. package/src/lib/pythonize.pyj +20 -20
  33. package/src/lib/random.pyj +118 -118
  34. package/src/lib/react.pyj +74 -74
  35. package/src/lib/traceback.pyj +63 -63
  36. package/src/lib/uuid.pyj +77 -77
  37. package/src/monaco-language-service/diagnostics.js +2 -2
  38. package/src/monaco-language-service/dts.js +550 -550
  39. package/src/monaco-language-service/index.js +2 -2
  40. package/src/output/comments.pyj +45 -45
  41. package/src/output/exceptions.pyj +201 -201
  42. package/src/output/jsx.pyj +164 -164
  43. package/src/output/loops.pyj +9 -0
  44. package/src/output/treeshake.pyj +182 -182
  45. package/src/output/utils.pyj +72 -72
  46. package/src/string_interpolation.pyj +72 -72
  47. package/src/unicode_aliases.pyj +576 -576
  48. package/src/utils.pyj +192 -192
  49. package/test/_import_one.pyj +37 -37
  50. package/test/_import_two/__init__.pyj +11 -11
  51. package/test/_import_two/level2/deep.pyj +4 -4
  52. package/test/_import_two/other.pyj +6 -6
  53. package/test/_import_two/sub.pyj +13 -13
  54. package/test/aes_vectors.pyj +421 -421
  55. package/test/annotations.pyj +80 -80
  56. package/test/decorators.pyj +77 -77
  57. package/test/docstrings.pyj +39 -39
  58. package/test/elementmaker_test.pyj +45 -45
  59. package/test/functions.pyj +151 -151
  60. package/test/generators.pyj +41 -41
  61. package/test/generic.pyj +370 -370
  62. package/test/imports.pyj +72 -72
  63. package/test/internationalization.pyj +73 -73
  64. package/test/lint.pyj +164 -164
  65. package/test/loops.pyj +85 -85
  66. package/test/numpy.pyj +734 -734
  67. package/test/omit_function_metadata.pyj +20 -20
  68. package/test/repl.pyj +121 -121
  69. package/test/scoped_flags.pyj +76 -76
  70. package/test/unit/index.js +66 -0
  71. package/test/unit/language-service-dts.js +543 -543
  72. package/test/unit/language-service-hover.js +455 -455
  73. package/test/unit/language-service.js +1 -1
  74. package/tools/compiler.d.ts +367 -0
  75. package/tools/completer.js +131 -131
  76. package/tools/gettext.js +185 -185
  77. package/tools/ini.js +65 -65
  78. package/tools/msgfmt.js +187 -187
  79. package/tools/repl.js +223 -223
  80. package/tools/test.js +118 -118
  81. package/tools/utils.js +128 -128
  82. package/tools/web_repl.js +95 -95
  83. package/try +41 -41
  84. package/web-repl/env.js +196 -196
  85. package/web-repl/index.html +163 -163
  86. package/web-repl/prism.css +139 -139
  87. package/web-repl/prism.js +113 -113
  88. package/web-repl/rapydscript.js +224 -224
  89. package/web-repl/sha1.js +25 -25
@@ -1,182 +1,182 @@
1
- # vim:fileencoding=utf-8
2
- # License: BSD
3
- from __python__ import hash_literals
4
-
5
- from ast import (
6
- AST_Function, AST_Class, AST_SimpleStatement, AST_Assign,
7
- AST_SymbolRef, AST_Dot, AST_Sub, AST_Imports, TreeWalker, is_node_type
8
- )
9
- from utils import has_prop
10
-
11
-
12
- def get_top_level_name(stmt):
13
- if is_node_type(stmt, AST_Function) or is_node_type(stmt, AST_Class):
14
- if stmt.name:
15
- return stmt.name.name
16
- return None
17
- if is_node_type(stmt, AST_SimpleStatement):
18
- body = stmt.body
19
- if is_node_type(body, AST_Assign):
20
- lhs = body.left
21
- if is_node_type(lhs, AST_SymbolRef):
22
- return lhs.name
23
- return None
24
-
25
-
26
- def collect_refs_in_node(stmt, top_level_set, refs):
27
- def visit_fn(node, descend):
28
- if is_node_type(node, AST_SymbolRef):
29
- if has_prop(top_level_set, node.name):
30
- refs[node.name] = True
31
- stmt.walk(TreeWalker(visit_fn))
32
-
33
-
34
- def compute_transitive_closure(body, direct_names, nonlocalvars):
35
- nonlocal_set = {}
36
- if nonlocalvars:
37
- for nv in nonlocalvars:
38
- nonlocal_set[nv] = True
39
-
40
- name_map = {}
41
- unnamed_stmts = []
42
- for stmt in body:
43
- name = get_top_level_name(stmt)
44
- if name is not None:
45
- name_map[name] = stmt
46
- else:
47
- unnamed_stmts.push(stmt)
48
-
49
- top_level_set = {}
50
- for name in Object.keys(name_map):
51
- top_level_set[name] = True
52
-
53
- needed = {}
54
- queue = []
55
-
56
- # Always include top-level assignments to nonlocal vars — they affect the
57
- # global JavaScript scope and must never be filtered out.
58
- for name in Object.keys(name_map):
59
- if has_prop(nonlocal_set, name):
60
- needed[name] = True
61
- queue.push(name)
62
-
63
- # Add directly imported names
64
- for name in Object.keys(direct_names):
65
- if not has_prop(needed, name):
66
- needed[name] = True
67
- queue.push(name)
68
-
69
- # Unnamed statements (imports, if-blocks, bare expressions) are always
70
- # included in output, so their references to named top-level items must
71
- # also be included transitively.
72
- always_refs = {}
73
- for stmt in unnamed_stmts:
74
- collect_refs_in_node(stmt, top_level_set, always_refs)
75
- for ref_name in Object.keys(always_refs):
76
- if not has_prop(needed, ref_name):
77
- needed[ref_name] = True
78
- queue.push(ref_name)
79
-
80
- while queue.length > 0:
81
- current = queue.shift()
82
- if not has_prop(name_map, current):
83
- continue
84
- refs = {}
85
- collect_refs_in_node(name_map[current], top_level_set, refs)
86
- for ref_name in Object.keys(refs):
87
- if not has_prop(needed, ref_name):
88
- needed[ref_name] = True
89
- queue.push(ref_name)
90
-
91
- return needed
92
-
93
-
94
- def check_module_attr_access(main_body, info, alias_set):
95
- def visit_fn(node, descend):
96
- if is_node_type(node, AST_Dot):
97
- expr = node.expression
98
- if is_node_type(expr, AST_SymbolRef) and has_prop(alias_set, expr.name):
99
- info.direct_names[node.property] = True
100
- return True
101
- if is_node_type(node, AST_Sub):
102
- expr = node.expression
103
- if is_node_type(expr, AST_SymbolRef) and has_prop(alias_set, expr.name):
104
- info.can_tree_shake = False
105
- for stmt in main_body:
106
- stmt.walk(TreeWalker(visit_fn))
107
-
108
-
109
- def analyze_imports(main_body):
110
- result = {}
111
-
112
- # First pass: walk the entire AST recursively to collect all from-imports
113
- # (including those nested inside functions or other scopes).
114
- def visit_from_imports(node, descend):
115
- if is_node_type(node, AST_Imports):
116
- for imp in node.imports:
117
- if imp.argnames:
118
- key = imp.key
119
- if not has_prop(result, key):
120
- result[key] = {'direct_names': {}, 'can_tree_shake': True}
121
- for argname in imp.argnames:
122
- result[key].direct_names[argname.name] = True
123
- for stmt in main_body:
124
- stmt.walk(TreeWalker(visit_from_imports))
125
-
126
- # Second pass: handle top-level plain imports (import X as Y, import X).
127
- # Attribute tracking via check_module_attr_access applies to main_body scope.
128
- for stmt in main_body:
129
- if not is_node_type(stmt, AST_Imports):
130
- continue
131
- for imp in stmt.imports:
132
- if imp.argnames:
133
- continue # already handled in first pass
134
- key = imp.key
135
- if not has_prop(result, key):
136
- result[key] = {'direct_names': {}, 'can_tree_shake': True}
137
- info = result[key]
138
- if imp.alias:
139
- alias_set = {}
140
- alias_set[imp.alias.name] = True
141
- check_module_attr_access(main_body, info, alias_set)
142
- else:
143
- parts = key.split('.')
144
- if parts.length > 1:
145
- # import X.Y with no alias: user accesses via X.Y.attr which
146
- # requires two-level dot traversal — disable tree-shaking safely
147
- info.can_tree_shake = False
148
- else:
149
- alias_set = {}
150
- alias_set[parts[0]] = True
151
- check_module_attr_access(main_body, info, alias_set)
152
-
153
- return result
154
-
155
-
156
- def tree_shake(ast, context):
157
- import_infos = analyze_imports(ast.body)
158
- for mod_key in Object.keys(import_infos):
159
- info = import_infos[mod_key]
160
- if not info.can_tree_shake:
161
- continue
162
- if not has_prop(ast.imports, mod_key):
163
- continue
164
- mod = ast.imports[mod_key]
165
- # If body is missing (cached module), re-parse to get it
166
- if not mod.body and mod.src_code:
167
- parsed = context.parse(mod.src_code, {
168
- 'filename': mod.filename,
169
- 'module_id': mod_key,
170
- 'libdir': context.libdir,
171
- 'import_dirs': context.import_dirs or [],
172
- 'discard_asserts': context.discard_asserts,
173
- 'for_linting': True,
174
- })
175
- mod.body = parsed.body
176
- mod.localvars = parsed.localvars
177
- if not mod.nonlocalvars:
178
- mod.nonlocalvars = parsed.nonlocalvars
179
- if not mod.body:
180
- continue
181
- needed = compute_transitive_closure(mod.body, info.direct_names, mod.nonlocalvars)
182
- mod.needed_names = needed
1
+ # vim:fileencoding=utf-8
2
+ # License: BSD
3
+ from __python__ import hash_literals
4
+
5
+ from ast import (
6
+ AST_Function, AST_Class, AST_SimpleStatement, AST_Assign,
7
+ AST_SymbolRef, AST_Dot, AST_Sub, AST_Imports, TreeWalker, is_node_type
8
+ )
9
+ from utils import has_prop
10
+
11
+
12
+ def get_top_level_name(stmt):
13
+ if is_node_type(stmt, AST_Function) or is_node_type(stmt, AST_Class):
14
+ if stmt.name:
15
+ return stmt.name.name
16
+ return None
17
+ if is_node_type(stmt, AST_SimpleStatement):
18
+ body = stmt.body
19
+ if is_node_type(body, AST_Assign):
20
+ lhs = body.left
21
+ if is_node_type(lhs, AST_SymbolRef):
22
+ return lhs.name
23
+ return None
24
+
25
+
26
+ def collect_refs_in_node(stmt, top_level_set, refs):
27
+ def visit_fn(node, descend):
28
+ if is_node_type(node, AST_SymbolRef):
29
+ if has_prop(top_level_set, node.name):
30
+ refs[node.name] = True
31
+ stmt.walk(TreeWalker(visit_fn))
32
+
33
+
34
+ def compute_transitive_closure(body, direct_names, nonlocalvars):
35
+ nonlocal_set = {}
36
+ if nonlocalvars:
37
+ for nv in nonlocalvars:
38
+ nonlocal_set[nv] = True
39
+
40
+ name_map = {}
41
+ unnamed_stmts = []
42
+ for stmt in body:
43
+ name = get_top_level_name(stmt)
44
+ if name is not None:
45
+ name_map[name] = stmt
46
+ else:
47
+ unnamed_stmts.push(stmt)
48
+
49
+ top_level_set = {}
50
+ for name in Object.keys(name_map):
51
+ top_level_set[name] = True
52
+
53
+ needed = {}
54
+ queue = []
55
+
56
+ # Always include top-level assignments to nonlocal vars — they affect the
57
+ # global JavaScript scope and must never be filtered out.
58
+ for name in Object.keys(name_map):
59
+ if has_prop(nonlocal_set, name):
60
+ needed[name] = True
61
+ queue.push(name)
62
+
63
+ # Add directly imported names
64
+ for name in Object.keys(direct_names):
65
+ if not has_prop(needed, name):
66
+ needed[name] = True
67
+ queue.push(name)
68
+
69
+ # Unnamed statements (imports, if-blocks, bare expressions) are always
70
+ # included in output, so their references to named top-level items must
71
+ # also be included transitively.
72
+ always_refs = {}
73
+ for stmt in unnamed_stmts:
74
+ collect_refs_in_node(stmt, top_level_set, always_refs)
75
+ for ref_name in Object.keys(always_refs):
76
+ if not has_prop(needed, ref_name):
77
+ needed[ref_name] = True
78
+ queue.push(ref_name)
79
+
80
+ while queue.length > 0:
81
+ current = queue.shift()
82
+ if not has_prop(name_map, current):
83
+ continue
84
+ refs = {}
85
+ collect_refs_in_node(name_map[current], top_level_set, refs)
86
+ for ref_name in Object.keys(refs):
87
+ if not has_prop(needed, ref_name):
88
+ needed[ref_name] = True
89
+ queue.push(ref_name)
90
+
91
+ return needed
92
+
93
+
94
+ def check_module_attr_access(main_body, info, alias_set):
95
+ def visit_fn(node, descend):
96
+ if is_node_type(node, AST_Dot):
97
+ expr = node.expression
98
+ if is_node_type(expr, AST_SymbolRef) and has_prop(alias_set, expr.name):
99
+ info.direct_names[node.property] = True
100
+ return True
101
+ if is_node_type(node, AST_Sub):
102
+ expr = node.expression
103
+ if is_node_type(expr, AST_SymbolRef) and has_prop(alias_set, expr.name):
104
+ info.can_tree_shake = False
105
+ for stmt in main_body:
106
+ stmt.walk(TreeWalker(visit_fn))
107
+
108
+
109
+ def analyze_imports(main_body):
110
+ result = {}
111
+
112
+ # First pass: walk the entire AST recursively to collect all from-imports
113
+ # (including those nested inside functions or other scopes).
114
+ def visit_from_imports(node, descend):
115
+ if is_node_type(node, AST_Imports):
116
+ for imp in node.imports:
117
+ if imp.argnames:
118
+ key = imp.key
119
+ if not has_prop(result, key):
120
+ result[key] = {'direct_names': {}, 'can_tree_shake': True}
121
+ for argname in imp.argnames:
122
+ result[key].direct_names[argname.name] = True
123
+ for stmt in main_body:
124
+ stmt.walk(TreeWalker(visit_from_imports))
125
+
126
+ # Second pass: handle top-level plain imports (import X as Y, import X).
127
+ # Attribute tracking via check_module_attr_access applies to main_body scope.
128
+ for stmt in main_body:
129
+ if not is_node_type(stmt, AST_Imports):
130
+ continue
131
+ for imp in stmt.imports:
132
+ if imp.argnames:
133
+ continue # already handled in first pass
134
+ key = imp.key
135
+ if not has_prop(result, key):
136
+ result[key] = {'direct_names': {}, 'can_tree_shake': True}
137
+ info = result[key]
138
+ if imp.alias:
139
+ alias_set = {}
140
+ alias_set[imp.alias.name] = True
141
+ check_module_attr_access(main_body, info, alias_set)
142
+ else:
143
+ parts = key.split('.')
144
+ if parts.length > 1:
145
+ # import X.Y with no alias: user accesses via X.Y.attr which
146
+ # requires two-level dot traversal — disable tree-shaking safely
147
+ info.can_tree_shake = False
148
+ else:
149
+ alias_set = {}
150
+ alias_set[parts[0]] = True
151
+ check_module_attr_access(main_body, info, alias_set)
152
+
153
+ return result
154
+
155
+
156
+ def tree_shake(ast, context):
157
+ import_infos = analyze_imports(ast.body)
158
+ for mod_key in Object.keys(import_infos):
159
+ info = import_infos[mod_key]
160
+ if not info.can_tree_shake:
161
+ continue
162
+ if not has_prop(ast.imports, mod_key):
163
+ continue
164
+ mod = ast.imports[mod_key]
165
+ # If body is missing (cached module), re-parse to get it
166
+ if not mod.body and mod.src_code:
167
+ parsed = context.parse(mod.src_code, {
168
+ 'filename': mod.filename,
169
+ 'module_id': mod_key,
170
+ 'libdir': context.libdir,
171
+ 'import_dirs': context.import_dirs or [],
172
+ 'discard_asserts': context.discard_asserts,
173
+ 'for_linting': True,
174
+ })
175
+ mod.body = parsed.body
176
+ mod.localvars = parsed.localvars
177
+ if not mod.nonlocalvars:
178
+ mod.nonlocalvars = parsed.nonlocalvars
179
+ if not mod.body:
180
+ continue
181
+ needed = compute_transitive_closure(mod.body, info.direct_names, mod.nonlocalvars)
182
+ mod.needed_names = needed
@@ -1,72 +1,72 @@
1
- # vim:fileencoding=utf-8
2
- # License: BSD Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
3
- from __python__ import hash_literals
4
-
5
- from ast import AST_BlockStatement, is_node_type
6
-
7
- def best_of(a):
8
- best = a[0]
9
- len_ = best.length
10
- for i in range(1, a.length):
11
- if a[i].length < len_:
12
- best = a[i]
13
- len_ = best.length
14
- return best
15
-
16
- def make_num(num):
17
- str_ = num.toString(10)
18
- a = [ str_.replace(/^0\./, ".").replace("e+", "e") ]
19
- m = None
20
-
21
- if Math.floor(num) is num:
22
- if num >= 0:
23
- a.push("0x" + num.toString(16).toLowerCase(), # probably pointless
24
- "0" + num.toString(8))
25
- else:
26
- a.push("-0x" + (-num).toString(16).toLowerCase(), # probably pointless
27
- "-0" + (-num).toString(8))
28
-
29
- if m = /^(.*?)(0+)$/.exec(num):
30
- a.push(m[1] + "e" + m[2].length)
31
-
32
- elif m = /^0?\.(0+)(.*)$/.exec(num):
33
- a.push(m[2] + "e-" + (m[1].length + m[2].length), str_.substr(str_.indexOf(".")))
34
-
35
- return best_of(a)
36
-
37
- def make_block(stmt, output):
38
- if is_node_type(stmt, AST_BlockStatement):
39
- stmt.print(output)
40
- return
41
-
42
- output.with_block(def():
43
- output.indent()
44
- stmt.print(output)
45
- output.newline()
46
- )
47
-
48
- def create_doctring(docstrings):
49
- ans = v'[]'
50
- for ds in docstrings:
51
- ds = str.rstrip(ds.value)
52
- lines = v'[]'
53
- min_leading_whitespace = ''
54
- for line in ds.split(/$/gm):
55
- r = /^\s+/.exec(line)
56
- leading_whitespace = ''
57
- if r:
58
- leading_whitespace = r[0].replace(/[\n\r]/g, '') if r else ''
59
- line = line[r[0].length:]
60
- if not str.strip(line):
61
- lines.push(v'["", ""]')
62
- else:
63
- leading_whitespace = leading_whitespace.replace(/\t/g, ' ')
64
- if leading_whitespace and (not min_leading_whitespace or leading_whitespace.length < min_leading_whitespace.length):
65
- min_leading_whitespace = leading_whitespace
66
- lines.push(v'[leading_whitespace, line]')
67
- for lw, l in lines:
68
- if min_leading_whitespace:
69
- lw = lw[min_leading_whitespace.length:]
70
- ans.push(lw + l)
71
- ans.push('')
72
- return str.rstrip(ans.join('\n'))
1
+ # vim:fileencoding=utf-8
2
+ # License: BSD Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
3
+ from __python__ import hash_literals
4
+
5
+ from ast import AST_BlockStatement, is_node_type
6
+
7
+ def best_of(a):
8
+ best = a[0]
9
+ len_ = best.length
10
+ for i in range(1, a.length):
11
+ if a[i].length < len_:
12
+ best = a[i]
13
+ len_ = best.length
14
+ return best
15
+
16
+ def make_num(num):
17
+ str_ = num.toString(10)
18
+ a = [ str_.replace(/^0\./, ".").replace("e+", "e") ]
19
+ m = None
20
+
21
+ if Math.floor(num) is num:
22
+ if num >= 0:
23
+ a.push("0x" + num.toString(16).toLowerCase(), # probably pointless
24
+ "0" + num.toString(8))
25
+ else:
26
+ a.push("-0x" + (-num).toString(16).toLowerCase(), # probably pointless
27
+ "-0" + (-num).toString(8))
28
+
29
+ if m = /^(.*?)(0+)$/.exec(num):
30
+ a.push(m[1] + "e" + m[2].length)
31
+
32
+ elif m = /^0?\.(0+)(.*)$/.exec(num):
33
+ a.push(m[2] + "e-" + (m[1].length + m[2].length), str_.substr(str_.indexOf(".")))
34
+
35
+ return best_of(a)
36
+
37
+ def make_block(stmt, output):
38
+ if is_node_type(stmt, AST_BlockStatement):
39
+ stmt.print(output)
40
+ return
41
+
42
+ output.with_block(def():
43
+ output.indent()
44
+ stmt.print(output)
45
+ output.newline()
46
+ )
47
+
48
+ def create_doctring(docstrings):
49
+ ans = v'[]'
50
+ for ds in docstrings:
51
+ ds = str.rstrip(ds.value)
52
+ lines = v'[]'
53
+ min_leading_whitespace = ''
54
+ for line in ds.split(/$/gm):
55
+ r = /^\s+/.exec(line)
56
+ leading_whitespace = ''
57
+ if r:
58
+ leading_whitespace = r[0].replace(/[\n\r]/g, '') if r else ''
59
+ line = line[r[0].length:]
60
+ if not str.strip(line):
61
+ lines.push(v'["", ""]')
62
+ else:
63
+ leading_whitespace = leading_whitespace.replace(/\t/g, ' ')
64
+ if leading_whitespace and (not min_leading_whitespace or leading_whitespace.length < min_leading_whitespace.length):
65
+ min_leading_whitespace = leading_whitespace
66
+ lines.push(v'[leading_whitespace, line]')
67
+ for lw, l in lines:
68
+ if min_leading_whitespace:
69
+ lw = lw[min_leading_whitespace.length:]
70
+ ans.push(lw + l)
71
+ ans.push('')
72
+ return str.rstrip(ans.join('\n'))
@@ -1,72 +1,72 @@
1
- # vim:fileencoding=utf-8
2
- # License: BSD Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
3
- from __python__ import hash_literals
4
-
5
- def quoted_string(x):
6
- return '"' + x.replace(/\\/g, '\\\\').replace(/"/g, r'\"').replace(/\n/g, '\\n') + '"'
7
-
8
- def render_markup(markup):
9
- pos, key = 0, ''
10
- while pos < markup.length:
11
- ch = markup[pos]
12
- if ch is '!' or ch is ':':
13
- break
14
- key += ch
15
- pos += 1
16
- fmtspec = markup[pos:]
17
- prefix = ''
18
- if key.endsWith('='):
19
- prefix=key
20
- key = key[:-1]
21
- return 'ρσ_str.format("' + prefix + '{' + fmtspec + '}", ' + key + ')'
22
-
23
-
24
- def interpolate(template, raise_error):
25
- pos = in_brace = 0
26
- markup = ''
27
- ans = v'[""]'
28
- while pos < template.length:
29
- ch = template[pos]
30
- if in_brace:
31
- if ch is '{':
32
- in_brace += 1
33
- markup += '{'
34
- elif ch is '}':
35
- in_brace -= 1
36
- if in_brace > 0:
37
- markup += '}'
38
- else:
39
- ans.push(v'[markup]')
40
- ans.push('')
41
- else:
42
- markup += ch
43
- else:
44
- if ch is '{':
45
- if template[pos+1] is '{':
46
- pos += 1
47
- ans[-1] += '{'
48
- else:
49
- in_brace = 1
50
- markup = ''
51
- elif ch is '}':
52
- if template[pos+1] is '}':
53
- pos += 1
54
- ans[-1] += '}'
55
- else:
56
- raise_error("f-string: single '}' is not allowed")
57
- else:
58
- ans[-1] += ch
59
-
60
- pos += 1
61
-
62
- if in_brace:
63
- raise_error("expected '}' before end of string")
64
-
65
- if ans[-1] is '+':
66
- ans[-1] = ''
67
- for v'var i = 0; i < ans.length; i++':
68
- if jstype(ans[i]) is 'string':
69
- ans[i] = quoted_string(ans[i])
70
- else:
71
- ans[i] = '+' + render_markup.apply(this, ans[i]) + '+'
72
- return ans.join('')
1
+ # vim:fileencoding=utf-8
2
+ # License: BSD Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
3
+ from __python__ import hash_literals
4
+
5
+ def quoted_string(x):
6
+ return '"' + x.replace(/\\/g, '\\\\').replace(/"/g, r'\"').replace(/\n/g, '\\n') + '"'
7
+
8
+ def render_markup(markup):
9
+ pos, key = 0, ''
10
+ while pos < markup.length:
11
+ ch = markup[pos]
12
+ if ch is '!' or ch is ':':
13
+ break
14
+ key += ch
15
+ pos += 1
16
+ fmtspec = markup[pos:]
17
+ prefix = ''
18
+ if key.endsWith('='):
19
+ prefix=key
20
+ key = key[:-1]
21
+ return 'ρσ_str.format("' + prefix + '{' + fmtspec + '}", ' + key + ')'
22
+
23
+
24
+ def interpolate(template, raise_error):
25
+ pos = in_brace = 0
26
+ markup = ''
27
+ ans = v'[""]'
28
+ while pos < template.length:
29
+ ch = template[pos]
30
+ if in_brace:
31
+ if ch is '{':
32
+ in_brace += 1
33
+ markup += '{'
34
+ elif ch is '}':
35
+ in_brace -= 1
36
+ if in_brace > 0:
37
+ markup += '}'
38
+ else:
39
+ ans.push(v'[markup]')
40
+ ans.push('')
41
+ else:
42
+ markup += ch
43
+ else:
44
+ if ch is '{':
45
+ if template[pos+1] is '{':
46
+ pos += 1
47
+ ans[-1] += '{'
48
+ else:
49
+ in_brace = 1
50
+ markup = ''
51
+ elif ch is '}':
52
+ if template[pos+1] is '}':
53
+ pos += 1
54
+ ans[-1] += '}'
55
+ else:
56
+ raise_error("f-string: single '}' is not allowed")
57
+ else:
58
+ ans[-1] += ch
59
+
60
+ pos += 1
61
+
62
+ if in_brace:
63
+ raise_error("expected '}' before end of string")
64
+
65
+ if ans[-1] is '+':
66
+ ans[-1] = ''
67
+ for v'var i = 0; i < ans.length; i++':
68
+ if jstype(ans[i]) is 'string':
69
+ ans[i] = quoted_string(ans[i])
70
+ else:
71
+ ans[i] = '+' + render_markup.apply(this, ans[i]) + '+'
72
+ return ans.join('')