solpl 2.6.5__tar.gz → 2.7.0__tar.gz

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.
@@ -186,7 +186,7 @@
186
186
  same "printed page" as the copyright notice for easier
187
187
  identification within third-party archives.
188
188
 
189
- Copyright [yyyy] [name of copyright owner]
189
+ Copyright 2026 stefand-0
190
190
 
191
191
  Licensed under the Apache License, Version 2.0 (the "License");
192
192
  you may not use this file except in compliance with the License.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: solpl
3
- Version: 2.6.5
3
+ Version: 2.7.0
4
4
  Summary: A lightweight, multi-threaded interpreter for the Sol programming language
5
5
  Author-email: stefand-0 <stefand-0@users.noreply.github.com>
6
6
  License-Expression: Apache-2.0
@@ -82,6 +82,11 @@ main()
82
82
  end
83
83
  ```
84
84
 
85
+ > [!IMPORTANT]
86
+ > Asynchronous functions cannot use the _return method to return a value. Please use _out instead.
87
+
88
+ > [!TIP]
89
+ > Make use of the standard libraries for extra data types, such as Booleans, HashMaps etc.
85
90
  # License
86
91
 
87
92
  This repository is licensed with Apache License 2.0, see the LICENSE file for more details
@@ -69,6 +69,11 @@ main()
69
69
  end
70
70
  ```
71
71
 
72
+ > [!IMPORTANT]
73
+ > Asynchronous functions cannot use the _return method to return a value. Please use _out instead.
74
+
75
+ > [!TIP]
76
+ > Make use of the standard libraries for extra data types, such as Booleans, HashMaps etc.
72
77
  # License
73
78
 
74
79
  This repository is licensed with Apache License 2.0, see the LICENSE file for more details
@@ -25,35 +25,80 @@ class SolInterpreter:
25
25
  self.thread_local.stack = [self.variables]
26
26
  return self.thread_local.stack
27
27
 
28
- def _import_file(self, filename):
29
- if not os.path.exists(filename):
30
- print(f"Error: Module '{filename}' not found.")
31
- return
28
+ def _has_guard(self, line):
29
+ in_quote = False
30
+ quote_char = None
31
+ for char in line:
32
+ if char in ['"', "'"]:
33
+ if not in_quote:
34
+ in_quote = True
35
+ quote_char = char
36
+ elif char == quote_char:
37
+ in_quote = False
38
+ quote_char = None
39
+ elif not in_quote and char == '?':
40
+ return True
41
+ return False
32
42
 
33
- with open(filename, "r") as f:
34
- code = f.read()
35
- module_name = os.path.splitext(os.path.basename(filename))[0]
43
+ def _split_on_op(self, string, op):
44
+ """Split on first occurrence of op outside quotes. Returns (left, right) or (None, None)."""
45
+ in_quote = False
46
+ quote_char = None
47
+ for i, char in enumerate(string):
48
+ if char in ['"', "'"]:
49
+ if not in_quote:
50
+ in_quote = True
51
+ quote_char = char
52
+ elif char == quote_char:
53
+ in_quote = False
54
+ quote_char = None
55
+ elif not in_quote and char == op:
56
+ return string[:i], string[i+1:]
57
+ return None, None
36
58
 
37
- lines = []
38
- for l in code.split('\n'):
39
- l = l.strip()
40
- if not l: continue
41
- if ';' in l:
42
- l = l.split(';', 1)[0].strip()
43
- if l:
44
- lines.append(l)
59
+ def _split_outside_quotes(self, string, op):
60
+ parts = []
61
+ current = []
62
+ in_quote = False
63
+ quote_char = None
64
+ for char in string:
65
+ if char in ['"', "'"]:
66
+ if not in_quote:
67
+ in_quote = True
68
+ quote_char = char
69
+ elif char == quote_char:
70
+ in_quote = False
71
+ quote_char = None
72
+ current.append(char)
73
+ elif not in_quote and char == op:
74
+ parts.append("".join(current))
75
+ current = []
76
+ else:
77
+ current.append(char)
78
+ parts.append("".join(current))
79
+ return parts
45
80
 
46
- for pc, line in enumerate(lines):
47
- if "(" in line and ")" in line and "main" not in line and "end" not in line and "->" not in line and ">>" not in line and "?" not in line and not line.startswith(("if ", "while ", "for ", "elseif", "else")):
48
- parts = line.split("(")
49
- name = parts[0].strip()
50
- params_str = parts[1].replace(")", "").strip()
51
- params = [p.strip() for p in params_str.split(",")] if params_str else []
52
- self.functions[f"{module_name}.{name}"] = {"start_pc": pc + 1, "params": params, "source_lines": lines}
81
+ def _find_last_op(self, string, op):
82
+ in_quote = False
83
+ quote_char = None
84
+ last_idx = -1
85
+ for i, char in enumerate(string):
86
+ if char in ['"', "'"]:
87
+ if not in_quote:
88
+ in_quote = True
89
+ quote_char = char
90
+ elif char == quote_char:
91
+ in_quote = False
92
+ quote_char = None
93
+ elif not in_quote and char == op:
94
+ last_idx = i
95
+ return last_idx
53
96
 
54
97
  def _compute(self, expr):
55
98
  expr = str(expr).strip()
56
- idx = 0
99
+
100
+ # Handle parentheses
101
+ idx = 0
57
102
  while True:
58
103
  end_idx = expr.find(')', idx)
59
104
  if end_idx == -1:
@@ -61,68 +106,75 @@ class SolInterpreter:
61
106
  start_idx = expr.rfind('(', 0, end_idx)
62
107
  if start_idx == -1:
63
108
  break
64
-
65
- # If there's a letter, number, or underscore right before the '(',
66
- # it's a function call (like my_func() or _fetch()), so skip it here!
109
+ # Skip function calls
67
110
  if start_idx > 0 and (expr[start_idx - 1].isalnum() or expr[start_idx - 1] == '_'):
68
111
  idx = end_idx + 1
69
112
  continue
70
-
71
- # Extract and evaluate the pure math inside the brackets
72
113
  inner_expr = expr[start_idx + 1:end_idx]
73
114
  inner_result = self._compute(inner_expr)
74
-
75
- # Stitch the evaluated number back into the main formula string
76
115
  expr = expr[:start_idx] + str(inner_result) + expr[end_idx + 1:]
77
- idx = 0 # Reset to scan the newly formed string from the beginning
78
- def split_outside_quotes(string, op):
79
- parts = []
80
- current = []
81
- in_quote = False
82
- quote_char = None
83
- for char in string:
84
- if char in ['"', "'"]:
85
- if not in_quote:
86
- in_quote = True
87
- quote_char = char
88
- elif char == quote_char:
89
- in_quote = False
90
- quote_char = None
91
- current.append(char)
92
- elif not in_quote and char == op:
93
- parts.append("".join(current))
94
- current = []
95
- else:
96
- current.append(char)
97
- parts.append("".join(current))
98
- return parts
99
-
100
- plus_parts = split_outside_quotes(expr, '+')
116
+ idx = 0
117
+
118
+ # Handle string concatenation with +
119
+ plus_parts = self._split_outside_quotes(expr, '+')
101
120
  if len(plus_parts) > 1:
102
- if any(any(c.isalpha() or c in ['"', "'"] for c in p) for p in plus_parts):
121
+ is_string_concat = False
122
+ for p in plus_parts:
123
+ p = p.strip()
124
+ if (p.startswith('"') and p.endswith('"')) or (p.startswith("'") and p.endswith("'")):
125
+ is_string_concat = True
126
+ break
127
+ if is_string_concat:
103
128
  result = ""
104
129
  for p in plus_parts:
105
130
  val = self._resolve(p.strip())
106
131
  result += str(val) if val is not None else ""
107
132
  return result
108
-
109
- ops = ['+', '-', '*', ':']
110
- for op in ops:
111
- op_parts = split_outside_quotes(expr, op)
112
- if len(op_parts) > 1:
113
- try:
114
- left = op_parts[0]
115
- right = op.join(op_parts[1:])
116
- r1 = self._resolve(left.strip())
117
- r2 = self._resolve(right.strip())
118
- if r1 is None or r2 is None: continue
119
- val1 = float(r1)
120
- val2 = float(r2)
121
- if op == '+': return val1 + val2
122
- if op == '-': return val1 - val2
123
- if op == '*': return val1 * val2
124
- if op == ':': return val1 / val2
125
- except (ValueError, TypeError): continue
133
+
134
+ # Check for equality (=) - comparison operator
135
+ left, right = self._split_on_op(expr, '=')
136
+ if left is not None:
137
+ r1 = self._resolve(left.strip())
138
+ r2 = self._resolve(right.strip())
139
+ if r1 is not None and r2 is not None:
140
+ return 1 if r1 == r2 else 0
141
+
142
+ # Level 1: + and -
143
+ for op in ['+', '-']:
144
+ idx = self._find_last_op(expr, op)
145
+ if idx != -1:
146
+ left = expr[:idx].strip()
147
+ right = expr[idx+1:].strip()
148
+ r1 = self._resolve(left)
149
+ r2 = self._resolve(right)
150
+ if r1 is None or r2 is None:
151
+ continue
152
+ val1 = float(r1)
153
+ val2 = float(r2)
154
+ if op == '+': return val1 + val2
155
+ if op == '-': return val1 - val2
156
+
157
+ # Level 2: * and :
158
+ last_idx = -1
159
+ last_op = None
160
+ for op in ['*', ':']:
161
+ idx = self._find_last_op(expr, op)
162
+ if idx > last_idx:
163
+ last_idx = idx
164
+ last_op = op
165
+ if last_idx != -1:
166
+ left = expr[:last_idx].strip()
167
+ right = expr[last_idx+1:].strip()
168
+ r1 = self._resolve(left)
169
+ r2 = self._resolve(right)
170
+ if r1 is None or r2 is None:
171
+ pass
172
+ else:
173
+ val1 = float(r1)
174
+ val2 = float(r2)
175
+ if last_op == '*': return val1 * val2
176
+ if last_op == ':': return val1 / val2
177
+
126
178
  return self._resolve(expr)
127
179
 
128
180
  def _lookup(self, name):
@@ -140,6 +192,14 @@ class SolInterpreter:
140
192
  if val.startswith("_fetch(") and val.endswith(")"):
141
193
  url_expr = val[7:-1].strip()
142
194
  return self._native_fetch(str(self._compute(url_expr)))
195
+ if val.endswith("{}") and "{" not in val[:-2]:
196
+ struct_name = val[:-2].strip()
197
+ blueprint = self.struct_blueprints.get(struct_name)
198
+ if blueprint is not None:
199
+ return blueprint.copy()
200
+ obj = self._lookup(struct_name)
201
+ if isinstance(obj, dict):
202
+ return obj.copy()
143
203
  if "(" in val and val.endswith(")") and not val.startswith("_"):
144
204
  parts = val.split("(", 1)
145
205
  name = parts[0].strip()
@@ -149,7 +209,7 @@ class SolInterpreter:
149
209
  return self._call_function(name, args)
150
210
  if "[" in val and "]" in val:
151
211
  try:
152
- name, idx_part = val.split("[")
212
+ name, idx_part = val.split("[", 1)
153
213
  idx = int(idx_part.replace("]", "").strip())
154
214
  container = self._lookup(name)
155
215
  if isinstance(container, list): return container[idx]
@@ -162,23 +222,10 @@ class SolInterpreter:
162
222
  for scope in reversed(self.scope_stack):
163
223
  if val in scope: return scope[val]
164
224
 
165
- # FIX: Only try to parse as number if it looks like one
166
225
  if val.replace('.', '', 1).isdigit() or (val.startswith('-') and val[1:].replace('.', '', 1).isdigit()):
167
226
  return float(val) if '.' in val else int(val)
168
-
169
- return None
170
-
171
- def _async_worker(self, expr, action):
172
- result = self._compute(expr)
173
- if action == "_out": print(f"[ASYNC OUTPUT] {result}")
174
227
 
175
- def _async_input_worker(self, prompt, var_name):
176
- user_val = input(prompt)
177
- try:
178
- if '.' in user_val: user_val = float(user_val)
179
- else: user_val = int(user_val)
180
- except ValueError: pass
181
- self._assign(var_name, user_val)
228
+ return None
182
229
 
183
230
  def _native_fetch(self, url):
184
231
  try:
@@ -206,17 +253,17 @@ class SolInterpreter:
206
253
  self.threads.append(t)
207
254
 
208
255
  def _evaluate_condition(self, cond):
209
- if "==" in cond:
210
- left, right = cond.split("==", 1)
211
- return self._compute(left.strip()) == self._compute(right.strip())
212
- if "=" in cond:
213
- left, right = cond.split("=")
214
- return self._compute(left.strip()) == self._compute(right.strip())
215
- elif "<" in cond:
216
- left, right = cond.split("<")
256
+ # Equality comparison uses =
257
+ left, right = self._split_on_op(cond, "=")
258
+ if left is not None:
259
+ return self._compute(cond) # _compute handles =
260
+ # Less than
261
+ left, right = self._split_on_op(cond, "<")
262
+ if left is not None:
217
263
  return self._compute(left.strip()) < self._compute(right.strip())
218
- elif ">" in cond:
219
- left, right = cond.split(">")
264
+ # Greater than
265
+ left, right = self._split_on_op(cond, ">")
266
+ if left is not None:
220
267
  return self._compute(left.strip()) > self._compute(right.strip())
221
268
  return False
222
269
 
@@ -233,8 +280,21 @@ class SolInterpreter:
233
280
  return pc
234
281
 
235
282
  def execute(self, code):
236
- self.lines = [l.strip() for l in code.split('\n') if l.strip() and ';' not in l or (';' in l and l.split(';', 1)[0].strip())]
237
- self.lines = [l.split(';', 1)[0].strip() for l in self.lines]
283
+ raw_lines = code.split('\n')
284
+ self.lines = []
285
+ for l in raw_lines:
286
+ l = l.strip()
287
+ if not l:
288
+ continue
289
+ if l.startswith(';'):
290
+ continue
291
+ if l.startswith('//'):
292
+ continue
293
+ if ';' in l:
294
+ l = l.split(';', 1)[0].strip()
295
+ if l:
296
+ self.lines.append(l)
297
+
238
298
  self.threads = []
239
299
  pc = 0
240
300
  while pc < len(self.lines):
@@ -242,7 +302,10 @@ class SolInterpreter:
242
302
  if "(" in line and ")" in line and "main" not in line and "end" not in line and "->" not in line and ">>" not in line and "?" not in line and not line.startswith(("if ", "while ", "for ", "elseif", "else")):
243
303
  parts = line.split("(")
244
304
  self.functions[parts[0].strip()] = {"start_pc": pc + 1, "params": [p.strip() for p in parts[1].replace(")", "").split(",")] if parts[1].replace(")", "").strip() else []}
245
- elif "{" in line: pc = self._parse_struct(pc)
305
+ elif line.endswith("{}") and "->" not in line and ">>" not in line and "?" not in line and not line.startswith("_"):
306
+ pc = self._parse_struct(pc)
307
+ pc += 1
308
+ continue
246
309
  pc += 1
247
310
  for i, line in enumerate(self.lines):
248
311
  if line == "main()":
@@ -275,7 +338,9 @@ class SolInterpreter:
275
338
  return self._run_with_scope(self.functions[name]["start_pc"], self.functions[name]["params"], args)
276
339
 
277
340
  def _run_with_scope(self, start_pc, params, args):
278
- local_scope = {p: a for p, a in zip(params, args)}
341
+ local_scope = {}
342
+ for p, a in zip(params, args):
343
+ local_scope[p] = a
279
344
  self.scope_stack.append(local_scope)
280
345
  try: self._run_block(start_pc)
281
346
  except ReturnSignal as sig: return sig.value
@@ -283,55 +348,114 @@ class SolInterpreter:
283
348
  return 0
284
349
 
285
350
  def _execute_line(self, line):
286
- if "_get(" in line:
351
+ # Handle imports
352
+ if line.startswith("_get("):
287
353
  filename = line.split('"')[1]
288
354
  self._import_file(filename)
289
355
  return
290
- if ">>" in line and "_input" in line:
356
+
357
+ # Handle piping input
358
+ if ">>" in line and "_in" in line:
291
359
  parts = [p.strip() for p in line.split(">>")]
292
- t = threading.Thread(target=self._async_input_worker, args=(str(self._compute(parts[0])), parts[2]))
293
- t.start(); self.threads.append(t); return
294
- is_async_func = ">>" in line and "_async" in line and "(" in line and not line.startswith("_")
295
- if is_async_func: line = line.split(">>")[0].strip()
296
- if line.startswith("_return ->"): raise ReturnSignal(self._compute(line.split("->", 1)[1]))
297
- elif ">>" in line and "_async" in line:
298
- parts = line.split(">>")
299
- t = threading.Thread(target=self._async_worker, args=(parts[0].strip(), parts[2].strip()))
300
- t.start(); self.threads.append(t)
301
- elif line.startswith("_in"):
302
- prompt = line.split('(')[1].split(')')[0].replace('"', '').replace("'", "") if "(" in line else f"Input {line.split('->')[1].strip()}: "
303
- sys.stdout.write(prompt + " "); sys.stdout.flush()
304
- val = sys.stdin.readline().strip()
305
- try: self._assign(line.split("->")[1].strip(), float(val) if '.' in val else int(val))
306
- except: self._assign(line.split("->")[1].strip(), val)
307
- elif "_add(" in line:
360
+ if len(parts) >= 3 and parts[1] == "_in":
361
+ prompt = str(self._compute(parts[0]))
362
+ var_name = parts[2]
363
+ sys.stdout.write(prompt + " "); sys.stdout.flush()
364
+ val = sys.stdin.readline().strip()
365
+ try: self._assign(var_name, float(val) if '.' in val else int(val))
366
+ except: self._assign(var_name, val)
367
+ return
368
+
369
+ # Check if async
370
+ is_async = False
371
+ if ">>" in line:
372
+ parts = [p.strip() for p in line.split(">>")]
373
+ if parts[-1] == "_async":
374
+ line = ">>".join(line.split(">>")[:-1]).strip()
375
+ is_async = True
376
+
377
+ if line.startswith("_return ->"):
378
+ raise ReturnSignal(self._compute(line.split("->", 1)[1]))
379
+
380
+ # Handle _out with piping
381
+ if ">>" in line and "_out" in line:
382
+ parts = [p.strip() for p in line.split(">>")]
383
+ if len(parts) == 2 and parts[1] == "_out":
384
+ val = self._compute(parts[0])
385
+ if is_async:
386
+ t = threading.Thread(target=lambda v=val: print(v if v is not None else ""))
387
+ t.start(); self.threads.append(t)
388
+ else:
389
+ print(val if val is not None else "")
390
+ return
391
+
392
+ if line.startswith("_out"):
393
+ val = self._compute(line.split("->", 1)[1].strip())
394
+ if is_async:
395
+ t = threading.Thread(target=lambda v=val: print(v if v is not None else ""))
396
+ t.start(); self.threads.append(t)
397
+ else:
398
+ print(val if val is not None else "")
399
+ return
400
+
401
+ if line.startswith("_in"):
402
+ if "->" in line:
403
+ var_name = line.split("->", 1)[1].strip()
404
+ sys.stdout.write(f"Input {var_name}: "); sys.stdout.flush()
405
+ val = sys.stdin.readline().strip()
406
+ try: self._assign(var_name, float(val) if '.' in val else int(val))
407
+ except: self._assign(var_name, val)
408
+ return
409
+
410
+ if "_add(" in line:
308
411
  parts = [p.strip() for p in line.replace("_add(", "").replace(")", "").split(",")]
309
412
  container = self._lookup(parts[0])
310
413
  if isinstance(container, list): container.insert(int(parts[1]), self._resolve(parts[2]))
311
- elif "_remove(" in line:
414
+ return
415
+
416
+ if "_remove(" in line:
312
417
  parts = [p.strip() for p in line.replace("_remove(", "").replace(")", "").split(",")]
313
418
  container = self._lookup(parts[0])
314
419
  if isinstance(container, list): container.pop(int(parts[1]))
315
- elif "_listen(" in line:
420
+ return
421
+
422
+ if "_listen(" in line:
316
423
  p, h = [p.strip() for p in line.replace("_listen(", "").replace(")", "").split(",")]
317
424
  self._native_listen(self._compute(p), h)
318
- elif "_out" in line:
319
- val = self._compute(line.split("->")[1].strip())
320
- print(val if val is not None else "Error")
321
- elif "mylist<" in line: self._assign(line.split("<")[1].split(">")[0], [])
322
- elif "->" in line:
323
- var, val = [x.strip() for x in line.split("->")]
425
+ return
426
+
427
+ # List declaration
428
+ if "<" in line and ">" in line and "->" not in line and ">>" not in line and "?" not in line and not line.startswith("_"):
429
+ parts = line.split("<")
430
+ if len(parts) == 2 and ">" in parts[1]:
431
+ list_name = parts[1].split(">")[0].strip()
432
+ self._assign(list_name, [])
433
+ return
434
+
435
+ # Assignment - ONLY with ->
436
+ if "->" in line:
437
+ var, val = [x.strip() for x in line.split("->", 1)]
324
438
  if "." in var:
325
439
  obj, prop = var.split(".", 1)
326
440
  target = self._lookup(obj)
327
441
  if isinstance(target, dict): target[prop] = self._compute(val)
328
- else: self._assign(var, self._compute(val))
329
- elif "(" in line and line.endswith(")") and not line.startswith("_"):
442
+ else:
443
+ computed = self._compute(val)
444
+ self._assign(var, computed)
445
+ return
446
+
447
+ # Function call
448
+ if "(" in line and line.endswith(")") and not line.startswith("_"):
330
449
  parts = line.split("(")
331
- if is_async_func:
332
- t = threading.Thread(target=self._call_function, args=(parts[0].strip(), [self._compute(a.strip()) for a in parts[1].replace(")", "").split(",")]))
450
+ func_name = parts[0].strip()
451
+ args_str = parts[1][:-1].strip()
452
+ args = [self._compute(a.strip()) for a in args_str.split(",")] if args_str else []
453
+ if is_async:
454
+ t = threading.Thread(target=self._call_function, args=(func_name, args))
333
455
  t.start(); self.threads.append(t)
334
- else: self._call_function(parts[0].strip(), [self._compute(a.strip()) for a in parts[1].replace(")", "").split(",")])
456
+ else:
457
+ self._call_function(func_name, args)
458
+ return
335
459
 
336
460
  def _run_block(self, start_pc):
337
461
  pc = start_pc
@@ -367,8 +491,9 @@ class SolInterpreter:
367
491
  elif line.startswith("for "):
368
492
  parts = line.split("for ", 1)[1].split("->")
369
493
  var_name = parts[0].strip()
370
- start_v = int(self._compute(parts[1].split(" to ")[0].strip()))
371
- end_v = int(self._compute(parts[1].split(" to ")[1].strip()))
494
+ range_parts = parts[1].split(" to ")
495
+ start_v = int(self._compute(range_parts[0].strip()))
496
+ end_v = int(self._compute(range_parts[1].strip()))
372
497
  end_pc = self._get_block_end(pc)
373
498
  if is_executing and start_v <= end_v:
374
499
  self._assign(var_name, start_v)
@@ -387,7 +512,7 @@ class SolInterpreter:
387
512
  pc = top["start_pc"] - 1
388
513
  pc += 1; continue
389
514
  if is_executing:
390
- if "?" in line:
515
+ if self._has_guard(line):
391
516
  cond, action = line.split("?", 1)
392
517
  if self._evaluate_condition(cond.strip()): self._execute_line(action.strip())
393
518
  else: self._execute_line(line)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "solpl"
7
- version = "2.6.5"
7
+ version = "2.7.0"
8
8
  authors = [
9
9
  { name = "stefand-0", email = "stefand-0@users.noreply.github.com" }
10
10
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: solpl
3
- Version: 2.6.5
3
+ Version: 2.7.0
4
4
  Summary: A lightweight, multi-threaded interpreter for the Sol programming language
5
5
  Author-email: stefand-0 <stefand-0@users.noreply.github.com>
6
6
  License-Expression: Apache-2.0
@@ -82,6 +82,11 @@ main()
82
82
  end
83
83
  ```
84
84
 
85
+ > [!IMPORTANT]
86
+ > Asynchronous functions cannot use the _return method to return a value. Please use _out instead.
87
+
88
+ > [!TIP]
89
+ > Make use of the standard libraries for extra data types, such as Booleans, HashMaps etc.
85
90
  # License
86
91
 
87
92
  This repository is licensed with Apache License 2.0, see the LICENSE file for more details
File without changes
File without changes