├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── docs
├── arrays.md
├── assets
│ ├── icon-black.png
│ ├── icon-white.png
│ ├── icon.png
│ ├── script.js
│ └── style.css
├── builtins.md
├── classes.md
├── comments.md
├── controlflow.md
├── enums.md
├── examples.md
├── fileio.md
├── functions.md
├── index.md
├── interop.md
├── modules.md
├── null.md
├── numbers.md
├── operators.md
├── slices.md
├── stdcollections.md
├── stddatetime.md
├── stdio.md
├── stditer.md
├── stdmath.md
├── stdoperator.md
├── stdrandom.md
├── stdstring.md
├── stdtypes.md
├── strings.md
├── tables.md
├── tools.md
├── variables.md
└── zip.md
├── justfile
├── mkdocs.yml
├── poetry.lock
├── pyproject.toml
└── samarium
├── __init__.py
├── __main__.py
├── builtins.py
├── classes
├── __init__.py
├── base.py
└── fileio.py
├── core.py
├── exceptions.py
├── imports.py
├── modules
├── collections.sm
├── datetime.sm
├── io.sm
├── iter.sm
├── math.sm
├── operator.sm
├── pystd.py
├── random.sm
├── string.sm
└── types.sm
├── python.py
├── repl.py
├── runtime.py
├── template.txt
├── tokenizer.py
├── tokens.py
├── transpiler.py
└── utils.py
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode
2 | .mypy_cache
3 | __pycache__
4 | test
5 | dist/
6 | site/
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## [0.6.2] - 2024-06-19
9 |
10 | ### Changed
11 | - Session names now only allow a limited set of characters (`[a-zA-Z0-9_\-]`)
12 |
13 | ### Fixed
14 | - `:session load` will no longer crash on a non-existent session
15 |
16 | ## [0.6.1] - 2024-06-18
17 |
18 | ### Changed
19 | - Rewrote REPL config handling to not use the `configzen` library
20 |
21 | ## [0.6.0] - 2024-06-14
22 |
23 | ### Added
24 | - `=>!` now accepts strings
25 | - Added an exception note for `string.split`
26 | - Allowed adding notes for `!!!`
27 | - Command system for the REPL
28 | - Data classes
29 | - Function composition
30 | - Made the semicolon optional for `!`, `!!!`, `*`, `**`
31 | - `math.max` and `math.min` now work with varargs
32 | - `String+`
33 | - `-String`
34 | - `~Table`
35 | - The REPL now automatically displays most expressions
36 |
37 | ### Changed
38 | - `String ++ -Number` now throws a type error (ambiguous operation)
39 |
40 | ### Fixed
41 | - Fixed function conversion when `@export`ing
42 | - Fixed internal name of `math.round`
43 | - Fixed `math.sqrt` using an incorrect variable name
44 | - Fixed multiline string handling in the REPL
45 | - Fixed REPL quitting on syntax error
46 | - `math.round` now correctly provides a default value for `ndigits`
47 | - The transpiler now correctly disallows literals around identifiers
48 | - `types.Boolean` should now correctly interract with external types
49 | - Various minor fixes to the transpiler
50 |
51 | ## [0.5.3] - 2023-04-21
52 |
53 | ### Fixed
54 | - Fixed entrypoint argc check template
55 |
56 | ## [0.5.2] - 2023-04-21
57 |
58 | ### Fixed
59 | - Corrected `types.Number` type alias name
60 |
61 | ## [0.5.1] - 2023-04-08
62 |
63 | ### Added
64 | - `~` is now equal to `-1` when not followed by a value (e.g. `~;` or `~<`, but
65 | not `~x` or `~/\`)
66 |
67 | ### Fixed
68 | - Implicit null is no longer placed before `~` (meaning that `>~<` is no longer
69 | invalid syntax)
70 |
71 | ## [0.5.0] - 2023-04-02
72 |
73 | ### Added
74 | - `collections.Queue` now supports membership checking
75 | - `collections.Set` now uses the built-in set-based Array operations (working
76 | with small sets can be up to 3,600x faster)
77 | - Files are now iterable
78 | - `iter.filter` and `iter.map` now adapt to functions based on their number of
79 | parameters
80 | - `math.is_int`
81 | - `math.ceil`
82 | - `math.floor`
83 | - `math.round`
84 | - `math.to_bin` (to replace `Integer$`)
85 | - Number type (in place of the Integer type):
86 | - supports floats
87 | - `x$` now floors the number
88 | - `x -- y` now does true division rather than floor division
89 | - Number literals, using `` ` `` as the decimal point (meaning that it's no
90 | longer ignored by the tokenizer)
91 | - Nulls can now be cast to Numbers
92 | - Parallel assignment
93 | - `random.sample` now supports slices
94 | - Set-based Array operations:
95 | - `-x` returns a copy of `x` with duplicates removed
96 | - `x -- y` ("difference") tries removing elements of `y` from `x`, even if
97 | they're not present
98 | - `x --- y` removes all duplicates of `y` in `x`
99 | - `x | y` ("union") creates an array with elements that appear in either `x`
100 | or `y` (duplicates possible)
101 | - `x & y` ("intersection") creates an array with elements that appear in both
102 | `x` and `y` (duplicates possible)
103 | - `x ^ y` ("symmetric difference") creates an array with elements that appear
104 | in either `x` or `y`, but not both (duplicates possible)
105 | - Slices are now hashable
106 | - `String -- String` (old `String - String` behavior)
107 | - `String++` (equivalent to `String ++ /\`)
108 | - `string.split` now accepts multiple separators
109 | - String→Number now supports scientific notation
110 | - `string.split_lines`
111 | - Strings can have their characters shifted by adding or subtracting Integers
112 | - `types.Frozen`
113 |
114 | ### Changed
115 | - Flipped argument order for `iter.reduce`
116 | - Improved Array/Slice/String index typechecking
117 | - Improved CLI error messages
118 | - Improved error messages
119 | - Improved `File.__str__`
120 | - Improved implicit null detection
121 | - Improved slice transpilation
122 | - `random.shuffle` now does type checking againast slices
123 | - `String - String` now removes only the first occurence
124 | - The main function is no longer required
125 | - Two or more consecutive logical NOTs are now considered a syntax error
126 |
127 | ### Fixed
128 | - Comments are now correctly tokenized
129 | - Fixed `...` special method not being detected
130 | - Fixed Slice→Array construction
131 | - Fixed some yield statements crashing the transpiler
132 | - Functions are now correctly displayed in Arrays/Tables
133 | - Logical NOT (`~~`) no longer lets Python's `bool` slip in
134 | - Multiline strings are now correctly tokenized
135 | - `Number ++ String` is no longer detected as an invalid operation
136 | - Slices are now correctly detected as objects directly after scope exit
137 | - `String.__repr__` now correctly handles escape sequences
138 | - `Type(a) != B` no longer yields incorrect results
139 | - `UserAttrs` can no longer show up when using `x!?`
140 | - Varargs functions now work correctly with recursive functions and decorators
141 |
142 | ### Removed
143 | - `collections.StaticArray`
144 | - Integer type
145 |
146 |
147 |
148 | The [Examples](https://samarium-lang.github.io/Samarium/examples/) page was also
149 | updated with new examples.
150 |
151 | Thanks to [@DancingGrumpyCat](https://github.com/DancingGrumpyCat) for improving the documentation!
152 |
153 | ## [0.4.0] - 2022-12-01
154 |
155 | ### Added
156 | - `Array%`
157 | - `Enum%`
158 | - `io` module:
159 | - `io.Bytes`
160 | - `io.inputcast`
161 | - `io.read_until`
162 | - `iter.cycle` by [@Lunarmagpie](https://github.com/Lunarmagpie)
163 | - New import system, including:
164 | - import aliases
165 | - inline imports
166 | - New special method syntax
167 | - Partial Python Interoperability by [@Endercheif](https://github.com/Endercheif)
168 | - `start` parameter for `math.sum`, thus allowing to sum non-Integers
169 | - Static methods (`~'*` keyword)
170 | - `string.ordinal`
171 | - `string.split()` now supports separators of length greater than 1, and also handles empty separators
172 | - Strings of length greater than 1 can now be cast into Arrays of Integers
173 | - Subtracting strings
174 | - Support for `^L`, `^[[A`, `^[[B`, `^[[C`, `^[[D` in the REPL
175 | - `to_bit` methods for:
176 | - `Enum`
177 | - `File`
178 | - `Iterator`
179 | - `Module`
180 | - `Type`s and functions are now hashable
181 | - Zip `><` operator
182 | - `Zip` type
183 |
184 | ### Changed
185 | - Flipped argument order for:
186 | - `iter.map`
187 | - `iter.filter`
188 | - `iter.filter_false`
189 | - Greatly improved error messages
190 | - Improved `collections.Set` methods:
191 | - Replaced `union` with `|`
192 | - Replaced `intersection` with `&`
193 | - Replaced `difference` with `-`
194 | - Removed `is_subset` in place of `::`, `:::`, `>`, `<`, `>:`, `<:` operator support
195 | - Improved function to string conversion
196 | - Improved implicit null detection
197 | - Improved readline error handling
198 | - Improved slice object detection
199 | - Improved string integer parsing
200 | - Improved variable type verification
201 | - Rewrote the objects (should be 10–50% faster than Samarium 0.3.1 🚀)
202 | - Replaced the native tokenizer with a [crossandra](https://github.com/trag1c/crossandra) tokenizer (~3x faster tokenization 🚀)
203 | - Updated `to_string` methods of:
204 | - `collections.ArithmeticArray`
205 | - `collections.Deque`
206 | - `collections.Queue`
207 | - `collections.Set`
208 |
209 | ### Fixed
210 | - `collections.Set.#new_set_size` now takes unsized sets into consideration
211 | - Constructing `Slice`s from `Type` now correctly works
212 | - Error message shown when trying to run an unknown file is now written to stderr, not stdout
213 | - Syntax errors now cannot be caught
214 |
215 | ### Removed
216 | - `collections.Set.values()` (use `collections.Set.items`)
217 | - English special method names
218 | - `iter.zip` (use the `><` operator)
219 | - `iter.enumerate` (use the `><` operator with `<<>>`)
220 | - Some dead code :)
221 | - `string.format` (use `String`'s `---` operator)
222 |
223 | ---
224 |
225 | Also big thanks to [@qexat](https://github.com/qexat) & [@Celeo](https://github.com/Celeo) for code improvements! ❤️
226 |
227 | ## [0.3.1] - 2022-09-23
228 |
229 | ### Fixed
230 | - Added missing cases for implicit null
231 |
232 | ### Removed
233 | - Unused imports & variables in the standard library
234 | - Unused slots in built-in types
235 |
236 |
237 | ## [0.3.0] - 2022-09-21
238 |
239 | ### Added
240 | - `**` (the yield statement)
241 | - Array support for `string.format`
242 | - Better error messages
243 | - `datetime` module by [@Endercheif](https://github.com/Endercheif)
244 | - Enums
245 | - `iter.chunks`
246 | - `iter.flatten`
247 | - `iter.sorted`
248 | - Iterators
249 | - Memory address lookup
250 | - Private variables
251 | - snake_case support
252 | - Substring support for `iter.find_all`
253 |
254 | ### Changed
255 | - `#` for assert has been replaced by `!!`
256 | - `@@` now returns the Unix timestamp
257 | - `@@@` now returns the date and time array
258 | - Classes can now be used as entry points
259 | - Improved hashing speed
260 | - Improved `iter` module speed by making it use Iterators over Arrays
261 | - Moved from [termcolor](https://pypi.org/project/termcolor/) to [Dahlia](https://github.com/trag1c/Dahlia)
262 | - Rewrote the transpiler
263 | - Slice objects are now iterable and can be used as ranges (about 3–7x faster than `iter.range` 🚀)
264 | - Slices now use `..` instead of `**` for the step delimiter (`<>` would be `<>`)
265 | - Class special method names were changed to snake_case
266 | - Standard Library function names were changed to snake_case
267 | - Standard Library constant names were changed to SCREAMING_SNAKE_CASE
268 | - `to_string` method doesn't need to be defined for the object to be printable (defaults to ``)
269 |
270 | ### Fixed
271 | - Nested slicing
272 |
273 | ### Removed
274 | - `_` as a null token
275 | - `iter.range` (use slices)
276 | - `iter.sort` (use `iter.sorted`)
277 |
278 |
279 | ## [0.2.3] - 2022-06-20
280 |
281 | ### Fixed
282 | - Fixed converting slices to strings
283 |
284 |
285 | ## [0.2.2] - 2022-06-18
286 |
287 | ### Fixed
288 | - Added a missing `argc` attribute for the template
289 |
290 |
291 | ## [0.2.1] - 2022-06-18
292 |
293 | ### Fixed
294 | - Class methods now correctly pass the instance
295 |
296 |
297 | ## [0.2.0] - 2022-06-13
298 |
299 | ### Added
300 | - `-h` / `--help` option
301 | - Argument unpacking
302 | - Decorators
303 | - Enriched modules:
304 | - `collections.ArithmeticArray`
305 | - `iter`:
306 | - `all`
307 | - `any`
308 | - `findAll`
309 | - `pairwise`
310 | - `zip`
311 | - `zipLongest`
312 | - `math.isPrime`
313 | - `random.randint`
314 | - `string.format`
315 | - `types`:
316 | - `Array`
317 | - `Integer`
318 | - `Null`
319 | - `Slice`
320 | - `String`
321 | - `Table`
322 | - `UUID4`
323 | - File descriptor support for standard stream access
324 | - Greatly improved object constructors for:
325 | - `Array`:
326 | - Default value: `[]`
327 | - `Array(a)` will now return a copy of the array `a`
328 | - Arrays can now be constructed from strings and tables:
329 | - `Array("ball") :: ["b", "a", "l", "l"]`
330 | - `Array({{// -> /\\/, "X" -> "D"}}) :: [[//, /\\/], ["X", "D"]]`
331 | - `Integer`:
332 | - Default value: `\`
333 | - Added support for binary, octal, hexadecimal representations:
334 | - `Integer("b:1000") :: Integer("8")`
335 | - `Integer("o:1000") :: Integer("512")`
336 | - `Integer("x:1000") :: Integer("4096")`
337 | - `String`:
338 | - Default value: `""`
339 | - `String(s)` will now return a copy of the string `s`
340 | - `Table`:
341 | - Default value: `{{}}`
342 | - `Table(t)` will now return a copy of the table `t`
343 | - Tables can be constructed from arrays containing 2-element iterables:
344 | - `Table([[//, /\\/], "XD"]) :: {{// -> /\\/, "X" -> "D"}}`
345 | - Introduced a new `Function` class for functions, replacing the old decorator
346 | - New function syntax:
347 | - `arg?` now makes the argument optional
348 | - optional arguments require setting the default value using the `arg <> default` statement inside the function body
349 | - `args...` will now accept a variable number of arguments and pass them to the function as an array
350 | - New `operator` module - containing standard operators as functions:
351 | - `add` (`x + y`)
352 | - `and` (`x & y`)
353 | - `cast` (`x%`)
354 | - `divide` (`x -- y`)
355 | - `equals` (`x :: y`)
356 | - `greaterThanOrEqual` (`x >: y`)
357 | - `greaterThan` (`x > y`)
358 | - `has` (`y ->? x`)
359 | - `hash` (`x##`)
360 | - `lessThanOrEqual` (`x <: y`)
361 | - `lessThan` (`x < y`)
362 | - `mod` (`x --- y`)
363 | - `multiplty` (`x ++ y`)
364 | - `not` (`~x`)
365 | - `notEquals` (`x ::: y`)
366 | - `or` (`x | y`)
367 | - `power` (`x +++ y`)
368 | - `random` (`x??`)
369 | - `special` (`x$`)
370 | - `subtract` (`x - y`)
371 | - `toBit`
372 | - `toString`
373 | - `xor` (`x ^ y`)
374 | - Project description in `pyproject.toml`
375 | - Samarium REPL
376 | - Separate error for IO operations: `IOError`
377 | - Some objects now have a `.random` method, supported by the `object??` syntax:
378 | - `array??` will return a random element
379 | - `integer??` will return:
380 | - a number in range `[0, n)` for positive values
381 | - a number in range `[n, 0)` for negative values
382 | - `0` for `0??`
383 | - `slice??` will return a number from an attribute-based range
384 | - `string??` will return a random character
385 | - `table??` will return a random key
386 | - Shebang support
387 | - Special method for functions:
388 | - `function$` will now return the number of parameters the function accepts
389 | - While loop condition is now optional (`.. {}` is equivalent to `.. / {}`)
390 |
391 | ### Changed
392 | - `<>` now serves for setting the default parameter value
393 | - Bumped the minimum Python version to 3.9
394 | - Classes no longer need the `create` method defined to be initializable.
395 | - Improved scopes and empty body handling
396 | - Improved and added new error messages, such as:
397 | - Invalid table keys
398 | - Unbalanced brackets
399 | - Too many parameters for the main function (or the lack of one)
400 | - Improved object speed:
401 | - `Array`s — up to 30% faster
402 | - `Integer`s — up to 2.4x faster
403 | - `Null` objects — up to 2.2x faster
404 | - `String`s — up to 40% faster
405 | - `Table`s — up to 25% faster
406 | - Improved transpiling safety
407 | - Improved internal typechecking accuracy
408 | - RNG syntax (`^^x -> y^^`) was replaced by the new random `object??` syntax
409 | - `Slice`s can now be instantiated as standalone objects
410 | - Various refactorings
411 |
412 | ### Fixed
413 | - Fixed `-c` option not tokenizing some statements properly, such as:
414 | - statements starting with a table definition
415 | - statements ending with a `!`
416 | - Fixed `string.join` for empty delimiters
417 | - Fixed `string.pad` doubling when `string$` was equal to `length`
418 | - Fixed exception name categorization
419 | - Fixed `string.replace`
420 | - Functions defined inside class methods no longer need an instance
421 | - Fixed false positives for multiple type verification
422 | - Fixed Integer construction for cross-type comparison
423 |
424 | ### Removed
425 | - `->` operator support for assert (`#`)
426 | - Constants
427 | - Default parameter value in the function definition
428 | - `math.fromDecimal` - use `types.Integer(string)` instead
429 | - `random.choice` - use `iterable??` instead
430 |
431 |
432 | ## [0.1.0] - 2022-03-05
433 |
434 | Initial release 🚀
435 |
436 | [0.1.0]: https://github.com/samarium-lang/Samarium/releases/tag/0.1.0
437 | [0.2.0]: https://github.com/samarium-lang/Samarium/compare/0.1.0...0.2.0
438 | [0.2.1]: https://github.com/samarium-lang/Samarium/compare/0.2.0...0.2.1
439 | [0.2.2]: https://github.com/samarium-lang/Samarium/compare/0.2.1...0.2.2
440 | [0.2.3]: https://github.com/samarium-lang/Samarium/compare/0.2.2...0.2.3
441 | [0.3.0]: https://github.com/samarium-lang/Samarium/compare/0.2.3...0.3.0
442 | [0.3.1]: https://github.com/samarium-lang/Samarium/compare/0.3.0...0.3.1
443 | [0.4.0]: https://github.com/samarium-lang/Samarium/compare/0.3.1...0.4.0
444 | [0.5.0]: https://github.com/samarium-lang/Samarium/compare/0.4.0...0.5.0
445 | [0.5.1]: https://github.com/samarium-lang/Samarium/compare/0.5.0...0.5.1
446 | [0.5.2]: https://github.com/samarium-lang/Samarium/compare/0.5.1...0.5.2
447 | [0.5.3]: https://github.com/samarium-lang/Samarium/compare/0.5.2...0.5.3
448 | [0.6.0]: https://github.com/samarium-lang/Samarium/compare/0.5.3...0.6.0
449 | [0.6.1]: https://github.com/samarium-lang/Samarium/compare/0.6.0...0.6.1
450 | [0.6.2]: https://github.com/samarium-lang/Samarium/compare/0.6.1...0.6.2
451 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2021–2024 trag1c
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Samarium
2 |
3 | Samarium is a dynamic interpreted language transpiled to Python.
4 | Samarium, in its most basic form, doesn't use any digits or letters.
5 |
6 | Here's a `Hello, World!` program written in Samarium:
7 |
8 | ```kt
9 | "Hello, World!"!;
10 | ```
11 |
12 | Documentation on how to program in Samarium can be found [here](https://samarium-lang.github.io/Samarium/).
13 |
14 | ## [Examples](https://samarium-lang.github.io/Samarium/latest/examples/)
15 |
16 |
17 | # Installation
18 |
19 | ## [pip](https://pypi.org/project/pip/)
20 |
21 | ```sh
22 | pip install samarium
23 | ```
24 |
25 | ## [AUR](https://aur.archlinux.org/)
26 |
27 | ```sh
28 | git clone https://aur.archlinux.org/samarium.git && cd samarium && makepkg -sirc
29 | ```
30 | or use your favorite [AUR helper](https://wiki.archlinux.org/title/AUR_helpers).
31 |
32 | ## Using Samarium
33 |
34 | You can run Samarium programs with `samarium program.sm`.
35 | `samarium-debug` may be used instead, which will first print out the intermediary Python code that the Samarium program is transpiled into, before executing it.
36 |
37 | Short | Long | Description
38 | :---: | :---: | :---
39 | `-c ` | `--command ` | Can be used to execute Samarium code from the string `cmd`,
directly in the terminal. `cmd` can be one or more statements
separated by semicolons as usual. Note that the last statement
of `cmd` will be printed if it does not end in a semicolon.
40 | `-h` | `--help` | Shows the help message
41 | `-v` | `--version` | Prints Samarium version
42 |
43 |
44 | There is also a VSCode syntax highlighting extension for Samarium, which can be found [here](https://marketplace.visualstudio.com/items?itemName=Samarium.samarium-language). The source code can be found [here](https://github.com/samarium-lang/vscode-samarium).
45 |
46 |
47 | # Credits
48 |
49 | Samarium was inspired by several languages, including [brainfuck](https://esolangs.org/wiki/Brainfuck), [Rust](https://www.rust-lang.org/), and [Python](https://www.python.org/).
50 |
51 | Special thanks to:
52 |
53 | - [tetraxile](https://github.com/tetraxile) for helping with design choices and writing the docs
54 | - [MithicSpirit](https://github.com/MithicSpirit) for making an AUR package for Samarium
55 | - [DarviL82](https://github.com/DarviL82) for fixing some issues
56 | - [Endercheif](https://github.com/Endercheif) for making the documentation look fancy, helping with design choices, and adding partial Python Interoperability
57 |
58 | If you have any questions, or would like to get in touch, join my [Discord server](https://discord.gg/C8QE5tVQEq)!
59 |
--------------------------------------------------------------------------------
/docs/arrays.md:
--------------------------------------------------------------------------------
1 | # Arrays
2 |
3 | Arrays are defined using square brackets, with items separated by commas:
4 |
5 | ```sm
6 | [\, /\, //]
7 | ```
8 |
9 | Arrays can be concatenated with the `+` operator:
10 |
11 | > `[/, /\] + [//]` is the same as `[/, /\, //]`
12 |
13 | To remove items from an array, the `-` operator can be used with either an index
14 | or a value. When removing by value, only the first instance of each value will
15 | be removed. Trying to remove an element not present in the array will result in
16 | an error.
17 |
18 | > `["a", "b", "c"] - /` gives `["a", "c"]`
19 | > `["a", "b", "c", "d"] - ["b", "d"]` gives `["a", "c"]`
20 |
21 | `--` can be used instead if we want to remove elements even if they're not
22 | present in the array:
23 |
24 | > `["a", "b", "c", "d"] - ["c", "d", "e"]` gives `["a", "b"]`
25 |
26 | Negating an array will return its copy with duplicates removed:
27 |
28 | > `-[/\\\, //, //, //\, /\\\, //, /\/]` gives `[/\\\, //, //\, /\/]`
29 |
30 | If we want to remove duplicates of just one value, we can use `---`:
31 |
32 | > `[/\\\, //, //, //\, /\\\, //, /\/] --- //` gives `[/\\\, //, //\, /\\\, /\/]`
33 |
34 | Set-like union, intersection, and symmetric difference can be obtained by using
35 | `|`, `&` and `^`, respectively:
36 |
37 | ```sm
38 | primes: [/\, //, /\/, ///, /\//, //\/]!;
39 | evens: [/\, /\\, //\, /\\\, /\/\, //\\]!;
40 |
41 | "|", primes | evens!;
42 | "&", primes & evens!;
43 | "^", primes ^ evens!;
44 | ```
45 | ```
46 | [2, 3, 5, 7, 11, 13]
47 | [2, 4, 6, 8, 10, 12]
48 | | [2, 3, 5, 7, 11, 13, 4, 6, 8, 10, 12]
49 | & [2]
50 | ^ [3, 5, 7, 11, 13, 4, 6, 8, 10, 12]
51 | ```
52 |
53 | Arrays of integers can be cast to type String:
54 |
55 | > `[//\/\\\, //\/\\/, ////\\/, //\\\\/]%` gives `"hiya"`
--------------------------------------------------------------------------------
/docs/assets/icon-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samarium-lang/Samarium/c6c5c6d0aabf434efe7f80bdccc42c234b3f8228/docs/assets/icon-black.png
--------------------------------------------------------------------------------
/docs/assets/icon-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samarium-lang/Samarium/c6c5c6d0aabf434efe7f80bdccc42c234b3f8228/docs/assets/icon-white.png
--------------------------------------------------------------------------------
/docs/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samarium-lang/Samarium/c6c5c6d0aabf434efe7f80bdccc42c234b3f8228/docs/assets/icon.png
--------------------------------------------------------------------------------
/docs/assets/script.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | const codeBlocks = document.querySelectorAll("pre.sm");
3 |
4 | fetch("https://raw.githubusercontent.com/samarium-lang/vscode-samarium/master/syntaxes/samarium.tmLanguage.json")
5 | .then((data) => data.json())
6 | .then((grammar) => ({
7 | id: "samarium",
8 | scopeName: "source.samarium",
9 | grammar,
10 | }))
11 | .then(async (sm) => shiki.getHighlighter({ theme: "github-dark", langs: [sm] }))
12 | .then((h) => {
13 | [...codeBlocks].forEach((block) => {
14 | block.innerHTML = highlight(h, block.innerText);
15 | });
16 | });
17 |
18 | function highlight(h, code) {
19 | return h.codeToHtml(code, { lang: "samarium" });
20 | }
21 |
--------------------------------------------------------------------------------
/docs/assets/style.css:
--------------------------------------------------------------------------------
1 | code {
2 | --md-code-bg-color: reset !important;
3 | }
4 |
--------------------------------------------------------------------------------
/docs/builtins.md:
--------------------------------------------------------------------------------
1 | # Built-in Functions
2 |
3 |
4 | ## INPUT
5 |
6 | Standard input can be read from with `???`.
7 | It will read until it receives a newline character.
8 |
9 | `x: ???;` will assign to `x` as a string what it reads from standard input,
10 | stopping at a newline character.
11 |
12 | A prompt can be given by preceding the `???` with a string,
13 | for example `x: "input: "???;`.
14 |
15 | > Related library: [`io`](stdio.md)
16 |
17 |
18 | ## PRINTLN
19 |
20 | Objects can be written to standard output by adding an `!` at the end.
21 | Note that they won't be written exactly as they would appear in Samarium.
22 |
23 | `"a"!` will write `a` to standard output.
24 |
25 | `//\/!` will write `13` to standard output.
26 |
27 | This function will return what it writes to stdout (though not necessarily as
28 | a string), and can thus be used in an assignment statement for example.
29 |
30 | ```sm
31 | x: //\! == optional semicolon after !
32 | == the string "6" is written to stdout, and `x` now has the value 6 (number)
33 | ```
34 |
35 |
36 | ## THROW
37 |
38 | Similarly to PRINTLN, some objects can be written to standard error using `!!!`.
39 | This will throw an error, and exit the program if the error is not caught.
40 | Only strings and arrays of length 2 can be used for throwing errors.
41 |
42 | ```sm
43 | "exception raised"!!! == optional semicolon after !!!
44 | == the following is written to stderr:
45 | == [Error] exception raised
46 |
47 | == 2-element arrays can be used for adding notes to exceptions:
48 | ["invalid date format", "use YYYY-MM-DD format"]!!!
49 | == [Error] invalid date format
50 | == [Note] use YYYY-MM-DD format
51 | ```
52 |
53 |
54 | ## EXIT
55 |
56 | The program may be exited with `=>!`.
57 | If a particular exit code is desired, it may be put after the exclamation mark:
58 |
59 | `=>! //` will exit the program with exit code 3.
60 |
61 | A string may also be supplied instead of a number, which will first write it to stderr
62 | and then exit the program with exit code 1.
63 |
64 | > `=>! "uh oh";` is equivalent to `"uh oh\n" ~> /\; =>! /;`
65 |
66 |
67 | ## HASH
68 |
69 | The hash function `##` returns as a integer the hash value of an object if it
70 | has one (arrays and tables do not). The value it returns will remain consistent
71 | for the life of the program, but may vary if the program is run multiple times.
72 |
73 | `"hash"##` will return the hash value of the string `"hash"`.
74 |
75 |
76 | ## TYPEOF
77 |
78 | The typeof function `?!` returns the type of an object,
79 | as an instance of the `Type` class.
80 |
81 | > `/?!` returns `Number`.
82 | > `"abc"?!` returns `String`.
83 | > `/?!?!` returns `Type`.
84 |
85 | These instances are callable and can be used to convert values into that type,
86 | like so:
87 |
88 | ```sm
89 | \?!("123") + /\!;
90 | == writes `125` to stdout
91 | ```
92 |
93 | > Related library: [`types`](stdtypes.md)
94 |
95 |
96 | ## SPECIAL
97 |
98 | The special method `$` has different uses depending on the type of object it's
99 | used on.
100 |
101 | Object | Use
102 | --- | ---
103 | Array | Returns the length of the array
104 | Function | Returns the number of parameters the function has
105 | Number | Returns the integer part of the number as a string
106 | Slice | Returns the length of the range covered by the slice
107 | String | Returns the length of the string
108 | Table | Returns an array of the table's values
109 |
110 | For example:
111 |
112 | ```sm
113 | "string"$!;
114 | == writes `6` to stdout
115 | ```
116 |
117 |
118 | ## TIMESTAMP
119 |
120 | The timestamp function `@@` returns the time in milliseconds since the epoch.
121 |
122 | !!! note
123 | Timezone is included. For UTC timestamp, use `datetime.timestamp_utc`.
124 |
125 | > Related library: [`datetime`](stddatetime.md)
126 |
127 |
128 | ## DTNOW
129 |
130 | The dtnow function `@@@` gets the system's current date and time as an array of
131 | integers, in the format `[year, month, day, hour, minute, second, millisecond,
132 | utc_hour_offset, utc_minute_offset]`.
133 |
134 | > Related library: [`datetime`](stddatetime.md)
135 |
136 | ## SLEEP
137 |
138 | The sleep function `,.,` pauses execution for the specified number of
139 | milliseconds.
140 |
141 | ```sm
142 | ,., /////\/\\\;
143 | == sleep for 1000 milliseconds (1 second)
144 | ```
145 |
146 | > Related library: [`datetime`](stddatetime.md)
147 |
148 |
149 | ## ASSERT
150 |
151 | The assert function `!!` is used as a debugging tool.
152 | If the input to this function is falsy (i.e. empty iterable, null, or just
153 | false), an `AssertionError` will be raised, otherwise, nothing will happen.
154 | A custom error message can be provided by putting it after a `,`.
155 |
156 | ```sm
157 | !! / > /\, "error message";
158 | ```
159 |
160 | will raise `[AssertionError] error message`.
161 |
162 |
163 | ## PARENT
164 |
165 | The parent function `!?` gets the parent/inherited classes of the input class.
166 | If a class only has one parent, it will be returned directly.
167 | If a class has multiple parents, they will be returned in an array, in the same
168 | order as in the class definition.
169 |
170 | Note that this will only go one layer deep, i.e. if class `A` has parent `B`
171 | and class `B` has parent `C`, `A!?` will only return `B`, and `A!?!?` will
172 | return `C`.
173 |
174 |
175 | ## RANDOM
176 |
177 | The special method `??` has different uses depending on the type of object it's
178 | used on.
179 |
180 | Object | Use
181 | --- | ---
182 | Array | Returns a random element
183 | Number | Returns a random number from
an interval based on its value
`n = 0` → `0`
`n > 0` → `[0, n)`
`n < 0` → `[n, 0)`
If `n` is an integer, `n??` is also going to return integers.
184 | Slice | Returns a random number in a range defined by that slice
185 | String | Returns a random character
186 | Table | Returns a random key
187 |
188 | > Related library: [`random`](stdrandom.md)
189 |
190 |
191 | ## ID
192 |
193 | The special method `**` returns the memory address of a given object.
194 |
195 | ```sm
196 | x: //;
197 | x**!; == 7f7ea0367800
198 | ```
199 |
--------------------------------------------------------------------------------
/docs/classes.md:
--------------------------------------------------------------------------------
1 | # Classes
2 |
3 | Classes are defined with the `@` character. Any inherited classes can follow the
4 | name of the class in parentheses, separated by commas[^1].
5 | Class variables and methods are defined inside curly braces.
6 |
7 | Inside a class' methods, the `'` character can be used to reference its instance
8 | variables and methods. Class variables are those that are shared between all
9 | instances of a class, and instance variables are for data unique to each
10 | instance.
11 |
12 | Class variables, instance variables and methods can be accessed with the `.`
13 | operator on an instance of the class. In this case, class methods will
14 | implicitly be given a reference to the instance as the first argument of the
15 | method; `x.method(arg1, arg2, ...)` is equivalent to `x?!.method(x, arg1, arg2, ...)`.
16 | Class variables and methods can also be accessed in the same way directly from
17 | the class itself, though note that in this case methods will not implicitly have
18 | an instance as the first argument, so it must be provided.
19 |
20 | Just like variables, class attributes can be made private by prefixing them with
21 | `#`, making them inaccessible outside the class.
22 |
23 | ```sm
24 | @ Foo {
25 | shared: [];
26 |
27 | => var * {
28 | 'var: var;
29 | '#pv: var - /;
30 | }
31 |
32 | get_pv * {
33 | * '#pv;
34 | }
35 | }
36 |
37 | a: Foo(/\\); == calls `Foo.create`; `a` is now an instance of `Foo`
38 | a.var!; == prints 5
39 | a.get_pv()!; == calls `Foo.get_pv(a)`; prints 4
40 |
41 | b: Foo(/\);
42 | a.shared+: ["str"]; == modifying a class variable for all instances
43 | b.shared!; == prints ["str"]
44 | ```
45 |
46 | Parent classes are inherited from right to left, i.e. the first class in the
47 | inheritance takes priority and will overwrite any functions/variables defined
48 | by the following classes:
49 |
50 | ```sm
51 | @ A {
52 | method * { "A"!; }
53 | }
54 |
55 | @ B {
56 | method * { "B"!; }
57 | }
58 |
59 | @ C(A, B) {}
60 |
61 | => * {
62 | c: C();
63 | c.method(); == prints "A", as class A was inherited last
64 | }
65 | ```
66 |
67 |
68 | There are a number of special methods that the user can implement to override
69 | certain functionality of a class, such as how it's initialized (with the
70 | `create` method), or how it interacts with different operators. These methods
71 | are as follows (where `func(...)` indicates a variable number of arguments):
72 |
73 |
74 |
75 | Function | Python | Use
76 | --- | --- | ---
77 | `+(other)` | `add` | Interacts with the addition operator `+`.
78 | `&(other)` | `and` | Interacts with the bitwise AND operator `&`.
79 | `()(...)` | `call` | Called when an instance itself is "called" as a function;
`x(...)` roughly translates to `x?!.call(x, ...)`.
80 | `%()` | -- | Interacts with the cast function operator `%`.
81 | `=>(...)` | `init` | Initializes an instance of a class, takes any number
of arguments. Typically used for setting instance
variables based on these arguments.
No return value necessary.
82 | `--(other)` | `floordiv` | Interacts with the division operator `--`.
83 | `::(other)` | `eq` | Implements the equality operator `::`.
84 | `<<>>(index)` | `getitem` | Implements indexing an object;
`x<>` is equivalent to `x.get_item(index)`.
85 | `>(other)` | `gt` | Implements the greater than operator `>`.
86 | `>:(other)` | `ge` | Implements the greater than or equal operator `>:`.
87 | `->?(item)` | `contains` | Implements membership testing,
returns `1` (object contains `item`)
or `0` (object does not contain `item`).
Interacts with `->?` operator.
88 | `##()` | `hash` | Called by the built-in hash function `##`,
and for keys in a table.
Objects which compare equal
should have the same hash value.
89 | `...()` | `iter` | Called when iterating over an object in a `foreach` loop.
Returns an array of objects to iterate over.
90 | `<(other)` | `lt` | Implements the less than operator `<`.
91 | `<:(other)` | `le` | Implements the less than or equal operator `<:`.
92 | `---(other)` | `mod` | Interacts with the modulo operator `---`.
93 | `++(other)` | `mul` | Interacts with the multiplication operator `++`.
94 | `-_()` | `neg` | Interacts with the negative unary operator `-`.
95 | `~()` | `invert` | Interacts with the bitwise NOT operator `~`.
96 | `:::(other)` | `ne` | Implements the inequality operator `:::`.
97 | `|(other)` | `or` | Interacts with the bitwise OR operator `\|`.
98 | `+_()` | `pos` | Interacts with the positive unary operator `+`.
99 | `+++(other)` | `pow` | Interacts with the exponentiation operator `+++`.
100 | `??()` | -- | Interacts with the random function operator `??`.
101 | `<<>>:(index, value)` | `setitem` | Implements assigning to an index of an object;
`x<>: value` is equivalent
to `x.set_item(index, value)`.
102 | `$()` | -- | Interacts with the special function operator `$`.
103 | `-(other)` | `sub` | Interacts with the subtraction operator `-`.
104 | `?()` | `bool` | Implements boolean value testing,
returns `1` (truthy) or `0` (falsy).
Used for conditional statements and logical operators.
105 | `!()` | `str` | Returns the string representation of an object.
106 | `^(other)` | `xor` | Interacts with the bitwise XOR operator `^`.
107 |
108 |
109 |
110 | Two special methods – `=>` and `!` – have default definitions:
111 | ```sm
112 | @ Foo {}
113 |
114 | f: Foo();
115 | f!; ==
116 | ```
117 | The above class definition is equivalent to:
118 | ```sm
119 | @ Foo {
120 | => * {}
121 |
122 | ! * {
123 | * "<$name@$address>" --- {{"name" -> '?!, "address" -> '**}};
124 | }
125 | }
126 | ```
127 |
128 | Some of the comparison operators can be inferred from others,
129 | so not all of them are necessary to provide implementations for.
130 | The following operators infer from each other:
131 | - `::` and `:::`
132 | - `>` and `<`
133 | - `>:` and `<:`
134 |
135 |
136 | ## Static Methods
137 | Methods can be made static by replacing the `*` keyword with the `~'*` keyword
138 | (where `~'` can be read as "no instance"):
139 | ```sm
140 | <=calendar.date;
141 |
142 | @ Calendar {
143 | is_weekend date ~'* {
144 | * date.weekday > /\\;
145 | }
146 | }
147 |
148 | Calendar.is_weekend(date("2022-11-08"))!; == 0
149 | ```
150 |
151 |
152 | ## Classes As Entry Points
153 |
154 | A class named `=>` can serve as an entry point instead of a function:
155 | ```sm
156 | => argv * {
157 | "Hello, " + argv<>>!;
158 | }
159 | ```
160 | ```sm
161 | @ => {
162 | => argv * {
163 | "Hello, " + argv<>>!;
164 | }
165 | }
166 | ```
167 |
168 |
169 | ## Class Decorators
170 |
171 | Decorators can also be created using classes:
172 | ```sm
173 | @ OutputStorage {
174 |
175 | => func * {
176 | 'func: func;
177 | 'outputs: [];
178 | }
179 |
180 | () args... * {
181 | out: 'function(**args);
182 | 'outputs_: [out];
183 | * out;
184 | }
185 |
186 | }
187 |
188 | OutputStorage @ multiply a b * {
189 | * a ++ b;
190 | }
191 |
192 | multiply(/\, /\/)!; == 10
193 | multiply(//, ///)!; == 21
194 | multiply(/\\/, //\\)!; == 108
195 |
196 | multiply.outputs!; == [10, 21, 108]
197 | ```
198 |
199 | [^1]: Note that order will be preserved here — if both class `A` and class `B`
200 | implement a function `f`, and class `C` inherits them in the order `(A, B)`,
201 | then `C` will inherit `f` from class `A`, as it is inherited later.
202 |
203 |
204 | ## Data Classes
205 |
206 | Samarium has a shorthand syntax for defining classes whose main purpose is
207 | storing data:
208 | ```sm
209 | @ Person {
210 | => name age * {
211 | 'name: name;
212 | 'age: age;
213 | }
214 |
215 | ! * { * "$0($1, $2)" --- ['?!, 'name, 'age]; }
216 | :: other * { * ['name, 'age] :: [other.name, other.age]; }
217 | > other * { * ['name, 'age] > [other.name, other.age]; }
218 | >: other * { * ['name, 'age] >: [other.name, other.age]; }
219 | }
220 | ```
221 | The above definition can be replaced with just
222 | ```sm
223 | @! Person(name, age);
224 | ```
225 | If we wish to define additional methods, we can just open a pair of braces after
226 | we specify the name and the fields like in a regular class:
227 | ```sm
228 | @! Person(name, age) {
229 | birthday * { 'age+:; }
230 | }
231 |
232 | p: Person("Jake", /\\//)! == Person("Jake", 19)
233 | p.birthday();
234 | p! == Person("Jake", 20)
235 | ```
236 | If you'd like your dataclass to not have any fields, you can either leave the
237 | parentheses empty or omit them entirely:
238 | ```sm
239 | @! Unit(); Unit()! == Unit()
240 | @! Unit; Unit()! == Unit()
241 | ```
242 |
243 | By default, all dataclass instances have a special method defined which returns
244 | its copy:
245 | ```sm
246 | @! Person(name, age);
247 |
248 | p: Person("Bob", /\\//);
249 | q: p;
250 | r: p$;
251 | q.name: "Alice";
252 | r.name: "Dave";
253 | p, q, r! == Person("Alice", 19) Person("Alice", 19) Person("Dave", 19)
254 | ```
--------------------------------------------------------------------------------
/docs/comments.md:
--------------------------------------------------------------------------------
1 | # Comments
2 |
3 | Comments are written using `==`, and comment blocks are written with `==<` and
4 | `>==`:
5 |
6 | ```sm
7 | == single-line comment
8 |
9 | ==< comment block
10 | doesn't end
11 | on newlines >==
12 | ```
--------------------------------------------------------------------------------
/docs/controlflow.md:
--------------------------------------------------------------------------------
1 | # Control Flow
2 |
3 |
4 | ## `if`/`else`
5 |
6 | `if` statements are written using a `?` character, and `else` is written as
7 | `,,`. Blocks are enclosed in curly brackets.
8 | `else if` can be written using `,, ?`.
9 |
10 | ```sm
11 | ? x < \ {
12 | "x is negative"!;
13 | } ,, ? x > \ {
14 | "x is positive"!;
15 | } ,, {
16 | "x = 0"!;
17 | }
18 | ```
19 |
20 |
21 | ## `foreach` loop
22 |
23 | `foreach` loops are written using `...`, and enclosed in curly brackets.
24 | They must be paired with a `->?` operator, indicating the object to iterate over.
25 |
26 | ```sm
27 | arr: [];
28 | ... char ->? "string" {
29 | arr+: [char];
30 | }
31 | == arr :: ["s", "t", "r", "i", "n", "g"]
32 | ```
33 |
34 |
35 | ### Comprehensions
36 |
37 |
38 | #### **Array Comprehensions**
39 |
40 | Array comprehensions are a way to create an array based on another iterable.
41 | Uses may include performing an operation on each item of the iterable, or
42 | creating a subsequence of those items that satisfy a certain condition.
43 |
44 | They are written similarly to `foreach` loops; they can come in two forms,
45 | as follows:
46 |
47 | ```sm
48 | [expression ... member ->? iterable]
49 | [expression ... member ->? iterable ? condition]
50 | ```
51 |
52 | For example, say we want to create an array of square numbers.
53 | Here are two equivalent approaches:
54 |
55 | ```sm
56 | input: [/, /\, //, /\\, /\/];
57 |
58 | == Approach 1
59 | arr: [];
60 | ... n ->? input {
61 | arr+: [n ++ n];
62 | }
63 |
64 | == Approach 2
65 | arr: [n ++ n ... n ->? input];
66 | ```
67 |
68 | In both cases, `arr` is equal to `[1, 4, 9, 16, 25]`.
69 |
70 | Now suppose we want to filter this result to only the odd-numbered items.
71 | There are again two equivalent approaches:
72 |
73 | ```sm
74 | arr: [/, /\\, /\\/, /\\\\, //\\/];
75 |
76 | == Approach 1
77 | filtered: [];
78 | ... n ->? arr {
79 | ? n --- /\ :: / {
80 | filtered+: [n];
81 | }
82 | }
83 |
84 | == Approach 2
85 | filtered: [n ... n ->? arr ? n --- /\ :: /];
86 | ```
87 |
88 | In both cases, `filtered` is equal to `[1, 9, 25]`.
89 |
90 |
91 | #### **Table Comprehensions**
92 |
93 | Table comprehensions have a similar syntax to array comprehensions:
94 |
95 | ```sm
96 | {{key -> value ... member ->? iterable}}
97 | {{key -> value ... member ->? iterable ? condition}}
98 | ```
99 |
100 | For example, both of the following approaches are equivalent:
101 |
102 | ```sm
103 | == Approach 1
104 | tab: {{}};
105 | ... x ->? [/\, /\\, //\] {
106 | tab<>: x ++ x;
107 | }
108 |
109 | == Approach 2
110 | tab: {{x -> x ++ x ... x ->? [/\, /\\, //\]}};
111 | ```
112 |
113 | In both cases, `tab` is equal to `{{2 -> 4, 4 -> 16, 6 -> 36}}`.
114 |
115 |
116 | ## `while` loop
117 |
118 | `while` loops are written with `..`, and enclosed in curly brackets.
119 | The loop condition follows the `..`.
120 | An infinite loop is created when no condition is given.
121 |
122 | ```sm
123 | x: \;
124 | .. x < /\/\ {
125 | x+: /\;
126 | x!;
127 | }
128 | == prints 2, 4, 6, 8, 10
129 | ```
130 |
131 |
132 | ## `break`/`continue`
133 |
134 | `break` statements are written with `<-`, and terminate the enclosing loop
135 | immediately. They can be used in both `foreach` and `while` loops.
136 |
137 | ```sm
138 | x: \;
139 | .. x < /\/ {
140 | x+: /;
141 | ? x :: // { <- }
142 | x!;
143 | }
144 | ```
145 |
146 | This program will print 1, 2, and then terminate the `while` loop on the third
147 | iteration, before printing 3.
148 |
149 | `continue` statements are written with `->`, and immediately finish the current
150 | iteration of the enclosing loop. These can also be used in both `for` and
151 | `while` loops.
152 |
153 | ```sm
154 | x: \;
155 | .. x < /\/ {
156 | x+: /;
157 | ? x :: // { -> }
158 | x!;
159 | }
160 | ```
161 |
162 | This program will print 1, 2, skip the third iteration of the `while` loop,
163 | then print 4, 5, and end the loop normally.
164 |
165 | !!! note
166 | Both `<-` and `->` do not need a semicolon at the end of the statement
167 | – it's optional.
168 |
169 |
170 | ## `try`/`catch`
171 |
172 | `try`-`catch` statements are used for error handling.
173 | `try` clauses are written with `??`, and enclosed in curly brackets.
174 | If, during execution of the contents of the `try` clause, an error is thrown,
175 | the rest of the clause is skipped, the error will be silenced, and the adjoining
176 | `catch` clause will be executed. `catch` clauses are written with `!!`, and are
177 | also enclosed in curly brackets.
178 |
179 | ```sm
180 | ?? {
181 | == error prone code here...
182 | / -- \;
183 | "unreachable"!;
184 | } !! {
185 | "error caught"!;
186 | }
187 | ```
--------------------------------------------------------------------------------
/docs/enums.md:
--------------------------------------------------------------------------------
1 | # Enums
2 |
3 | Enums are defined using the `#` character, before which comes the name of the
4 | enum.
5 |
6 | Each of the enum members has to be separated with a semicolon.
7 |
8 | By default, enum members are assigned increasing numbers, starting from 0.
9 |
10 | You can provide your own values for enum members by simply assigning values to
11 | the names.
12 |
13 | Enum members cannot be modified. Enums are truthy when they have at least 1
14 | member.
15 |
16 | ```sm
17 | Shape # {
18 | Circle;
19 | Square;
20 | }
21 |
22 | Color # {
23 | Red: "#FF0000";
24 | Green: "#00FF00";
25 | Blue: "#0000FF";
26 | }
27 |
28 | Shape!;
29 | Shape.Circle!;
30 | Shape.Square!;
31 | Color.Red!;
32 | ```
33 | ```
34 | Enum(Shape)
35 | 0
36 | 1
37 | #FF0000
38 | ```
39 |
40 | Enums can be casted to Tables:
41 | ```sm
42 | Shape # {
43 | Circle;
44 | Square;
45 | }
46 |
47 | Color # {
48 | Red: "#FF0000";
49 | Green: "#00FF00";
50 | Blue: "#0000FF";
51 | }
52 |
53 | Shape%!;
54 | Color%!;
55 | ```
56 | ```
57 | {{"Circle" -> 0, "Square" -> 1}}
58 | {{"Red" -> "#FF0000", "Green" -> "#00FF00", "Blue" -> "#0000FF"}}
59 | ```
--------------------------------------------------------------------------------
/docs/examples.md:
--------------------------------------------------------------------------------
1 | # Examples
2 |
3 |
4 | ## Reverse a file's contents
5 |
6 | ```sm
7 | => argv * {
8 | file: argv<>>;
9 | data <~ file;
10 | -data ~> file;
11 | }
12 | ```
13 |
14 |
15 | ## Counting duplicates
16 |
17 | ```sm
18 | arr: <-io.inputcast("Enter an array of values: ");
19 | "Found $0 duplicate(s) in the array" --- [arr$ - (-arr)$]!;
20 | ```
21 |
22 |
23 | ## Checking Pythagorean triplets
24 |
25 | ```sm
26 | a, b, c: <-iter.sorted(<-io.inputcast("Enter three integers: "));
27 | "These numbers do $0form a Pythagorean triplet"
28 | --- ["not " ++ (~~(a+++ + b+++ :: c+++))]!;
29 | ```
30 |
31 |
32 | ## Generating a password
33 |
34 | ```sm
35 | CHARSET: <-string.LETTERS + <-string.DIGITS;
36 | ... c ->? [CHARSET?? ... _ ->? <<../?!("Password length: "???)>>] { c ~> /; }!;
37 | ```
38 |
39 |
40 | ## Factorial function
41 |
42 | ```sm
43 | factorial n * {
44 | !! n>:, "n cannot be negative";
45 | ? ~~ n { * /; }
46 | * n ++ factorial(n-);
47 | }
48 |
49 | n: /?!("n: "???);
50 | "n! =", factorial(n)!;
51 | ```
52 |
53 |
54 | ## Memoization
55 |
56 | ```sm
57 | <=types.Frozen;
58 |
59 | memoize func * {
60 | cache: {{}};
61 | wrapper args... * {
62 | args: Frozen(args);
63 | ? args ->? cache { * cache<>; }
64 | result: func(**args);
65 | cache<>: result;
66 | * result;
67 | }
68 | * wrapper;
69 | }
70 |
71 | memoize @ fib n * {
72 | ? n < /\ { * n; }
73 | * fib(n-) + fib(n - /\);
74 | }
75 |
76 | fib(///\/)!;
77 | ```
78 |
79 |
80 | ## Filtering and enumerating a file
81 |
82 | ```sm
83 | == Reads URLs from a file, enumerates them and prints them out.
84 | == If a URL is 32 characters or longer, it will be printed in red.
85 | <=string.[to_upper, strip];
86 |
87 | urls <~~ "urls.txt";
88 | ... lineno, line ->? <> >< urls {
89 | line: strip(line);
90 | ? line$ > ///// {
91 | line: "\033[31m$0\033[0m" --- line;
92 | }
93 | "$0. $1" --- [lineno, line]!;
94 | }
95 | ~urls;
96 | ```
97 |
98 |
99 | ## Guess the number game
100 |
101 | ```sm
102 | "Guess the number!"!;
103 | secret_number: (/\/\+++)??+;
104 |
105 | .. {
106 | guess: "Please input your guess: "???;
107 |
108 | ?? { guess: /?!(guess); }
109 | !! { -> }
110 |
111 | "You guessed:", guess!;
112 |
113 | ? guess < secret_number { "Too small!"!; -> }
114 | ? guess > secret_number { "Too big!"!; -> }
115 |
116 | "You win!"!;
117 | <-
118 | }
119 | ```
120 |
121 |
122 | ## Point class implementation
123 |
124 | ```sm
125 | <=operator -> op;
126 | <=iter.map;
127 |
128 | @ Point {
129 | => scalars... * { 'vector: scalars; }
130 | ! * { * "($0)" --- <-string.join('vector, ", "); }
131 | ... * { ... i -.? 'vector { **i; } }
132 | magnitude * { * <-math.sum('vector) +++ `/; }
133 |
134 | - other * { * Point(**map(op.sub, ' >< other)); }
135 | + other * { * Point(**map(op.add, ' >< other)); }
136 | -- other * { * Point(**map(op.div, ' >< other)); }
137 | ++ other * { * Point(**map(op.mul, ' >< other)); }
138 | --- other * { * Point(**map(op.mod, ' >< other)); }
139 | >< other * { * 'vector >< other.vector; }
140 | +++ other * {
141 | out: [];
142 | ... a ->? 'vector {
143 | ... b ->? other.vector { out+: [[a, b]]; }
144 | }
145 | * -out;
146 | }
147 | }
148 |
149 | q, p: Point(/\, //, /\\), Point(//, /\\, /\/);
150 |
151 | p.magnitude()!; == 3.4641016151377544
152 | []?!(q)!; == [2, 3, 4]
153 |
154 | p + q!; == (5, 7, 9)
155 | p - q!; == (1, 1, 1)
156 | p ++ q!; == (6, 12, 20)
157 | p --- q!; == (1, 1, 1)
158 | p +++ q!;
159 | == [
160 | == [3, 2], [3, 3], [3, 4],
161 | == [4, 2], [4, 3], [4, 4],
162 | == [5, 2], [5, 3], [5, 4]
163 | == ]
164 | ```
165 |
166 |
167 | ## Optional arguments
168 |
169 | ```sm
170 | rect_area length width? * {
171 | width <> length;
172 | * width ++ length;
173 | }
174 |
175 | rect_area(/\/, /\\\)!; == 40
176 | rect_area(/\/)!; == 25
177 | ```
178 |
--------------------------------------------------------------------------------
/docs/fileio.md:
--------------------------------------------------------------------------------
1 | # File I/O
2 |
3 | Files are handled through file I/O objects, which can be in one of several
4 | modes: read, write, read & write, append, and as either text or binary for each
5 | of these. File I/O objects have a cursor, which is updated whenever data is
6 | written to/read from the object. File objects are truthy when they are open.
7 | The current cursor position can be gotten like so:
8 |
9 | ```sm
10 | pos: f<<>>;
11 | == assuming `f` is a file I/O object
12 | ```
13 |
14 |
15 | ## Creating
16 |
17 | Files can be created with the unary `?~>` operator.
18 | `?~> "file.txt"` will create an empty file called `file.txt` in the program
19 | directory.
20 |
21 | !!! note
22 | Files will also be created if they are opened in write or append mode.
23 |
24 |
25 | ## Reading
26 |
27 | Files can be opened for reading in two ways:
28 |
29 | ```sm
30 | f <~~ "file.txt";
31 | == opens `file.txt` for reading, in text mode,
32 | == and stores the file I/O object in `f`.
33 |
34 | f <~% "file.bin";
35 | == opens `file.bin` for reading, in binary mode,
36 | == and stores the file I/O object in `f`.
37 | ```
38 |
39 | These file I/O objects can be read into a variable (a string for text mode, and
40 | an array of integers for binary mode) for use in the program.
41 |
42 | ```sm
43 | string <~ f;
44 | == reads the full contents of the file I/O object `f`
45 | == into `string` (assuming `f` is in text read mode)
46 |
47 | array <% f;
48 | == reads the full contents of the file I/O object `f`
49 | == into `array` (assuming `f` is in binary read mode)
50 | ```
51 |
52 | File objects are also iterable, yielding one line per iteration:
53 | ```sm
54 | path: "hello_world.rs";
55 | f <~~ path;
56 |
57 | "File:", path!;
58 | "-" ++ (//\ + path$)!;
59 | ... lineno, line ->? <> >< f {
60 | lineno, "|", <-string.strip(line, "\n")!;
61 | }
62 |
63 | ~f;
64 | ```
65 | ```
66 | File: hello_world.rs
67 | --------------------
68 | 1 | fn main() {
69 | 2 | println!("Hello, World!");
70 | 3 | }
71 | ```
72 |
73 |
74 | ## Writing
75 |
76 | Files can be opened for writing in two ways:
77 |
78 | ```sm
79 | f ~~> "file.txt";
80 | == opens/creates `file.txt` for writing, in text
81 | == mode, and stores the file I/O object in `f`.
82 |
83 | f %~> "file.bin";
84 | == opens/creates `file.bin` for writing, in binary
85 | == mode, and stores the file I/O object in `f`.
86 | ```
87 |
88 | These file I/O objects can be written to from a variable (a string for text
89 | mode, and an array of integers for binary mode).
90 |
91 | ```sm
92 | string ~> f;
93 | == writes the entirety of `string` into the file I/O
94 | == object `f` (assuming `f` is in text write mode)
95 |
96 | string %> f;
97 | == writes the entire contents of `array` into the file I/O
98 | == object `f` (assuming `f` is in binary write mode)
99 | ```
100 |
101 |
102 | ## Appending
103 |
104 | Files can be opened for appending in two ways:
105 |
106 | ```sm
107 | f &~~> "file.txt";
108 | == opens/creates `file.txt` for appending, in text
109 | == mode, and stores the file I/O object in `f`.
110 |
111 | f &%~> "file.bin";
112 | == opens/creates `file.bin` for appending, in binary
113 | == mode, and stores the file I/O object in `f`.
114 | ```
115 |
116 | The contents of these file I/O objects can be added to from a variable (a string
117 | for text mode, and an array of integers for binary mode).
118 |
119 | ```sm
120 | string &~> f;
121 | == appends the entirety of `string` to the current contents of
122 | == the file I/O object `f` (assuming `f` is in text append mode)
123 |
124 | array &%> f;
125 | == appends the entirety of `array` to the current contents of
126 | == the file I/O object `f` (assuming `f` is in binary append mode)
127 | ```
128 |
129 |
130 | ## Closing
131 |
132 | Files can be closed with the `~` operator.
133 | If files are not closed manually by the user, they will be automatically closed
134 | once the program terminates. Note that the file I/O object will not be released
135 | from memory, but it still cannot be used.
136 |
137 | ```sm
138 | ~f;
139 | == closes the file I/O object `f`
140 | ```
141 |
142 |
143 | ## Quick Operations
144 |
145 | Files can be read from, written to or appended to directly using the filename,
146 | with quick operations. These will open the file in the relevant mode, perform
147 | the operation, and close it, all in one.
148 |
149 | Mode | Operator
150 | --- | ---
151 | Text read | `<~`
152 | Text write | `~>`
153 | Text append | `&~>`
154 | Binary read | `<%`
155 | Binary write | `%>`
156 | Binary append | `&%>`
157 |
158 | For example:
159 |
160 | ```sm
161 | string ~> "file.txt";
162 | == writes the entirety of `string` directly into `file.txt`
163 |
164 | array <% "file.bin";
165 | == reads the full contents of `file.bin` directly into `array`
166 | ```
167 |
168 |
169 | ## File Descriptors
170 | You can also use file descriptors instead of file paths in order to access
171 | standard I/O streams.
172 |
173 | Integer value | Name
174 | :---: | :---:
175 | `\` | Standard Input
176 | `/` | Standard Output
177 | `/\` | Standard Error
178 |
179 | An example use of these could be printing without a newline at the end:
180 | ```sm
181 | ... i ->? <<../\/\>> {
182 | i ~> /;
183 | }
184 | ```
185 | The above code is equivalent to the following Python snippets:
186 | ```py
187 | def main():
188 | for i in range(10):
189 | print(i, end="", flush=True)
190 |
191 | if __name__ == "__main__":
192 | main()
193 | ```
194 | ```py
195 | import sys
196 |
197 | def main():
198 | for i in range(10):
199 | sys.stdout.write(str(i))
200 | sys.stdout.flush()
201 |
202 | if __name__ == "__main__":
203 | main()
204 | ```
205 | All snippets produce the following output:
206 | ```
207 | 0123456789
208 | ```
209 |
--------------------------------------------------------------------------------
/docs/functions.md:
--------------------------------------------------------------------------------
1 | # Functions
2 |
3 | Functions are defined using the `*` character.
4 | Both the function's name and its parameters come before this `*` character, in
5 | that order, separated by spaces.
6 | The function body is enclosed in curly brackets.
7 | The function's return value is preceded by a `*` character as well.
8 | (Functions may also have multiple return statements, or none at all.)
9 |
10 | ```sm
11 | add a b * {
12 | sum: a + b;
13 | * sum;
14 | }
15 | ```
16 |
17 | Calling a function is done as in C-like languages, with the function name,
18 | followed by its arguments in parentheses, separated by commas.
19 |
20 | ```sm
21 | a: /;
22 | b: /\;
23 | c: add(a, b);
24 | ```
25 |
26 | If no value is returned, the semicolon after `*` may be omitted:
27 |
28 | ```sm
29 | exit code * {
30 | ? code {*}
31 | "Success"!;
32 | }
33 | ```
34 |
35 | ## Main Function
36 |
37 | The main function/entrypoint of the program is denoted by `=>`.
38 | This function will be implicitly called on execution of the program.
39 | The return value of the main function indicates the exit code of the program
40 | (optional, defaults to 0). This function will not be called when importing
41 | the module it's located in. Command line arguments can be gotten as an array
42 | with an optional parameter in this function.
43 |
44 | ```sm
45 | => argv * {
46 | == program here
47 | }
48 | ```
49 |
50 |
51 | ## Optional Parameters
52 |
53 | Parameters can be made optional by adding a `?` character after the parameter's
54 | name. Optional parameters are required to have a default value defined in the
55 | function's body using the `param <> default` syntax.
56 |
57 | ```sm
58 | == string.leftpad
59 | leftpad string length char? * {
60 | char <> " ";
61 | * pad(string, length, char) + string;
62 | }
63 |
64 | leftpad("hello", /\/\)!; == hello
65 | leftpad("hello", /\/\, "-")!; == -----hello
66 | ```
67 |
68 |
69 | ## Varargs
70 |
71 | A function can accept a variable number of arguments by adding `...` after the
72 | last parameter's name. Packed arguments will be passed into the function as an
73 | array.
74 |
75 | ```sm
76 | product nums... * {
77 | prod: /;
78 | ... n ->? nums {
79 | prod++: n;
80 | }
81 | * prod;
82 | }
83 |
84 | prod()!; == 1
85 | prod(///)!; == 7
86 | prod(///, /\/\/)!; == 147
87 | prod(/\/, /\\\/\, /\, /\\/\\\, ///)!; == 171360
88 | ```
89 |
90 |
91 | ## Argument Unpacking
92 |
93 | Arguments can be spread into a function by using the `**` unary operator:
94 |
95 | ```sm
96 | pow a b * {
97 | * a +++ b;
98 | }
99 |
100 | arguments = [/\, //];
101 |
102 | pow(**arguments)!;
103 | == equivalent to pow(/\, //)!;
104 | ```
105 |
106 |
107 | ## Decorators
108 |
109 | Decorators are syntactic sugar for calling a function/class which argument is
110 | another callable.
111 |
112 | To use a function as a decorator, write the name, `@` and then declare the
113 | function it is decorating.
114 |
115 | ```sm
116 | == Decorator
117 | double func * {
118 | wrapper args... * {
119 | * func(**args) ++ /\;
120 | }
121 | * wrapper;
122 | }
123 |
124 | == Decorated functions
125 | double @ multiply a b * {
126 | * a ++ b;
127 | }
128 |
129 | double @ code_to_char code * {
130 | * code%;
131 | }
132 |
133 | multiply(/\, /\\)!; == 16
134 | code_to_char(/\\\\/)!; == !!
135 | ```
136 |
137 |
138 | ## Iterators
139 |
140 | Functions can yield values instead of returning them, thus making the function
141 | behave like an iterator. Values are yielded with the `**` operator:
142 |
143 | ```sm
144 | <=math.is_prime;
145 |
146 | prime_generator * {
147 | x: /\;
148 | .. {
149 | ? is_prime(x) {
150 | ** x;
151 | }
152 | x+: /;
153 | }
154 | }
155 |
156 | pg: prime_generator();
157 | pg!;
158 | "Primes below 100:"!;
159 | ... i ->? pg {
160 | ? i > /\/\ +++ /\ {
161 | !;
162 | <-
163 | }
164 | ""?!(i) + " " ~> /;
165 | }
166 | ```
167 | ```
168 |
169 | Primes below 100:
170 | 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97
171 | ```
172 |
173 | Just like with `*`, the semicolon after `**` can be omitted if no value is
174 | yielded:
175 | ```sm
176 | foo * {**}
177 |
178 | []?!(foo())! == [null]
179 | ```
180 |
181 | Iterators support special `$` and cast `%` methods.
182 |
183 | `Iterator%` returns the length of the iterator if available, and null otherwise.
184 |
185 | `Iterator$` yields the next value of the iterator.
186 |
187 | Iterators are always truthy.
188 |
189 |
190 | ## Function Composition
191 |
192 | Functions, types, and type aliases in Samarium can be composed together by using
193 | the `&` operator:
194 |
195 | ```sm
196 | (<-math.sqrt & <-operator.add)(//, //\)! == 3
197 | (<-types.Boolean & /?!)("1")! == true
198 | ```
199 | ```sm
200 | <=math.[abs, max, min];
201 | arrmap: []?! & <-iter.map;
202 | arrmap(abs, [/, `/, //\, -/\\, -/\/])! == [1, 0.5, 6, 4, 5]
203 |
204 | high x * { * min([x, /\/]); }
205 | low x * { * max([x, \]); }
206 | clamp: high & low;
207 |
208 | x: []?!(<<-//../\\/>>)! == [-3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8]
209 | arrmap(clamp, x)! == [0, 0, 0, 0, 1, 2, 3, 4, 5, 5, 5, 5]
210 | ```
211 | ```sm
212 | compose funcs... * {
213 | * <-iter.reduce(<-operator.and, funcs);
214 | }
215 |
216 | repeat_function func times * {
217 | * compose(**[func]++times);
218 | }
219 |
220 | foo x * {
221 | out: x +++ /?!("0.1")!
222 | * out;
223 | }
224 |
225 | repeat_function(foo, /\/)(`/);
226 | == 0.9330329915368074
227 | == 0.9930924954370359
228 | == 0.9993070929904525
229 | == 0.9999306876841536
230 | == 0.999993068552217
231 | ```
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | # Samarium
2 |
3 | Samarium is a dynamic interpreted language transpiled to Python.
4 | Samarium, in its most basic form, doesn't use any digits or letters.
5 |
6 | Here's a `Hello, World!` program written in Samarium:
7 |
8 | ```sm
9 | "Hello, World!"!;
10 | ```
11 |
12 |
13 | # Installation
14 |
15 |
16 | ## [pip](https://pypi.org/project/pip/)
17 |
18 | ```sh
19 | pip install samarium
20 | ```
21 |
22 |
23 | ## [AUR](https://aur.archlinux.org/)
24 |
25 | ```sh
26 | git clone https://aur.archlinux.org/samarium.git && cd samarium && makepkg -sirc
27 | ```
28 | or use your favorite [AUR helper](https://wiki.archlinux.org/title/AUR_helpers).
29 |
30 |
31 | ## Using Samarium
32 |
33 | You can run Samarium programs with `samarium program.sm`.
34 | `samarium-debug` may be used instead, which will first print out the
35 | intermediary Python code that the Samarium program is transpiled into, before
36 | executing it.
37 |
38 |
39 |
40 | Short | Long | Description
41 | :---: | :---: | :---
42 | `-c ` | `--command ` | Can be used to execute Samarium code from the string `cmd`,
directly in the terminal. `cmd` can be one or more statements
separated by semicolons as usual. Note that the last statement
of `cmd` will be printed if it does not end in a semicolon.
43 | `-h` | `--help` | Shows the help message
44 | `-v` | `--version` | Prints Samarium version
45 |
46 |
47 |
48 | There is also a VSCode syntax highlighting extension for Samarium, which can be
49 | found [here](https://marketplace.visualstudio.com/items?itemName=Samarium.samarium-language).
50 | Its source code can be found [here](https://github.com/samarium-lang/vscode-samarium).
51 |
52 |
53 | # Credits
54 |
55 | Samarium was inspired by several languages, including
56 | [brainfuck](https://esolangs.org/wiki/Brainfuck),
57 | [Rust](https://www.rust-lang.org/), and [Python](https://www.python.org/).
58 |
59 | Special thanks to:
60 |
61 | - [tetraxile](https://github.com/tetraxile) for helping with design choices and
62 | writing the docs
63 | - [MithicSpirit](https://github.com/MithicSpirit) for making an AUR package for
64 | Samarium
65 | - [DarviL82](https://github.com/DarviL82) for fixing some issues
66 | - [Endercheif](https://github.com/Endercheif) for making the documentation look
67 | fancy, helping with design choices, and adding partial Python Interoperability
68 |
69 | If you have any questions, or would like to get in touch, join my [Discord server](https://discord.gg/C8QE5tVQEq)!
70 |
--------------------------------------------------------------------------------
/docs/interop.md:
--------------------------------------------------------------------------------
1 | # Python Interoperability
2 |
3 | Samarium 0.4.0 introduced partial Python Interoperability, allowing you to use
4 | Python functions inside Samarium.
5 |
6 | The Python file you want to use has to be in the same directory as your Samarium
7 | file (so standard importing rules apply).
8 |
9 | Making a Python function usable in Samarium is as easy as decorating it with
10 | `@export`—it's gonna do all conversions between supported Samarium and Python
11 | types automatically.
12 |
13 | Python files are imported the same way as Samarium files.
14 |
15 | Samarium files take priority over Python files, meaning that if you have both
16 | `file.py` and `file.sm` in the same folder, Samarium will import `file.sm`.
17 |
18 | ## Examples
19 |
20 | ### Example 1
21 | ```py
22 | # foo.py
23 | from samarium import run, Registry
24 | from samarium.python import export
25 |
26 |
27 | @export
28 | def exec(code: str) -> None:
29 | run(code, Registry({}), __file__)
30 | ```
31 | ```sm
32 | <-foo.exec("/////??!;"); == 14
33 | ```
34 |
35 | ### Example 2
36 | ```py
37 | # bar.py
38 | import json
39 |
40 | from samarium.python import export
41 |
42 |
43 | @export
44 | def read_json(source: str) -> None:
45 | return json.loads(source)
46 | ```
47 | ```sm
48 | source <~ "sample.json";
49 | <-bar.read_json(source)!;
50 | == {{"hello" -> "world", "pi" -> 3.14}}
51 | ```
52 |
53 | ## Supported Conversions
54 |
55 | ### Samarium → Python
56 | Samarium Type | Python Type
57 | --- | ---
58 | Array | list
59 | Enum | Enum
60 | File | IOBase
61 | Number | float
62 | Iterator | Iterator
63 | Null | NoneType
64 | Slice | SliceRange[^1]
65 | String | str
66 | Table | dict
67 | Zip | zip
68 |
69 | [^1]:
70 |
71 | ### Python → Samarium
72 | Python Type | Samarium Type
73 | --- | ---
74 | int | Number
75 | bool | Number
76 | float | Number
77 | str | String
78 | NoneType | Null
79 | list | Array
80 | tuple | Array
81 | set | Array
82 | dict | Table
83 | range | Slice
84 | slice | Slice
85 | SliceRange[^1] | Slice
86 | IOBase | File
87 | zip | Zip
88 | type[Enum] | Enum
89 | Iterator | Iterator
90 |
91 | Additionally, Enum members only get their values converted.
92 |
93 | [^1]: Samarium Slices can act as both Python `range`s and `slice`s, therefore a
94 | `SliceRange` object is being returned. It only has 2 properties, `range` and
95 | `slice`, which return exactly those objects.
--------------------------------------------------------------------------------
/docs/modules.md:
--------------------------------------------------------------------------------
1 | # Modules
2 |
3 | Modules can contain functions and variables that a user may wish to import.
4 | Modules are named after their filename (with `.sm` omitted).
5 | Like variables, module names must consist of only letters and numbers, and are
6 | case sensitive.
7 |
8 |
9 | ## Importing
10 |
11 | Modules can be imported using the `<-` operator, followed by the module's name.
12 | Objects (classes, functions, variables) from this module can then be accessed
13 | with the `.` operator. Module objects are always truthy.
14 |
15 | ```sm
16 | <=string;
17 | == imports the `string` module from Samarium's standard library
18 |
19 | string.to_upper("abc")!;
20 | == prints "ABC"
21 |
22 | string.digits!;
23 | == prints "0123456789"
24 | ```
25 |
26 | Objects can also be directly imported from a module one by one, in which case
27 | they don't need to be preceded by the module name when using them:
28 |
29 | ```sm
30 | <=types.UUID4;
31 | <=math.[abs, sqrt];
32 |
33 | UUID4()!; == a3ace080-8545-4852-a4d8-0385ccbb6a70
34 | sqrt(/\\/)!; == 3
35 | abs(-/\)!; == 2
36 | ```
37 |
38 | All objects in a module can be directly imported at once by using the wildcard
39 | character `*`. Importing everything in this way is typically advised against, as
40 | it may cause poorly readable code and/or name collisions.
41 |
42 | ```sm
43 | <=math.*;
44 |
45 | factorial(//)!; == prints 6
46 | ```
47 |
48 |
49 | ### Import Aliases
50 |
51 | Imported objects can be renamed if needed
52 | by using the `name -> new_name` syntax:
53 | ```sm
54 | <=string.[to_upper -> shout, strip];
55 |
56 | str: " hello! ";
57 | shout(strip(str))!; == HELLO!
58 | ```
59 | This is different to
60 | ```
61 | <=string.to_upper;
62 |
63 | shout: to_upper;
64 | ```
65 | because in this case, both `to_upper` and `shout` are valid options,
66 | whereas the first code block only has `shout`.
67 |
68 |
69 | ### Inline Imports
70 |
71 | Imports that are going to be only used once can be replaced
72 | by inline imports (using the `<-` keyword):
73 | ```sm
74 | i: /?!("Enter a number: "???);
75 | ? <-math.is_prime(i) {
76 | i, "is a prime number"!;
77 | }
78 | ```
--------------------------------------------------------------------------------
/docs/null.md:
--------------------------------------------------------------------------------
1 | # Null
2 |
3 | The null value in Samarium is not represented by any symbol—in fact, it's
4 | represented by the lack of it:
5 | ```sm
6 | x: /;
7 | y:;
8 | z: [-/, , /];
9 | ```
10 | The above code sets `x` to `1`, `y` to `null`, and `z` to `[-1, null, 1]`.
11 |
12 | The following code prints `1` if `y` is equal to `null` and `null` is present in
13 | `z`, and `0` otherwise.
14 | ```sm
15 | y :: && ->? z!;
16 | ```
17 | One could alias null for clarity:
18 | ```sm
19 | null:;
20 | y :: null && null ->? z!;
21 | ```
22 |
23 | Converting null to a number yields the value `0`.
24 |
--------------------------------------------------------------------------------
/docs/numbers.md:
--------------------------------------------------------------------------------
1 | !!! note
2 | The following guide assumes that you are familiar with the basics of
3 | programming.
4 |
5 | # Numbers
6 |
7 | Numbers are represented in base 2, using slashes and backslashes to represent 1
8 | and 0 respectively. `` ` `` is used for the decimal point.
9 | Negative numbers are represented as normal, with a `-` sign before them.
10 |
11 | Let's see some examples of numbers:
12 |
13 | Base 10 | Base 2 | Samarium
14 | --- | --- | ---
15 | `0` | `0` | `\`
16 | `0.5` | `0.1` | ``\`/``
17 | `1` | `1` | `/`
18 | `2` | `10` | `/\`
19 | `2.3125` | `10.0101` | ``/\`\/\/``
20 | `3` | `11` | `//`
21 | `5` | `101` | `/\/`
22 | `8` | `1000` | `/\\\`
23 | `13` | `1101` | `//\/`
24 | `21` | `10101` | `/\/\/`
25 |
26 | Both the integer and decimal part of a number are optional, therefore:
27 | > ``/` `` is equivalent to ``/`\``
28 | > `` `/`` is equivalent to ``\`/``
29 | > `` ` `` is equivalent to ``\`\``
30 |
31 | Integers can be cast to characters represented by that integer's unicode code
32 | point:
33 |
34 | > `/\\\\/%` returns `"!"`
35 | > `//////%` returns `"?"`
--------------------------------------------------------------------------------
/docs/operators.md:
--------------------------------------------------------------------------------
1 | # Operators
2 |
3 |
4 | ## Arithmetic
5 |
6 | Operator | Meaning
7 | --- | ---
8 | `+` | Addition
9 | `-` | Subtraction
10 | `++` | Multiplication
11 | `--` | Division
12 | `+++` | Exponentiation
13 | `---` | Modulo
14 |
15 |
16 | ## Unary
17 |
18 | Operator | Meaning
19 | --- | ---
20 | `+` | Positive
21 | `-` | Negative
22 |
23 |
24 | ## Comparison
25 |
26 | Operator | Meaning
27 | --- | ---
28 | `<` | Less than
29 | `>` | Greater than
30 | `<:` | Less than or equal to
31 | `>:` | Greater than or equal to
32 | `::` | Equal to
33 | `:::` | Not equal to
34 |
35 |
36 | ## Logic and Membership
37 |
38 | Operator | Meaning
39 | --- | ---
40 | `&&` | Logical AND
41 | ||
| Logical OR
42 | `~~` | Logical NOT
43 | `->?` | `x ->? y` returns 1 if `x` is a member of `y`, and 0 if not
44 |
45 |
46 | ## Bitwise
47 |
48 | Operator | Meaning
49 | --- | ---
50 | `&` | Bitwise AND
51 | |
| Bitwise OR
52 | `~` | Bitwise NOT
53 | `^` | Bitwise XOR
54 |
55 |
56 | ## Assignment
57 |
58 | All arithmetic and bitwise operators (except `~`) can be used together with the
59 | assignment operator.
60 |
61 | For example:
62 |
63 | ```sm
64 | x: x - /\/;
65 | x: x ++ //;
66 | x: x --- /\\;
67 | ```
68 |
69 | is equivalent to:
70 |
71 | ```sm
72 | x-: /\/;
73 | x++: //;
74 | x---: /\\;
75 | ```
76 |
--------------------------------------------------------------------------------
/docs/slices.md:
--------------------------------------------------------------------------------
1 | # Slices
2 |
3 | Slices are used to access a range of items in an iterable (strings, arrays,
4 | tables). They're enclosed in double angle brackets.
5 | Slices have three optional parameters delimited by `..`
6 | — `start`, `stop`, and `step`.
7 |
8 | Slices can be either applied to indexable objects (like `String` or `Array`)
9 | or serve as stand-alone objects. Slice objects are hashable.
10 |
11 | Iterating over a slice object generates integers from `start` (inclusive)
12 | to `stop` (exclusive) separated by gaps of size `step`.
13 |
14 | If `start` is not specified, it defaults to 0.
15 | If `stop` is not specified, it defaults to 263 - 1.
16 | If `step` is not specified, it defaults to 1.
17 |
18 | ```sm
19 | str: "abcdefgh";
20 | str<<\>> :: "a";
21 | str</..//\>> :: "def";
22 | str<> :: "bdf";
23 | ```
24 |
25 | All valid slice parameters are as follows:
26 |
27 | Slice | Returns
28 | --- | ---
29 | `<<>>` | the whole iterable
30 | `<>` | the item at position `index`
31 | `<<..stop>>` | items up to index `stop`
32 | `<<....step>>` | items separated by gaps of size `step`
33 | `<>` | items starting from index `start`
34 | `<<..stop..step>>` | items up to index `stop` separated by gaps of size `step`
35 | `<>` | items starting from index `start` separated by gaps of size `step`
36 | `<>` | items starting from index `start` up to index `stop`
37 | `<>` | items starting from index `start` up to index `stop` separated by gaps of size `step`
38 |
--------------------------------------------------------------------------------
/docs/stdcollections.md:
--------------------------------------------------------------------------------
1 | # `collections` module
2 |
3 | The `collections` module implements a few different data structure classes:
4 | [Stack](#stack), [Queue](#queue), [Set](#set), [Deque](#deque),
5 | and [ArithmeticArray](#arithmeticarray).
6 |
7 |
8 | ## Stack
9 |
10 | A stack is a collection of items that the user may "push" a new item on top of,
11 | or "pop" the most recently added/top item from, following a last in first out
12 | order (LIFO).
13 |
14 |
15 |
16 | Method | Use
17 | --- | ---
18 | `=>([size])` | Initializes an empty Stack object with capacity `size`.
If `size` is unspecified it will default to `-1`, giving the stack unbounded capacity.
19 | `is_empty()` | Returns `1` if the number of items in the stack is equal to 0, otherwise returns `0`.
20 | `is_full()` | Returns `1` if the number of items in the stack is equal to the specified capacity,
otherwise returns `0`.[^1]
21 | `peek()` | Returns the value of the item on top of the stack without popping it.
If the stack is empty, this will instead throw an error.
22 | `pop()` | Pops/removes an item from the top of the stack, and returns it.
If the stack is empty, this will instead throw an error.
23 | `push(item)` | Pushes `item` on top of the stack. If the stack is full,
i.e. its size is equal to the specified capacity, this will instead throw an error.
24 | `push_all(items)` | Pushes each element of `items` on top of the stack, one at a time.
25 | `$` | Returns the number of items in the stack.
26 | `?` | Returns `1` if the stack is not empty, otherwise returns `0`.[^2]
27 | `!` | Returns some information about the stack as a string;
its capacity, number of items, and the value of the top item.
28 |
29 |
30 |
31 |
32 | ## Queue
33 |
34 | A queue is a collection of items that the user may "put" ("enqueue") an item at
35 | the back of, or "get" ("dequeue") an item from the front of, following a first
36 | in first out order (FIFO).
37 |
38 |
39 |
40 | Method | Use
41 | --- | ---
42 | `=>([size])` | Initializes an empty Queue object with capacity `size`.
If `size` is unspecified it will default to `-1`, giving the queue unbounded capacity.
43 | `first()` | Returns the value of the item at the front of the queue, without removing it.
If the queue is empty, this will instead throw an error.
44 | `get()` | Gets/removes an item from the front of the queue, and returns it.
If the queue is empty, this will instead throw an error.
45 | `is_empty()` | Returns `1` if the number of items in the queue is equal to 0, otherwise returns `0`.
46 | `is_full()` | Returns `1` if the number of items in the queue is equal to the specified capacity,
otherwise returns `0`.[^1]
47 | `last()` | Returns the value of the item at the back of the queue, without removing it.
If the queue is empty, this will instead throw an error.
48 | `put(item)` | Puts `item` at the back of the queue. If the queue is full,
i.e. its size is equal to the specified capacity, this will instead throw an error.
49 | `put_all(items)` | Puts each element of `items` at the back of the queue, one at a time.
50 | `->?(item)` | Returns `1` if `item` is present in the queue, `0` otherwise.
51 | `$` | Returns the number of items in the queue.
52 | `?` | Returns `1` if the queue is not empty, otherwise returns `0`.[^2]
53 | `!` | Returns some information about the queue as a string;
its capacity, number of items, and the values of its items.
Note that if there are more than 5 items in the queue, the string will be truncated.
54 |
55 |
56 |
57 |
58 | ## Deque
59 |
60 | A deque is a data structure similar to a queue, but where insertion and removal
61 | of elements can be performed from both the front and the back.
62 |
63 |
64 |
65 | Method | Use
66 | --- | ---
67 | `=>([size])` | Initializes an empty Deque object with capacity `size`.
If `size` is unspecified it will default to `-1`, giving the deque unbounded capacity.
68 | `back()` | Returns the value of the item at the back of the deque, without removing it.
If the deque is empty, this will instead throw an error.
69 | `front()` | Returns the value of the item at the front of the deque, without removing it.
If the deque is empty, this will instead throw an error.
70 | `get()` | Gets/removes an item from the back of the deque, and returns it.
If the deque is empty, this will instead throw an error.
71 | `get_front()` | Gets/removes an item from the front of the deque, and returns it.
If the deque is empty, this will instead throw an error.
72 | `is_empty()` | Returns `1` if the number of items in the deque is equal to 0, otherwise returns `0`.
73 | `is_full()` | Returns `1` if the number of items in the deque is equal to the specified capacity,
otherwise returns `0`.[^1]
74 | `put(item)` | Puts `item` at the back of the deque. If the deque is full,
i.e. its size is equal to the specified capacity, this will instead throw an error.
75 | `put_all(items)` | Puts each element of `items` at the back of the deque, one at a time.
76 | `put_front(item)` | Puts `item` at the front of the deque. If the deque is full,
i.e. its size is equal to the specified capacity, this will instead throw an error.
77 | `put_front_all(items)` | Puts each element of `items` at the front of the deque, one at a time.
78 | `$` | Returns the number of items in the deque.
79 | `?` | Returns `1` if the deque is not empty, otherwise returns `0`.[^2]
80 | `!` | Returns some information about the deque as a string;
its capacity, number of items, and the values of its items.
Note that if there are more than 5 items in the deque, the string will be truncated.
81 |
82 |
83 |
84 |
85 | ## Set
86 |
87 | A set is an unordered collection of items, with no duplicates.
88 |
89 |
90 |
91 | Method | Use
92 | --- | ---
93 | `=>([items][, capacity])` | Initializes a `Set` object, with its contents being `items` with any
duplicate elements removed, and its capacity being `capacity`.
If `items` is unspecified it will default to an empty array.
If `capacity` is unspecified it will default to `-1`,
giving the set unbounded capacity.
94 | `items` | The contents of the set as an array.
95 | `add(value)` | Adds `value` to the set, provided it doesn't already exist in the set,
and returns a status code (`0` or `1`) based on whether it was added.
If `value` isn't already in the set, and the set is full,
i.e. its size is equal to the specified capacity, this will instead throw an error.
96 | `clear()` | Removes every element from the set.
97 | `value ->?` | Returns `1` if `value` is contained in the set, otherwise returns `0`.
98 | `is_empty()` | Returns `1` if the number of items in the set is equal to 0,
otherwise returns `0`.
99 | `is_full()` | Returns `1` if the number of items in the set
is equal to the specified capacity, otherwise returns `0`.[^1]
100 | `remove(value)` | Removes `value` from the set, provided it exists in the set.
101 | `- other` | Returns the difference of the current set and `other`.
102 | `& other` | Returns the intersection of the current set and `other`.
103 | `:: other` | Returns `1` if the current set and `other` have the same elements, `0` otherwise.
104 | `::: other` | Returns `1` if the current set and `other`
don't have the same elements, `0` otherwise.
105 | `<: other` | Returns `1` if the current set is a subset of `other`.
106 | `< other` | Returns `1` if the current set is a strict subset of `other`.
107 | `>: other` | Returns `1` if the current set is a superset of `other`.
108 | `> other` | Returns `1` if the current set is a strict superset of `other`.
109 | `| other` | Returns the union of the current set and `other`.
110 | `$` | Returns the number of items in the set.
111 | `?` | Returns `1` if the deque is not empty, otherwise returns `0`.[^2]
112 | `!` | Returns some information about the set as a string;
its capacity, number of items, and the values of its items.
113 |
114 |
115 |
116 |
117 | ## ArithmeticArray
118 |
119 | An arithmetic array is an array which can be used with different binary
120 | operators.
121 |
122 | ```sm
123 | <=collections.ArithmeticArray;
124 |
125 | aa: ArithmeticArray([/\, //, /\/]);
126 |
127 | aa!; == [2, 3, 5]
128 | aa + /\!; == [4, 5, 7]
129 | aa ++ ///!; == [14, 21, 35]
130 |
131 | is_odd: aa --- /\;
132 | is_odd!; == [0, 1, 1]
133 |
134 | aa: ArithmeticArray(["oh", "hey", "hello"]);
135 | aa + "!"!; == ["oh!", "hey!", "hello!"]
136 | ```
137 |
138 | Binary operators supported by ArithmeticArray:
139 | - arithmetic: `+`, `++`, `+++`, `-`, `--`, `---`
140 | - bitwise: `&`, `|`, `^`
141 | - comparison: `::`, `>:`, `>`, `<:`, `<`, `:::`
142 |
143 | ArithmeticArray allows item assignment and inherits behavior for `$`, `to_bit`,
144 | and `to_string` from the `Array` class.
145 |
146 | You can also use your own custom operators in the form of functions by using the
147 | `apply(op, other)` method:
148 |
149 | ```sm
150 | <=collections.ArithmeticArray;
151 |
152 | aa: ArithmeticArray([///, /\//, //\/]);
153 | aa!;
154 | aa.apply(<-math.shl, /\)!;
155 |
156 |
157 | remove_null nullable default * {
158 | * default ? nullable :: _ ,, nullable;
159 | }
160 |
161 | aa: ArithmeticArray([_, /\, //, _, /\/, _, ///]);
162 | aa!; == [null, 2, 3, null, 5, null, 7]
163 | aa.apply(remove_null, \)!; == [0, 2, 3, 0, 5, 0, 7]
164 | ```
165 |
166 |
167 | [^1]: Note that this will always return `0` if the specified capacity is
168 | negative, or if the user does not provide a capacity.
169 |
170 | [^2]: `?` is functionally the opposite of `is_empty()`.
171 |
--------------------------------------------------------------------------------
/docs/stddatetime.md:
--------------------------------------------------------------------------------
1 | # `datetime` module
2 |
3 | The `datetime` module consists of several functions that assist with date and
4 | time related tasks.
5 | Additionally, it includes two classes (`DateTime` and `DTDiff`).
6 |
7 |
8 |
9 | Variable | Contents
10 | --- | ---
11 | MONTHS | `["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]`
12 | WEEKDAYS | `["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]`
13 |
14 | Function | Use
15 | --- | ---
16 | `sleep_seconds(seconds)` | Pauses execution for `seconds` seconds.
17 | `sleep_minutes(minutes)` | Pauses execution for `minutes` minutes.
18 | `is_leap_year(year)` | Returns `1` if the year is a leap year, `0` otherwise.
19 | `days_in_month(year, month)` | Returns the amount of days in a given month keeping in track leapyears.
20 | `month_name(n)` | Gives the name of a numbered month where `1` is `January`.
21 | `weekday_name(n)` | Gives the day of a week from a number
where `1` is a `Monday` and `7` is `Sunday`.
22 | `timestamp_utc([ts])` | Yields the `UTC` timestamp using the provided timestamp or current time.
23 |
24 |
25 |
26 | ## DateTime
27 |
28 |
29 |
30 | Method | Use
31 | --- | ---
32 | `subtract(other)` | Subtract two DateTime objects, returns a DTDiff object.
33 | `to_timestamp()` | Returns the date & time as a Unix timestamp in milliseconds.
34 | `!` | Return the date and time in the format `Y-M-D h:m:s.z`.
35 |
36 |
37 |
--------------------------------------------------------------------------------
/docs/stdio.md:
--------------------------------------------------------------------------------
1 | # `io` module
2 |
3 | The `io` module contains a few utilities for working with I/O.
4 |
5 | ### `io.Bytes`
6 | Allows for easier working with files in binary mode:
7 | ```sm
8 | b: <-io.Bytes("ball");
9 | b!; == 62 61 6c 6c
10 | b+: "!"; == supports Strings, integers, Arrays of integers, and other Bytes
11 | b.export_string()!; == ball!
12 | b.export()!; == [98, 97, 108, 108, 33]
13 | ```
14 |
15 | ### `io.inputcast([prompt])`
16 | Works just like `???`, but tries converting the input to a specific type.
17 |
18 | Example (showing all available conversions):
19 | ```sm
20 | <=io.inputcast;
21 |
22 | .. {
23 | value: inputcast(">> ");
24 | "Type:", value?!!;
25 | "Value:", value!;
26 | }
27 | ```
28 | ```
29 | >> hello
30 | Type: String
31 | Value: hello
32 | >> hello there
33 | Type: Array
34 | Value: ["hello", "there"]
35 | >> 1
36 | Type: Number
37 | Value: 1
38 | >> 1 2
39 | Type: Array
40 | Value: [1, 2]
41 | >> 1, 2
42 | Type: Array
43 | Value: [1, 2]
44 | >>
45 | Type: Null
46 | Value: null
47 | >> a=1, b=2
48 | Type: Table
49 | Value: {{"a" -> 1, "b" -> 2}}
50 | >> 3..7
51 | Type: Slice
52 | Value: <<3..7>>
53 | ```
54 |
55 | ### `io.read_until([target])`
56 | Keeps reading lines until the `target` (`""` by default) is entered.
57 | The `target` is included in the output string.
58 |
59 | Example:
60 | ```sm
61 | "Enter your JSON:"!;
62 | program: <-io.read_until("}");
63 | name: "Enter file name: "???;
64 | program ~> name;
65 | "Your program was saved to " + name!;
66 | ```
67 | ```
68 | Enter your JSON:
69 | {
70 | "name": "John Doe",
71 | "age": 30,
72 | "email": "johndoe@example.com",
73 | "interests": [
74 | "reading",
75 | "hiking",
76 | "cooking"
77 | ]
78 | }
79 | Enter file name: ball.json
80 | Your program was saved to ball.json
81 | ```
--------------------------------------------------------------------------------
/docs/stditer.md:
--------------------------------------------------------------------------------
1 | # `iter` module
2 |
3 | The `iter` module contains several functions that interact with iterable
4 | objects, like strings or arrays.
5 |
6 |
7 |
8 | Function | Use
9 | --- | ---
10 | `accumulate(array, function)` | Yields accumulated applications of `function`[^2]
on consecutive elements of `array`.
If for example `function` returned the sum of both of its arguments,
then `accumulate([/, /\, //, /\\, /\/], function)`
would yield `1`, `3`, `6`, `10`, and `15`.
11 | `all(array)` | Returns `1` if all elements of `array` are truthy,
`0` otherwise. Returns `1` for empty arrays.
12 | `any(array)` | Returns `1` if any of the elements of `array` is truthy,
`0` otherwise. Returns `0` for empty arrays.
13 | `chunks(array, size)` | Iterates over `array` in chunks of size `size`.
When `array`'s length is not evenly divided by `size`,
the last slice of `array` will be the remainder.
14 | `cycle(iter)` | Copies an iterable by consuming it,
and yields its elements in an infinite cycle.
15 | `count(array, target)` | Returns the number of times `target` appears in `array`.
16 | `drop_while(array, function)` | Evaluates `function`[^1] on each item of `array`,
and yields elements of `array` starting from the first item
(from the left) for which `function` returns a falsy value.
17 | `filter(function, array)` | Evaluates `function`[^1] on each item of `array`,
and yields those items that cause `function` to return a truthy value.
`function`s taking *x* (≥2) arguments require
each element of `array` to have *x* elements.
18 | `filter_false(function, array)` | Evaluates `function`[^1] on each item of `array`,
and yields those items that cause `function` to return a falsy value.
19 | `find(array, target)` | Finds the first instance of `target` in `array`, and returns its index.
`array` may be of type Array or String.
If `target` does not appear in `array`, `-1` is returned instead.
20 | `find_all(array, target)` | Finds all instances of `target` in `array`, and yields their indices.
21 | `flatten(array[, depth])` | Flattens `array` `depth` times.
By default, flattens recursively as deep as possible.
22 | `map(function, array)` | Applies `function`[^1] to each item of `array`, and yields those new values.
`function`s taking *x* (≥2) arguments require
each element of `array` to have *x* elements.
23 | `pairwise(array)` | Yields successive overlapping pairs taken from `array`.
24 | `reduce(function, array)` | Applies `function`[^2] cumulatively to consecutive items of `array`,
reducing it to a single value, then returns this value.
Equivalent to `[i ... i ->? accumulate(array, function)]<<-/>>`.
25 | `reverse(array)` | Yields the items of `array` in reverse order.
26 | `sorted(array[, key])` | Returns a sorted copy of `array`.
The optional parameter `key` specifies a function[^1] that is used
to extract a comparison key from each element in `array`.
Elements are compared directly by default.
27 | `take_while(array, function)` | Evaluates `function`[^1] on each item of `array`,
and yields elements of `array` that is cut off at the first item
(from the left) for which `function` returns a falsy value.
28 | `zip_longest(fill, arrays...)` | Iterates over several arrays, producing a set of arrays
containing an item from each original array.
If the arrays are of uneven length,
missing values are filled using the `fill` argument.
29 |
30 |
31 |
32 | [^1]: Note that `function` must take only one argument (excluding optional
33 | parameters).
34 |
35 | [^2]: Note that `function` must take exactly two arguments (excluding optional
36 | parameters).
37 |
--------------------------------------------------------------------------------
/docs/stdmath.md:
--------------------------------------------------------------------------------
1 | # `math` module
2 |
3 | The `math` module provides access to a set of commonly used mathematical
4 | functions and constants.
5 |
6 | Constants:
7 |
8 | - `math.E` (e: 2.718281828459045...)
9 | - `math.PHI` (φ: 1.618033988749894...)
10 | - `math.PI` (π: 3.141592653589793...)
11 | - `math.TAU` (τ: 6.283185307179586...)
12 |
13 |
14 |
15 | Function | Use
16 | --- | ---
17 | `abs(n)` | Returns the absolute value of `n`.
18 | `ceil(x)` | Returns the least integer ≥ `x`.
19 | `factorial(n)` | Returns `n` factorial.
20 | `floor(x)` | Returns the greatest integer ≤ `x`.
21 | `gcd(a, b)` | Returns the greatest common divisor of `a` and `b`.
If either argument is zero, the absolute value of the other argument will be returned.
22 | `is_int(x)` | Returns `1` if `x` is an integer, `0` otherwise. Equivalent to `x :: x$`.
23 | `is_prime(n)` | Returns `1` if `n` is prime, `0` otherwise.
24 | `lcm(a, b)` | Returns the least common multiple of `a` and `b`.
If any of the arguments is zero,
then the returned value is `0`.
25 | `max(array[, values...])` | Returns the largest value in `array` if only one argument is passed,
otherwise returns the largest value in `[array] + values`.
26 | `min(array[, values...])` | Returns the smallest value in `array` if only one argument is passed,
otherwise returns the smallest value in `[array] + values`.
27 | `product(array)` | Multiplies the items of `array` from left to right and returns the total.
The `array`'s items must be numbers.
28 | `round(x[, ndigits])` | Returns `x` rounded to `ndigits` precision after the decimal point.
Works exactly like Python's [`round()`](https://docs.python.org/3/library/functions.html#round).
29 | `shl(a, b)` | Returns `a` shifted to the left by `b` bits.
30 | `shr(a, b)` | Returns `a` shifted to the right by `b` bits.
31 | `sqrt(x)` | Returns the square root of the nonnegative number `n`.
32 | `sum(array[, start])` | Sums `start` and the items of `array` from left to right and returns the total.
`start` defaults to `0`.
33 | `to_bin(n)` | Returns the binary representation of `n` as a string.
34 | `to_oct(n)` | Returns the octal representation of `n` as a string.
35 | `to_hex(n)` | Returns the hexadecimal representation of `n` as a string.
36 |
37 |
38 |
--------------------------------------------------------------------------------
/docs/stdoperator.md:
--------------------------------------------------------------------------------
1 | # `operator` module
2 |
3 | This module contains a set of functions corresponding to the native operators
4 | of Samarium. For instance, `operator.mul(a, b)` is equivalent to `a ++ b`.
5 | Each of the function names can be used for defining special methods in classes.
6 |
7 |
8 |
9 | Function | Operator
10 | --- | :---:
11 | `add(x, y)` | `x + y`
12 | `and(x, y)` | `x & y`
13 | `cast(x)` | `x%`
14 | `div(x, y)` | `x -- y`
15 | `eq(x, y)` | `x :: y`
16 | `ge(x, y)` | `x >: y`
17 | `gt(x, y)` | `x > y`
18 | `has(x, y)` | `y ->? x`
19 | `hash(x)` | `x##`
20 | `le(x, y)` | `x <: y`
21 | `lt(x, y)` | `x < y`
22 | `mod(x, y)` | `x --- y`
23 | `mul(x, y)` | `x ++ y`
24 | `not(x)` | `~x`
25 | `ne(x, y)` | `x ::: y`
26 | `or(x, y)` | `x | y`
27 | `pow(x, y)` | `x +++ y`
28 | `random(x)` | `x??`
29 | `special(x)` | `x$`
30 | `sub(x, y)` | `x - y`
31 | `to_bit(x)` | `/ ? x ,, \`
32 | `to_string(x)` | `""?!(x)`
33 | `xor(x, y)` | `x ^ y`
34 |
35 |
36 |
--------------------------------------------------------------------------------
/docs/stdrandom.md:
--------------------------------------------------------------------------------
1 | # `random` module
2 |
3 | The `random` module implements functions that make use of the
4 | [random method `??`](builtins.md#RANDOM).
5 |
6 | Function | Use
7 | --- | ---
8 | `choices(iter, k)` | Randomly selects an item from `iter` `k` times
and returns the resulting choices as an array.
9 | `randint(a, b)` | Returns a random integer in range [a, b].
10 | `sample(array, k)` | Randomly selects `k` unique items from `array`,
and returns the resulting choices as an array.
Also accepts slices.
11 | `shuffle(array)` | Randomly shuffles `array` and returns the result.
`array` must be of type Array.
12 |
--------------------------------------------------------------------------------
/docs/stdstring.md:
--------------------------------------------------------------------------------
1 | # `string` module
2 |
3 | The `string` module contains several useful functions for string manipulation,
4 | as well as some variables containing groups of similar characters.
5 |
6 |
7 |
8 | Variable | Contents
9 | --- | ---
10 | `DIGITS` | `"0123456789"`
11 | `HEXDIGITS` | `"0123456789abcdef"`
12 | `LETTERS` | `UPPERCASE + LOWERCASE`
13 | `LOWERCASE` | `"abcdefghijklmnopqrstuvwxyz"`
14 | `OCTDIGITS` | `"01234567"`
15 | `PRINTABLE` | `LETTERS + DIGITS + PUNCTUATION + WHITESPACE`
16 | `PUNCTUATION` | ``"!\"#$%&'()*+,-./:;<=>?@[]^_`{\|}~"``
17 | `UPPERCASE` | `"ABCDEFGHIJKLMNOPQRSTUVWXYZ"`
18 | `WHITESPACE` | `" \t\n\r\f\v"`
19 |
20 | Function | Use
21 | --- | ---
22 | `capitalize(string)` | Returns a copy of `string` with the first character
set to uppercase (assuming it's a cased character[^2])
and all subsequent character set to lowercase.
23 | `center(string, length[, char])`[^1] | Returns `string` centered in a new string of length `length`,
padded using the specified `char`.
If `char` is not specified, it defaults to `" "` (space).
`string` is returned unchanged if `length` is
less than or equal to the length of `string`.
24 | `ends_with(string, suffix)` | Returns `1` if `string` ends with the substring `suffix`,
otherwise returns `0`.
25 | `is_alphabetic(string)` | Returns `1` if every character in `string` is
an alphabetic character,
i.e. is contained in the string `letters`, otherwise returns `0`.
26 | `is_alphanumeric(string)` | Returns `1` if every character in `string` is
an alphanumeric character,
i.e. is contained in the strings `letters` or `numbers`,
otherwise returns `0`.
27 | `is_capitalized(string)` | Returns `1` if `string` is capitalized,
i.e. matches the output of `capitalize(string)` exactly.
28 | `is_decimal(string)` | Returns `1` if every character in `string` is a decimal digit,
i.e. is contained in the string `DIGITS`, otherwise returns `0`.
29 | `is_hexadecimal(string)` | Returns `1` if every character in `string` is a hexadecimal digit,
i.e. is contained in the string `HEXDIGITS`, otherwise returns `0`.
30 | `is_in_group(string, group)` | Returns `1` if every character in `string` is
in the specified `group`
of type Array or String, otherwise returns `0`.
31 | `is_lower(string)` | Returns `1` if every cased character[^2] in `string` is lowercase,
otherwise returns `0`.
32 | `is_octal(string)` | Returns `1` if every character in `string` is an octal digit,
i.e. is contained in the string `OCTDIGITS`, otherwise returns `0`.
33 | `is_title(string)` | Returns `1` if `string` is in title case,
i.e. matches the output of `title(string)` exactly.
34 | `is_upper(string)` | Returns `1` if every cased character[^2] in `string` is uppercase,
otherwise returns `0`.
35 | `is_wrapped(string, chars)` | Returns `1` if `string` both starts and ends with
the substring `chars`, otherwise returns `0`.
36 | `join(iterable[, delimiter])` | Returns a string with each consecutive member
of `iterable` converted to a string
and joined with `delimiter` between them.
If `delimiter` is not specified, it defaults to `" "`.
37 | `leftpad(string, length[, char])` | Returns a copy of `string` padded on the left so that
it's `length` characters long, using `char` for padding.
If `char` is not specified, it defaults to `" "`.
If `length` is shorter than `string`'s length,
a copy of `string` is returned.
38 | `ordinal(n)` | Returns an ordinal numeral of a number,
e.g. `ordinal(/)` returns `"1st"`.
39 | `replace(string, replacement[, count])` | Returns a copy of `string`, with all instances of each key
in the `replacement` table replaced with its corresponding value.
If `count` is specified, only the first `count` instances of each key
will be replaced, starting from the left.
40 | `rightpad(string, length[, char])` | Returns a copy of `string` padded on the right so that
it's `length` characters long, using `char` for padding.
If `char` is not specified, it defaults to `" "`.
If `length` is shorter than `string`'s length,
a copy of `string` is returned.
41 | `split(string[, separator])` | Returns an array of the words in `string`,
separated by `separator`.
If `separator` is not specified, it defaults to `" "`.
If `separator` is an Array, all of its elements will be used as separators.
42 | `split_lines(string)` | Returns an array of the lines in the string,
breaking at line boundaries.
Line breaks are not included in the resulting array.
43 | `starts_with(string, prefix)` | Returns `1` if `string` starts with the substring `prefix`,
otherwise returns `0`.
44 | `strip(string, chars)` | Returns a copy of `string` with `chars` removed from
both the beginning and the end,
as in `strip_left` and `strip_right`.
45 | `strip_left(string, prefix)` | Returns a copy of `string` with `prefix` removed
from the beginning,
multiple times if `string` still begins with `prefix`.
If `string` doesn't begin with `prefix`,
a copy of the original `string` is returned.
46 | `strip_right(string, suffix)` | Returns a copy of `string` with `suffix` removed from the end,
multiple times if `string` still ends with `suffix`.
If `string` doesn't end with `suffix`,
a copy of the original `string` is returned.
47 | `swapcase(string)` | Returns a copy of `string` with every cased character[^2]
set to the opposite of its original case.
48 | `title(string)` | Returns a copy of `string` with the first character of each word
(separated by spaces) set to uppercase (assuming they're
cased characters[^2]), and all subsequent characters
of each word set to lowercase.
49 | `to_lower(string)` | Returns a copy of `string` with every
cased character[^2] set to lowercase.
50 | `to_upper(string)` | Returns a copy of `string` with every
cased character[^2] set to uppercase.
51 | `wrap(string, wrapper)` | Returns a copy of `string` with
`wrapper` added to the start and end.
52 |
53 |
54 |
55 | [^1]: An argument in `[square brackets]` means that it has a default value,
56 | and so it isn't necessary to give it a value.
57 |
58 | [^2]: Cased characters are alphabetic characters in either uppercase
59 | or lowercase; `LETTERS` is a string of all cased characters.
60 |
--------------------------------------------------------------------------------
/docs/stdtypes.md:
--------------------------------------------------------------------------------
1 | # `types` module
2 |
3 | The `types` module implements native data type aliases and data types not
4 | built-in to the language itself.
5 |
6 |
7 | ## Additional types
8 |
9 |
10 | ### Boolean
11 |
12 | The Boolean type can have value either `true` or `false`.
13 |
14 | Booleans can be initialized with either a string with the value `"true"` or
15 | `"false"`, or a truthy/falsy value. Booleans support several operations: all
16 | comparison operations, all arithmetic operations, all bitwise operations, and
17 | can be used with logical operators, and converted to a string (specifically
18 | either `"true"` or `"false"`).
19 |
20 |
21 | ### Frozen
22 |
23 | `Frozen` is a wrapper type designed to allow mutable types like Arrays and
24 | Tables to be used as keys in a Table. `Frozen` is immutable, meaning that once a
25 | value has been wrapped in a Frozen, it cannot be changed.
26 |
27 | ```sm
28 | test_key key * {
29 | ?? { {{key -> <-math.sum(key)}}!; }
30 | !! { "invalid key:", key!; }
31 | }
32 |
33 | test_key([//, /\\]); == invalid key: [3, 4]
34 | test_key(<-types.Frozen([//, /\\])); == {{Frozen([3, 4]) -> 7}}
35 | ```
36 |
37 |
38 |
39 | ### UUID4
40 |
41 | Generates a random UUID, version 4.
42 | Returns a UUID object with attributes `hex` and `dec`:
43 |
44 | ```sm
45 | <=types.UUID4;
46 |
47 | uuid: UUID4();
48 | uuid!;
49 | == 8b46f521-b821-4010-ae8f-9ae9522d9889
50 |
51 | uuid.hex!;
52 | == "8b46f521b8214010ae8f9ae9522d9889"
53 |
54 | uuid.dec!;
55 | == 185131124056068440795959350641466120329
56 | ```
57 |
58 |
59 | ## Built-in type aliases
60 |
61 |
62 | ### Array
63 |
64 | The `Array` type alias is defined to be equal to `[]?!`.
65 |
66 | Calling `Array()` with no arguments will return an empty array.
67 |
68 | A copy of an array `a` can be made by using `Array(a)`.
69 |
70 | ```sm
71 | a: [/, /\, //];
72 | b: a;
73 | c: Array(a);
74 | d: a<<>>;
75 |
76 | a<\>>: /\\;
77 | a, b, c, d!;
78 | ```
79 |
80 | Arrays can also be constructed from strings and tables:
81 |
82 | > `Array("ball")` is equivalent to `["b", "a", "l", "l"]`
83 | > `Array({{// -> /\\, "X" -> "D" }})` is equivalent to `[[//, /\\], ["X", "D"]]`
84 |
85 |
86 | ### Number
87 |
88 | The `Number` type alias is defined to be equal to `\?!`.
89 |
90 | Caling `Number()` with no arguments will return the number `0`.
91 |
92 | Numbers can be constructed from strings, including
93 | binary, octal, and hexadecimal representations:
94 |
95 | > `Number("1000")` will return `1000`
96 | > `Number("b:1000")` will return `8`
97 | > `Number("o:1000")` will return `512`
98 | > `Number("x:1000")` will return `4096`
99 |
100 | Floats can also be supplied:
101 | > `Number("3.14")` will return `3.14`
102 | > `Number("x:3.23d70a3d70a3e")` will return `3.14`
103 | > `Number("o:23.6560507534121727")` will return `19.84`
104 |
105 | Scientific notation is also supported, with `significand[e]exponent` for bases
106 | 2, 8 and 10, and `significand[p]exponent` for base 16:
107 | > `Number("b:0.11e11")` will return `6`
108 | > `Number("o:5e7")` will return `10485760`
109 | > `Number("1e-3")` will return `0.001`
110 | > `Number("x:0.2p7")` will return `16`
111 |
112 |
113 | ### Null
114 |
115 | The `Null` type alias is defined to be equal to `(||)?!`. It can be used for
116 | explicit null values instead of relying on implicit null triggers.
117 |
118 |
119 | ### Slice
120 |
121 | The `Slice` type alias is defined to be equal to `<<>>?!`.
122 |
123 | Different slices can be constructed by using integers and nulls:
124 |
125 | - `Slice(/, /\, //)` is equivalent to `<>`
126 | - `Slice(, , -/)` is equivalent to `<<....-/>>`
127 | - `Slice(/, , ,)` is equivalent to `<>`
128 | - `Slice(, /\/, ,)` is equivalent to `<<../\/>>`
129 |
130 |
131 | ### String
132 |
133 | The `String` type alias is defined to be equal to `""?!`.
134 |
135 | Calling `String()` with no arguments will return an empty string.
136 |
137 | Any data type can be converted to a string.
138 |
139 | A copy of a string `s` can be made by using `String(s)`.
140 |
141 |
142 | ### Table
143 |
144 | The `Table` type alias is defined to be equal to `{{}}?!`.
145 |
146 | Calling `Table()` with no arguments will return an empty table.
147 |
148 | Tables can be constructed from arrays containing 2-element iterables:
149 | `Table([[//, /\\/], "XD"])` is equivalent to `{{// -> /\\/, "X" -> "D"}}`
150 |
151 | ### Zip
152 |
153 | The `Zip` type alias is defined to be equal to `("" >< "")?!`.
154 |
155 | Calling `Zip()` with no arguments will return an empty iterator.
156 | Passing in only 1 argument is equivalent to `... i ->? iter { ** [i]; }`.
157 | Calling `Zip()` with 2 or more arguments is equivalent to using the `><`
158 | operator on them.
--------------------------------------------------------------------------------
/docs/strings.md:
--------------------------------------------------------------------------------
1 | # Strings
2 |
3 | Strings are defined using double quotation marks:
4 |
5 | ```sm
6 | str: "Hello!";
7 | ```
8 |
9 | Multiline strings do not require any additional syntax:
10 |
11 | ```sm
12 | "This
13 | is a
14 | multiline
15 | string"
16 | ```
17 |
18 |
19 | ## Basic Operations
20 |
21 | Strings can be manipulated using some arithmetic operators:
22 |
23 | > `"hello" + "world"` is the same as `"helloworld"`
24 |
25 | > `"hello" ++ //` (or `// ++ "hello"`) is the same as `"hellohellohello"`
26 | > ``"ball" ++ `/`` is the same as `"ba"`
27 | > `"woah!" ++ <-math.PI` is the same as `"woah!woah!woah!w"`
28 | > `"hello"++` is the same as `"hello" ++ /\`
29 |
30 | > `-"hello"` is the same as `"olleh"`
31 |
32 | > `"hello" - "l"` is the same as `"helo"` (the 2nd operand removes the first
33 | > instance of itself from the 1st operand)
34 |
35 | > `"hello" -- "l"` is the same as `"heo"` (the 2nd operand removes all
36 | > occurences of itself from the 1st operand)
37 |
38 | !!! warning
39 | Multiplying strings by negative numbers is undefined, as it is unclear
40 | whether `s ++ -x` means `-(s ++ x)` or `(-s) ++ x`. These are equivalent for
41 | integers, but they produce different results for other numbers:
42 |
43 | - ``-("hello" ++ /`/)`` is `"eholleh"`
44 | - ``(-"hello") ++ /`/`` is `"ollehol"`
45 |
46 |
47 | ## Formatting
48 |
49 | Strings can be formatted using the `---` operator.
50 | The 2nd operand can be a String, Array, or a Table.
51 | ```sm
52 | "Hi $0!" --- "Bob"!;
53 | == Hi Bob!
54 |
55 | "$0$1$0" --- ["abra", "cad"]!;
56 | abracadabra
57 |
58 | s: "Coordinates: $lat, $long";
59 | coords: {{
60 | "lat" -> "56.37N",
61 | "long" -> "-8.34W"
62 | }};
63 | s --- coords!;
64 | == Coordinates: 56.37N, -8.34W
65 | ```
66 |
67 |
68 | ## Casting
69 |
70 | Strings can be cast to integers (for single characters) or Arrays of integers
71 | (for longer strings), representing the Unicode code point of each character:
72 |
73 | > `"a"%` returns `97`
74 | > `"hi!"%` returns `[104, 105, 33]`
75 |
76 |
77 | ## Shifting
78 |
79 | Strings can have their characters shifted by adding/subtracting numbers.
80 | For example, `"hi" + /` will result in the string `"ij"`, where each character
81 | in the string has been shifted one position ahead. Similarly, `"hi" - /` will
82 | result in the string `"gh"`, where each character in the string has been shifted
83 | one position backwards.
84 |
85 | > `"hi"+` is the same as `"hi" + /`
86 | > `"hi"-` is the same as `"hi" - /`
87 |
--------------------------------------------------------------------------------
/docs/tables.md:
--------------------------------------------------------------------------------
1 | # Tables
2 |
3 | Tables map hashable values to arbitrary objects.
4 | They are defined using double curly brackets, with `->` mapping each key to
5 | each value:
6 |
7 | ```sm
8 | tab: {{"key" -> "value", / -> [/\, "a"]}};
9 | ```
10 |
11 | A table may be indexed by its keys, which will return their corresponding
12 | values, for example, from the previous table:
13 |
14 | > `tab<<"key">>` returns `"value"`
15 | > tab<</>>
returns `[2, "a"]`
16 |
17 | After its initialization, the items of a table can be set using this indexing
18 | syntax. If the key doesn't already exist in the table, it will be created.
19 |
20 | > `tab<<"key">>: "newvalue"` will overwrite the previous value of `tab<<"key">>`.
21 |
22 | > `tab<<"newkey">>: //\` will create a new item in the table, with key `"newkey"`
23 | and value `6`.
24 |
25 | Tables can be merged together with the addition operator `+`. The values of the
26 | table to the right of the operator take priority when both tables share keys.
27 |
28 | Items can be removed from a table by key using the subtraction operator `-`:
29 |
30 | > `{{"a" -> /, "b" -> /\, // -> /\//}} - "b"` gives `{{"a" -> /, // -> /\//}}`
31 |
32 | Tables can be inverted using the unary `~` operator:
33 | > `~{{"a" -> /, "b" -> /\}}` gives `{{/ -> "a", /\ -> "b"}}`
34 |
--------------------------------------------------------------------------------
/docs/tools.md:
--------------------------------------------------------------------------------
1 | # Shebang
2 |
3 | You can easily use your Samarium scripts on Unix by putting an appropriate
4 | shebang line at the top of your program, for instance
5 | ```sm
6 | #!/usr/bin/env samarium
7 |
8 | "Hi!"!;
9 | ```
10 | and making it executable, e.g. with
11 | ```bash
12 | $ chmod +x script
13 | ```
14 |
15 |
16 | # Samarium REPL
17 |
18 | If you run the `samarium` command without any arguments,
19 | you'll launch the REPL, an interactive shell that will read
20 | and evaluate any Samarium code you enter.
21 | ```txt
22 | $ samarium
23 | Samarium 0.6.2
24 | -->
25 | ```
26 | Interacting with the REPL is a nice way to experiment with Samarium:
27 | ```txt
28 | --> / + /\
29 | 3
30 | --> 1: "ball"##!
31 | 1083481267058749873
32 | --> 1??
33 | 443527852557841359
34 | --> 1??
35 | 894622914084910886
36 | ```
37 | The REPL also supports compound statements:
38 | ```txt
39 | --> x: /\/\??
40 | --> ? x --- /\ :: \ {
41 | > x -- /\!;
42 | > } ,, {
43 | > 3 ++ x + /!;
44 | > }
45 | 4
46 | --> x
47 | 8
48 | ```
49 |
50 | ## Commands
51 |
52 | Samarium 0.6.0 introduced commands to improve your REPL experience.
53 | Commands are prefixed with a colon, use `:?` to see the list of all commands.
54 |
55 |
56 | ## `clear`
57 | Clears the screen.
58 |
59 |
60 | ## `color [color|save]`
61 | Changes the prompt color. The available colors are [Dahlia codes] from `0` to
62 | `e`, as well as their English names. Providing no argument will reset the
63 | color. `:color save` will save the current color to the REPL config, making the
64 | change permanent.
65 |
66 | The English names can be checked by using the `:? color` command:
67 | ```
68 | --> :? color
69 | color [color]
70 | providing no color will reset it to the default one
71 |
72 | use :color save to save the current color to your config
73 |
74 | 0|black
75 | 1|blue
76 | 2|green
77 | 3|cyan
78 | 4|red
79 | 5|purple
80 | 6|orange
81 | 7|light gray
82 | 8|gray
83 | 9|light blue
84 | a|lime
85 | b|aquamarine
86 | c|light red
87 | d|pink
88 | e|yellow
89 | ```
90 |
91 |
92 | ## `debug`
93 | Toggles debug mode which shows the intermediary Python code that the Samarium
94 | input is transpiled to before executing it (equivalent to using the
95 | `samarium-debug` command).
96 | ```
97 | --> /
98 | 1
99 | --> :debug
100 | --> /
101 | Num(1)
102 | 1
103 | ```
104 |
105 |
106 | ## `exit`
107 | > Aliases: `q`, `quit`
108 |
109 | Exits the REPL and saves the session if [autosave](#autosave-truefalse) is
110 | enabled. Appending a `!` or just using it as a command will "force quit" the
111 | REPL without saving the session.
112 | ```
113 | $ ls -1 ~/.cache/samarium | wc -l
114 | 7
115 | $ samarium
116 | Samarium 0.6.2
117 | --> :session autosave
118 | Autosave is enabled
119 | --> :!
120 | $ ls -1 ~/.cache/samarium | wc -l
121 | 7
122 | ```
123 |
124 |
125 | ## `help [section]`
126 | > Aliases: `?`, `h`
127 |
128 | Shows the help message.
129 | ```
130 | --> :help
131 | ?|h|help shows this message
132 | exit|q|quit saves the session and quits the repl
133 | !|exit!|q!|quit! force quits the repl
134 | session manages sessions, see :? session for details
135 | clear clears the screen
136 | color changes the prompt color, see :? color for details
137 | debug toggles debug mode
138 | restore restores the previous session
139 | t|time times the execution of the following statement
140 | undo undoes the last statement
141 | ```
142 |
143 |
144 | ## `time `
145 | > Aliases: `t`
146 |
147 | Runs a given piece of code and shows how much time it took to execute.
148 | ```
149 | --> :t <-math.is_prime(/?!("2137"))
150 | 1
151 | 0.009 seconds
152 | ```
153 |
154 |
155 | ## `undo`
156 | Undoes the last statement.
157 | ```
158 | --> x: /\/\!
159 | 10
160 | --> x+++:
161 | --> x
162 | 100
163 | --> :undo
164 | --> x
165 | 10
166 | ```
167 | This is done by rerunning all inputs except for the last one (simple value
168 | lookups like `x` on lines 4 and 7 are excluded) and silencing all writes to
169 | stdout/stderr.
170 |
171 |
172 | ## Sessions
173 |
174 | Sessions provide a convenient way to save and restore your REPL state, allowing
175 | you to pick up right where you left off.
176 |
177 |
178 | ## `autosave [true|false]`
179 | Specifies whether sessions should be automatically saved on exit. Providing no
180 | argument will display the current value.
181 | ```
182 | --> :session autosave false
183 | Autosave disabled
184 | --> :session autosave
185 | Autosave disabled
186 | ```
187 |
188 |
189 | ## `delete-all`
190 | Deletes all saved sessions (both named and unnamed).
191 | ```
192 | --> :session delete-all
193 | Are you sure you want to delete all 27 sessions? (Y/n)
194 | Removed 1.7KB of session files
195 | ```
196 |
197 |
198 | ## `lifetime [time]`
199 | Specifies the lifetime (in days) for unnamed sessions. Sessions that are too old
200 | get removed on the next launch of the REPL. Providing no argument will display
201 | the current value.
202 | ```
203 | --> :session lifetime
204 | Current session lifetime is 30 days
205 | --> :session lifetime 20
206 | Updated session lifetime to 20 days
207 | ```
208 |
209 |
210 | ## `list`
211 | Displays a list of saved sessions and their sizes.
212 | ```
213 | --> :session list
214 | 20230702193813.json (57.0B)
215 | 20230702163714.json (57.0B)
216 | 20230702194010.json (57.0B)
217 | 20230702163535.json (64.0B)
218 | 20230702193747.json (57.0B)
219 | 20230702210821.json (51.0B)
220 | 20230702170234.json (57.0B)
221 | prime-sieve.json (2.7KB)
222 | 20230702171025.json (57.0B)
223 | 20230702193958.json (57.0B)
224 | 20230702171009.json (57.0B)
225 | 20230702193902.json (57.0B)
226 | 20230702192942.json (57.0B)
227 | ----------------------------------------------
228 | Total 3.3KB
229 | ```
230 |
231 |
232 | ## `load `
233 | Loads a given session.
234 | ```
235 | --> :session load sorting
236 | --> quick_sort([/////?? ... _ ->? <<../\/\\>>])
237 | [1, 2, 2, 3, 3, 4, 5, 11, 13, 14, 17, 18, 20, 20, 21, 21, 23, 24, 26, 29]
238 | ```
239 |
240 |
241 | ## `restore`
242 | Loads the most recent unnamed session.
243 | ```
244 | --> :session autosave
245 | Autosave enabled
246 | --> f * { ("!"++(///+++))! }
247 | --> :q
248 | $ samarium
249 | Samarium 0.6.2
250 | --> :session restore
251 | --> f()
252 | !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
253 | ```
254 |
255 |
256 | ## `save [name]`
257 | Saves a sesssion under a given name. Becomes an unnamed session if no name is
258 | supplied. Session names can consist of English letters, digits, hyphens, and
259 | underscores.
260 | ```
261 | --> pi * { * <-math.TAU--; }
262 | --> :session save pi
263 | --> :q
264 | $ samarium
265 | Samarium 0.6.2
266 | --> :session load pi
267 | --> pi()
268 | 3.141592653589793
269 | ```
270 |
271 |
272 | [Dahlia codes]: https://github.com/dahlia-lib/spec/blob/main/SPECIFICATION.md#standard-formatting
--------------------------------------------------------------------------------
/docs/variables.md:
--------------------------------------------------------------------------------
1 | # Variables
2 |
3 | Variables are defined using the assignment operator `:`, like so:
4 | ```sm
5 | my_var: /;
6 | ```
7 | Variables can have many types, such as numbers, strings, arrays, tables,
8 | slices, or null. Functions and classes may also be treated as first-class
9 | variables. Only letters, numbers, and underscores can be used
10 | for variable names (case sensitive).
11 |
12 | !!! note
13 | Samarium follows the same naming convention as Python, i.e.:
14 | — snake_case for variables and functions
15 | — PascalCase for classes and type aliases
16 | — flatcase for modules
17 |
18 | Variables can be made private by prefixing the name with `#`, making them
19 | inaccessible to external modules. Private variable names don't collide with
20 | public variable names:
21 | ```sm
22 | var: -/;
23 | #var: /;
24 |
25 | var!; == -1
26 | #var!; == 1
27 | ```
28 |
29 |
30 | ## Parallel assignment
31 |
32 | Multiple variables can be set at once by separating them with a comma:
33 | ```sm
34 | a, b: /, //;
35 | == same as
36 | a: /;
37 | b: //;
38 |
39 |
40 | primes: [/\, //, /\/, ///, /\//];
41 |
42 | first, **rest, last: primes;
43 | == ^ collect as many values as possible
44 | == same as
45 | first: primes<<\>>;
46 | rest: primes<>;
47 | last: primes<<-/>>;
48 | ```
--------------------------------------------------------------------------------
/docs/zip.md:
--------------------------------------------------------------------------------
1 | # Zipping
2 |
3 | Samarium's zip operator `><` allow for easy zipping, that is, iterating through
4 | multiple iterables at once:
5 | ```sm
6 | names: ["Alice", "Bob", "Charlie"];
7 | ages: [/\\\\, /\\\/, ////];
8 |
9 | ... i ->? names >< ages {
10 | "$0 is $1 years old" --- i!;
11 | }
12 | ```
13 | ```
14 | Alice is 16 years old
15 | Bob is 17 years old
16 | Charlie is 15 years old
17 | ```
18 |
19 | Enumerating iterables can be simulated by using an empty slice object:
20 | ```sm
21 | x: ["Alpha", "Beta", "Gamma"];
22 |
23 | ... i, v ->? <<>> >< x {
24 | "x<<$0>> :: $1" --- [i, v]!;
25 | }
26 | ```
27 | ```
28 | x<<0>> :: Alpha
29 | x<<1>> :: Beta
30 | x<<2>> :: Gamma
31 | ```
32 |
33 |
34 | ```sm
35 | <=string.ordinal;
36 |
37 | winners: ["Jake", "Clarisse", "Matt"];
38 |
39 | ... p, w ->? <> >< winners {
40 | "$0 place: $1" --- [ordinal(p), w];
41 | }
42 | ```
43 | ```
44 | 1st place: Jake
45 | 2nd place: Clarisse
46 | 3rd place: Matt
47 | ```
48 |
--------------------------------------------------------------------------------
/justfile:
--------------------------------------------------------------------------------
1 | [private]
2 | default:
3 | @just --list
4 |
5 | set positional-arguments
6 |
7 | fmt:
8 | poetry run ruff check --select=I --fix
9 | poetry run ruff format
10 |
11 | ruff:
12 | poetry run ruff check
13 | poetry run ruff format --check
14 |
15 | mypy:
16 | poetry run mypy samarium
17 |
18 | @run *args:
19 | poetry run samarium $@
20 |
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: Samarium Documentation
2 | copyright: Copyright © 2021–2024 trag1c
3 | repo_url: https://github.com/samarium-lang/Samarium
4 | repo_name: samarium-lang/Samarium
5 | nav:
6 | - Home: index.md
7 | - Examples: examples.md
8 | - Language Guide:
9 | - Numbers: numbers.md
10 | - Variables: variables.md
11 | - Operators: operators.md
12 | - Strings: strings.md
13 | - Comments: comments.md
14 | - Arrays: arrays.md
15 | - Tables: tables.md
16 | - Built-in Functions: builtins.md
17 | - Control Flow: controlflow.md
18 | - Null: null.md
19 | - Slices: slices.md
20 | - Zipping: zip.md
21 | - Enums: enums.md
22 | - Functions: functions.md
23 | - Modules: modules.md
24 | - Classes: classes.md
25 | - File I/O: fileio.md
26 | - Python Interop: interop.md
27 | - Standard Library:
28 | - collections module: stdcollections.md
29 | - datetime module: stddatetime.md
30 | - io module: stdio.md
31 | - iter module: stditer.md
32 | - math module: stdmath.md
33 | - operator module: stdoperator.md
34 | - random module: stdrandom.md
35 | - string module: stdstring.md
36 | - types module: stdtypes.md
37 | - Tools: tools.md
38 | theme:
39 | name: material
40 | logo: assets/icon-white.png
41 | favicon: assets/icon-black.png
42 | palette:
43 | - media: "(prefers-color-scheme: light)"
44 | scheme: default
45 | primary: black
46 | toggle:
47 | icon: material/toggle-switch
48 | name: Switch to dark mode
49 | - media: "(prefers-color-scheme: dark)"
50 | scheme: slate
51 | primary: black
52 | toggle:
53 | icon: material/toggle-switch-off-outline
54 | name: Switch to light mode
55 | extra_javascript:
56 | - https://unpkg.com/shiki@0.10.1/dist/index.unpkg.iife.js
57 | - assets/script.js
58 | extra_css:
59 | - assets/style.css
60 | markdown_extensions:
61 | - admonition
62 | - pymdownx.superfences:
63 | custom_fences:
64 | - name: sm
65 | class: sm
66 | - footnotes
67 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "samarium"
3 | version = "0.6.2"
4 | description = "The Samarium Programming Language"
5 | authors = ["trag1c "]
6 | license = "MIT"
7 | documentation = "https://samarium-lang.github.io/Samarium/"
8 | repository = "https://github.com/samarium-lang/Samarium"
9 | readme = "README.md"
10 |
11 | [tool.poetry.dependencies]
12 | python = "^3.9"
13 | dahlia = "^3.0.0"
14 | crossandra = "^2.2.1"
15 |
16 | [tool.poetry.group.dev.dependencies]
17 | ruff = "^0.4.8"
18 | mkdocs = "^1.5.1"
19 | mkdocs-material = "^9.1.21"
20 | mypy = "^1.10.0"
21 | mike = "^2.1.1"
22 |
23 | [tool.ruff.lint]
24 | select = ["ALL"]
25 | ignore = ["COM", "D", "DTZ", "FIX", "S", "SLF", "ANN1", "ANN401", "C90", "ISC001", "T201", "TD003", "PLR2004", "PLR091", "PERF203", "PYI034"]
26 |
27 | [tool.ruff.lint.pylint]
28 | max-returns = 8
29 |
30 | [tool.mypy]
31 | disable_error_code = "attr-defined, override"
32 |
33 | [tool.poetry.scripts]
34 | samarium = "samarium.__main__:main"
35 | samarium-debug = "samarium.__init__:main_debug"
36 |
37 | [build-system]
38 | requires = ["poetry-core>=1.0.0"]
39 | build-backend = "poetry.core.masonry.api"
40 |
41 |
--------------------------------------------------------------------------------
/samarium/__init__.py:
--------------------------------------------------------------------------------
1 | import sys
2 | from contextlib import nullcontext, suppress
3 | from pathlib import Path
4 |
5 | from samarium.core import run
6 | from samarium.exceptions import DAHLIA
7 | from samarium.repl import REPL, Command
8 | from samarium.transpiler import Registry
9 | from samarium.utils import __version__
10 |
11 | OPTIONS = ("-v", "--version", "-c", "--command", "-h", "--help")
12 |
13 | HELP = """samarium &7[option] [-c cmd | file]&R
14 | options and arguments:\n""" + "\n".join(
15 | map(
16 | str,
17 | (
18 | Command(
19 | "-c",
20 | "--command",
21 | arg="",
22 | sep=", ",
23 | msg="reads program from string",
24 | ),
25 | Command("-h", "--help", sep=", ", msg="shows this message"),
26 | Command("-v", "--version", sep=", ", msg="prints Samarium version"),
27 | Command(arg="file", sep=", ", msg=" reads program from script file"),
28 | ),
29 | )
30 | )
31 |
32 |
33 | def main(*, debug: bool = False) -> None:
34 | reg = Registry(globals())
35 |
36 | if len(sys.argv) == 1:
37 | return REPL(debug=debug).run()
38 |
39 | if (arg := sys.argv[1]) in OPTIONS:
40 | if arg in OPTIONS[:2]:
41 | print(f"Samarium {__version__}")
42 | elif arg in OPTIONS[2:4]:
43 | if len(sys.argv) > 2:
44 | run(sys.argv[2] + " !", reg, arg, debug=debug)
45 | DAHLIA.print("&4missing code to execute", file=sys.stderr)
46 | elif arg in OPTIONS[4:]:
47 | DAHLIA.print(HELP)
48 | sys.exit()
49 |
50 | try:
51 | file = Path(arg).read_text()
52 | except OSError:
53 | DAHLIA.print(f"&4file not found: {arg}", file=sys.stderr)
54 | else:
55 | with nullcontext() if debug else suppress(Exception, KeyboardInterrupt):
56 | file = "\n".join(file.splitlines()[file.startswith("#!") :])
57 | run(file, reg, arg, debug=debug)
58 |
59 |
60 | def main_debug() -> None:
61 | main(debug=True)
62 |
--------------------------------------------------------------------------------
/samarium/__main__.py:
--------------------------------------------------------------------------------
1 | from samarium import main
2 |
3 | if __name__ == "__main__":
4 | main()
5 |
--------------------------------------------------------------------------------
/samarium/builtins.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from datetime import datetime, timezone
4 | from re import compile
5 | from time import sleep as _sleep
6 | from time import time_ns
7 | from types import GeneratorType
8 | from typing import TYPE_CHECKING, Any, TypeVar
9 |
10 | from samarium.classes import (
11 | MISSING,
12 | NULL,
13 | Array,
14 | Attrs,
15 | Null,
16 | Num,
17 | Number,
18 | Slice,
19 | String,
20 | correct_type,
21 | )
22 | from samarium.exceptions import (
23 | SamariumError,
24 | SamariumIOError,
25 | SamariumSyntaxError,
26 | SamariumTypeError,
27 | )
28 |
29 | if TYPE_CHECKING:
30 | from collections.abc import Callable
31 |
32 | MISSING_ARGS_PATTERN = compile(
33 | r"\w+\(\) takes exactly one argument \(0 given\)"
34 | r"|\w+\(\) missing (\d+) required positional argument"
35 | )
36 |
37 | NULL_STRING = String()
38 |
39 | T = TypeVar("T")
40 |
41 | TOO_MANY_ARGS_PATTERN = compile(
42 | r"\w+\(\) takes (\d+) positional arguments? but (\d+) (?:was|were) given"
43 | )
44 |
45 |
46 | def dtnow() -> Array:
47 | utcnow = datetime.now(tz=timezone.utc)
48 | now = datetime.now().timetuple()
49 | utcnow_tt = utcnow.timetuple()
50 | tz = now[3] - utcnow_tt[3], now[4] - utcnow_tt[4]
51 | utcnow_tpl = utcnow_tt[:-3] + (utcnow.microsecond // 1000,) + tz
52 | return Array(map(Num, utcnow_tpl))
53 |
54 |
55 | def mkslice(start: Any = None, stop: Any = MISSING, step: Any = None) -> Any:
56 | if stop is MISSING:
57 | if start is not None:
58 | return start
59 | return Slice(NULL)
60 | start = NULL if start is None else start
61 | stop = NULL if stop is None else stop
62 | step = NULL if step is None else step
63 | return Slice(start, stop, step)
64 |
65 |
66 | def print_safe(*args: Attrs | Callable[..., Any] | bool | None) -> Attrs:
67 | typechecked_args = list(map(correct_type, args))
68 | return_args = typechecked_args.copy()
69 | strs = list(map(str, typechecked_args))
70 | types = list(map(type, typechecked_args))
71 | if tuple in types:
72 | msg = "missing brackets"
73 | raise SamariumSyntaxError(msg)
74 | if GeneratorType in types:
75 | msg = "invalid comprehension"
76 | raise SamariumSyntaxError(msg)
77 | print(*strs)
78 | if len(return_args) > 1:
79 | return Array(return_args)
80 | if not return_args or types[0] is Null:
81 | return NULL
82 | return return_args[0] # type: ignore[return-value]
83 |
84 |
85 | def readline(prompt: String = NULL_STRING) -> String:
86 | try:
87 | return String(input(prompt.val))
88 | except KeyboardInterrupt:
89 | msg = "^C"
90 | except EOFError:
91 | msg = "^D"
92 | raise SamariumIOError(msg)
93 |
94 |
95 | def sleep(*args: Number) -> None:
96 | if not args:
97 | msg = "no argument provided for ,.,"
98 | raise SamariumTypeError(msg)
99 | if len(args) > 1:
100 | msg = ",., only takes one argument"
101 | raise SamariumTypeError(msg)
102 | if not isinstance((time := args[0]), Number):
103 | msg = ",., only accepts integers"
104 | raise SamariumTypeError(msg)
105 | _sleep(time.val / 1000)
106 |
107 |
108 | def t(obj: T | None = None) -> T | None:
109 | return obj
110 |
111 |
112 | def throw(obj: String | Array[Any] = NULL_STRING) -> None:
113 | note = ""
114 | if isinstance(obj, Array):
115 | if len(obj.val) != 2:
116 | msg = "array must be of form [error_msg, note]"
117 | raise SamariumTypeError(msg)
118 | error_msg, note = obj.val
119 | if not isinstance(error_msg, String):
120 | msg = "error message must be a string"
121 | raise SamariumTypeError(msg)
122 | if not isinstance(note, String):
123 | msg = "error note must be a string"
124 | raise SamariumTypeError(msg)
125 | note = f"\n&1[Note] {note.val}"
126 | elif isinstance(obj, String):
127 | error_msg = obj
128 | else:
129 | msg = "throw argument must be a string or a 2-element array"
130 | raise SamariumTypeError(msg)
131 | raise SamariumError(error_msg.val + note)
132 |
133 |
134 | def timestamp() -> Number:
135 | return Num(time_ns() // 1_000_000)
136 |
--------------------------------------------------------------------------------
/samarium/classes/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from samarium.classes.base import (
4 | MISSING,
5 | NEXT,
6 | NULL,
7 | Array,
8 | Attrs,
9 | Dataclass,
10 | Enum,
11 | Function,
12 | Iterator,
13 | Module,
14 | Null,
15 | Num,
16 | Number,
17 | Slice,
18 | String,
19 | Table,
20 | Type,
21 | UserAttrs,
22 | Zip,
23 | correct_type,
24 | )
25 | from samarium.classes.fileio import File, FileManager, Mode
26 |
27 | __all__ = (
28 | "Array",
29 | "Attrs",
30 | "Dataclass",
31 | "Enum",
32 | "File",
33 | "Function",
34 | "Iterator",
35 | "Slice",
36 | "Table",
37 | "Number",
38 | "Num",
39 | "String",
40 | "Module",
41 | "Type",
42 | "MISSING",
43 | "NEXT",
44 | "NULL",
45 | "Null",
46 | "FileManager",
47 | "Mode",
48 | "UserAttrs",
49 | "Zip",
50 | "correct_type",
51 | )
52 |
--------------------------------------------------------------------------------
/samarium/classes/fileio.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from enum import Enum
4 | from os import write
5 | from pathlib import Path
6 | from typing import IO, TYPE_CHECKING, Any, cast
7 |
8 | from samarium.classes.base import NULL, Array, Attrs, Null, Num, Number, Slice, String
9 | from samarium.exceptions import SamariumIOError, SamariumTypeError, SamariumValueError
10 | from samarium.utils import get_type_name
11 |
12 | if TYPE_CHECKING:
13 | from collections.abc import Iterator
14 |
15 |
16 | class Mode(Enum):
17 | READ = "r"
18 | WRITE = "w"
19 | READ_WRITE = "r+"
20 | APPEND = "a"
21 |
22 |
23 | class FileManager:
24 | @staticmethod
25 | def create(path: String) -> Null:
26 | Path(path.val).touch()
27 | return NULL
28 |
29 | @staticmethod
30 | def open(path: String | Number, mode: Mode, *, binary: bool = False) -> File:
31 | if isinstance(path, Number):
32 | if mode is Mode.READ_WRITE:
33 | msg = "cannot open a standard stream in a read & write mode"
34 | raise SamariumIOError(msg)
35 | if not path.is_int:
36 | msg = "cannot use non-integers"
37 | raise SamariumValueError(msg)
38 | pth = cast("str | int", path.val)
39 | if isinstance(pth, str):
40 | f = Path(pth).open(mode.value + "b" * binary) # noqa: SIM115
41 | else:
42 | f = open(pth, mode.value + "b" * binary) # noqa: PTH123, SIM115
43 | return File(f, mode.name, pth, binary=binary)
44 |
45 | @staticmethod
46 | def open_binary(path: String, mode: Mode) -> File:
47 | return FileManager.open(path, mode, binary=True)
48 |
49 | @staticmethod
50 | def quick(
51 | path: String | File | Number,
52 | mode: Mode,
53 | *,
54 | data: String | Array | None = None,
55 | binary: bool = False,
56 | ) -> String | Array | Null:
57 | if isinstance(path, String):
58 | p = Path(path.val)
59 | if mode is Mode.READ:
60 | content = p.read_bytes() if binary else p.read_text()
61 | return Array(map(Num, content)) if binary else String(content)
62 | if data is None:
63 | msg = "missing data"
64 | raise SamariumIOError(msg)
65 | if not isinstance(data, Array):
66 | p.write_text(data.val)
67 | return NULL
68 | if isinstance(data, Array):
69 | bytes_ = b""
70 | for i in data.val:
71 | try:
72 | bytes_ += i.val.to_bytes(1, "big")
73 | except AssertionError: # TODO(trag1c): why AssertionError?
74 | msg = "some items in the array are not of type Integer"
75 | raise SamariumTypeError(msg) from None
76 | p.write_bytes(bytes_)
77 | elif isinstance(path, Number):
78 | if not path.is_int:
79 | msg = "cannot use non-integers"
80 | raise SamariumValueError(msg)
81 | if mode in {Mode.APPEND, Mode.WRITE}:
82 | fd = cast(int, path.val)
83 | write(fd, str(data).encode())
84 | else:
85 | msg = (
86 | "reading from file descriptors is "
87 | "not supported for quick operations"
88 | )
89 | raise SamariumIOError(msg)
90 | else:
91 | file = path
92 | if mode is Mode.READ:
93 | return file.load()
94 | if data is not None:
95 | file.save(data)
96 | else:
97 | msg = "missing data"
98 | raise SamariumIOError(msg)
99 | return NULL
100 |
101 |
102 | class File(Attrs):
103 | __slots__ = ("binary", "mode", "path", "val")
104 |
105 | def __init__(self, file: IO, mode: str, path: str | int, *, binary: bool) -> None:
106 | self.binary = binary
107 | self.mode = mode
108 | self.path = path
109 | self.val = file
110 |
111 | def __bool__(self) -> bool:
112 | return not self.val.closed
113 |
114 | def __str__(self) -> str:
115 | return f'File(path:"{self.path}", mode:{self.mode})'
116 |
117 | def __invert__(self) -> Null:
118 | self.val.close()
119 | return NULL
120 |
121 | def __iter__(self) -> Iterator[String] | Iterator[Array[Number]]:
122 | if self.binary:
123 | for line in self.val:
124 | yield Array(map(Num, list(line)))
125 | else:
126 | yield from map(String, self.val)
127 |
128 | def __getitem__(self, index: Any) -> Array | String | Number | Null:
129 | if isinstance(index, Slice):
130 | if index.is_empty():
131 | return Num(self.val.tell())
132 | if isinstance(index.step, Number):
133 | msg = "cannot use step"
134 | raise SamariumIOError(msg)
135 | if isinstance(index.start, Number):
136 | if not isinstance(index.stop, Number):
137 | return self.load(index.start)
138 | if not (index.start.is_int or index.stop.is_int):
139 | msg = f"invalid index: {index}"
140 | raise SamariumValueError(msg)
141 | start = cast(int, index.start.val)
142 | stop = cast(int, index.stop.val)
143 | current_pos = self.val.tell()
144 | self.val.seek(start)
145 | data = self.val.read(stop - start)
146 | self.val.seek(current_pos)
147 | if self.binary:
148 | if isinstance(data, bytes):
149 | data = [*data]
150 | return Array(map(Num, data))
151 | return String(data)
152 | return self[Slice(Num(0), slice.stop, slice.step)]
153 |
154 | self.val.seek(index.val)
155 | return NULL
156 |
157 | def load(self, bytes_: Number | None = None) -> String | Array:
158 | if bytes_ is None:
159 | bytes_ = Num(-1)
160 | if not bytes_.is_int:
161 | msg = "cannot use non-integers"
162 | raise SamariumValueError(msg)
163 | val = self.val.read(cast(int, bytes_.val))
164 | if self.binary:
165 | return Array(map(Num, val))
166 | return String(val)
167 |
168 | def save(self, data: String | Array) -> Null:
169 | if (self.binary and isinstance(data, String)) or (
170 | not self.binary and isinstance(data, Array)
171 | ):
172 | raise SamariumTypeError(get_type_name(data))
173 | if isinstance(data, Array):
174 | bytes_ = b""
175 | for i in data.val:
176 | try:
177 | bytes_ += i.val.to_bytes(1, "big")
178 | except AssertionError:
179 | msg = "some items in the array are not of type Integer"
180 | raise SamariumTypeError(msg) from None
181 | self.val.write(bytes_)
182 | else:
183 | self.val.write(data.val)
184 | return NULL
185 |
--------------------------------------------------------------------------------
/samarium/core.py:
--------------------------------------------------------------------------------
1 | # ruff: noqa: F401
2 | from __future__ import annotations
3 |
4 | import ast
5 | import importlib.machinery
6 | import importlib.util
7 | import sys
8 | from pathlib import Path
9 | from typing import TYPE_CHECKING
10 |
11 | from samarium import exceptions as exc
12 | from samarium.builtins import (
13 | dtnow,
14 | mkslice,
15 | print_safe,
16 | readline,
17 | sleep,
18 | t,
19 | throw,
20 | timestamp,
21 | )
22 | from samarium.classes import (
23 | MISSING,
24 | NEXT,
25 | NULL,
26 | Array,
27 | Dataclass,
28 | Enum,
29 | FileManager,
30 | Function,
31 | Mode,
32 | Module,
33 | Null,
34 | Num,
35 | Number,
36 | String,
37 | Table,
38 | UserAttrs,
39 | correct_type,
40 | )
41 | from samarium.exceptions import DAHLIA
42 | from samarium.imports import merge_objects, parse_string, resolve_path
43 | from samarium.runtime import Runtime
44 | from samarium.tokenizer import tokenize
45 | from samarium.transpiler import Registry, Transpiler
46 | from samarium.utils import sysexit
47 |
48 | if TYPE_CHECKING:
49 | from samarium.classes import Attrs
50 |
51 |
52 | def import_to_scope(data: str, reg: Registry, source: str) -> None:
53 | modules = parse_string(data)
54 | for mod in modules:
55 | if mod.name == "samarium":
56 | raise exc.SamariumRecursionError
57 | path = resolve_path(mod.name, source)
58 | mod_path = (path / f"{mod.name}.sm").resolve()
59 | if mod_path.exists():
60 | imported = run(mod_path.read_text(), Registry({}), mod_path)
61 | else:
62 | spec = importlib.util.spec_from_file_location(
63 | mod.name, str(path / f"{mod.name}.py")
64 | )
65 | if spec is None:
66 | msg = "couldn't load spec"
67 | raise ValueError(msg)
68 | module = importlib.util.module_from_spec(spec)
69 | sys.modules[mod.name] = module
70 | if spec.loader is None:
71 | msg = "ModuleSpec.loader is None"
72 | raise ValueError(msg)
73 | spec.loader.exec_module(module)
74 | registry = {
75 | f"sm_{k}": v
76 | for k, v in vars(module).items()
77 | if getattr(v, "__pyexported__", False)
78 | }
79 | imported = Registry(registry)
80 |
81 | reg.vars.update(merge_objects(reg, imported, mod))
82 |
83 |
84 | def import_inline(data: str, source: str) -> Attrs:
85 | reg = Registry({})
86 | import_to_scope(data, reg, source)
87 | return reg.vars.popitem()[1]
88 |
89 |
90 | def run(
91 | code: str,
92 | reg: Registry,
93 | source: Path | str,
94 | *,
95 | debug: bool = False,
96 | load_template: bool = True,
97 | repl: bool = False,
98 | ) -> Registry:
99 | runtime_state = Runtime.repl
100 | Runtime.repl = repl
101 | code = Transpiler(tokenize(code), reg).transpile().output
102 | if load_template:
103 | code = (
104 | (Path(__file__).resolve().parent / "template.txt")
105 | .read_text()
106 | .replace("{{CODE}}", code)
107 | .replace(
108 | "{{SOURCE}}",
109 | str(Path(source).resolve() if isinstance(source, str) else source),
110 | )
111 | )
112 | try:
113 | if debug:
114 | code = ast.unparse(ast.parse(code))
115 | DAHLIA.print(f"&j{code}", file=sys.stderr)
116 | reg.vars = globals() | reg.vars
117 | if repl:
118 | try:
119 | res = eval(code, reg.vars)
120 | if not (res is None or res is NULL):
121 | print(repr(res))
122 | except SyntaxError:
123 | exec(code, reg.vars)
124 | else:
125 | exec(code, reg.vars)
126 | except Exception as e: # noqa: BLE001
127 | exc.handle_exception(e)
128 | Runtime.repl = runtime_state
129 | return reg
130 |
--------------------------------------------------------------------------------
/samarium/exceptions.py:
--------------------------------------------------------------------------------
1 | import sys
2 | from re import compile
3 |
4 | from dahlia import Dahlia
5 |
6 | from samarium.runtime import Runtime
7 |
8 | DAHLIA = Dahlia()
9 | NDE_TYPES = {AttributeError, NameError, UnboundLocalError}
10 |
11 | ARG_NOT_ITER = compile(r"argument of type '(\w+)' is not iterable")
12 | BAD_OP = compile(
13 | r"'(.+)' not supported between instances of '(\w+)' and '(\w+)'"
14 | r"|unsupported operand type\(s\) for (.+): '(\w+)' and '(\w+)'"
15 | )
16 | BAD_UOP = compile(r"bad operand type for unary (.+): '(\w+)'")
17 | NO_GETITEM = compile(r"'(\w+)' object is not subscriptable")
18 | NO_SETITEM = compile(r"'(\w+)' object does not support item assignment")
19 | NOT_CALLITER = compile(r"'(\w+)' object is not (\w+)")
20 | OP_MAP = {
21 | ">=": ">:",
22 | "<=": "<:",
23 | "/": "--",
24 | "%": "---",
25 | "*": "++",
26 | "@": "><",
27 | "** or pow()": "+++",
28 | }
29 | SINGLE_QUOTED_NAME = compile(r"'(\w+)'")
30 |
31 |
32 | def clear_name(name: str) -> str:
33 | return name.removeprefix("__").removeprefix("sm_")
34 |
35 |
36 | def handle_exception(exception: Exception) -> None:
37 | exc_type = type(exception)
38 | errmsg = str(exception)
39 | name = ""
40 | if exc_type is NotDefinedError:
41 | exception = NotDefinedError(".".join(map(clear_name, errmsg.split("."))))
42 | if (
43 | exc_type is TypeError
44 | and "missing 1 required positional argument: 'self'" in errmsg
45 | ):
46 | exception = SamariumTypeError("missing instance")
47 | elif m := BAD_OP.match(errmsg):
48 | op, lhs, rhs = filter(None, m.groups())
49 | op = OP_MAP.get(op, op)
50 | exc_type = NotDefinedError
51 | exception = exc_type(f"{clear_name(lhs)} {op} {clear_name(rhs)}")
52 | elif m := BAD_UOP.match(errmsg):
53 | op, type_ = m.groups()
54 | exc_type = NotDefinedError
55 | exception = exc_type(f"{op}{clear_name(type_)}")
56 | elif m := ARG_NOT_ITER.match(errmsg):
57 | exc_type = NotDefinedError
58 | type_ = clear_name(m.group(1))
59 | exception = exc_type(f"->? {type_}")
60 | elif m := NO_GETITEM.match(errmsg):
61 | exc_type = NotDefinedError
62 | type_ = clear_name(m.group(1))
63 | exception = exc_type(f"{type_}<<>>")
64 | elif m := NO_SETITEM.match(errmsg):
65 | exc_type = NotDefinedError
66 | type_ = clear_name(m.group(1))
67 | exception = exc_type(f"{type_}<<>>:")
68 | elif m := NOT_CALLITER.match(errmsg):
69 | exc_type = NotDefinedError
70 | type_ = clear_name(m.group(1))
71 | template = "... _ ->? {}" if m.group(2) == "iterable" else "{}()"
72 | exception = exc_type(template.format(type_))
73 | elif exc_type is SyntaxError:
74 | exception = SamariumSyntaxError(
75 | f"invalid syntax at {int(errmsg.split()[-1][:-1])}"
76 | )
77 | elif exc_type in NDE_TYPES:
78 | names = SINGLE_QUOTED_NAME.findall(errmsg)
79 | exc_type = NotDefinedError
80 | exception = exc_type(
81 | f"{clear_name(names[0])}<<>>{':' * (names[1] == '__setitem__')}"
82 | if len(names) >= 2 and names[1] in {"__getitem__", "__setitem__"}
83 | else ".".join(map(clear_name, names))
84 | )
85 | elif exc_type is ZeroDivisionError:
86 | name = "MathError"
87 | elif exc_type not in {AssertionError, NotDefinedError}:
88 | name = exc_type.__name__.removeprefix("Samarium")
89 | name = name or exc_type.__name__
90 | DAHLIA.print(f"&4[{name}] {exception}", file=sys.stderr)
91 | if not Runtime.repl:
92 | sys.exit(1)
93 |
94 |
95 | class SamariumError(Exception):
96 | pass
97 |
98 |
99 | class NotDefinedError(SamariumError):
100 | def __init__(self, inp: object, message: str = "") -> None:
101 | if isinstance(inp, str):
102 | message = inp
103 | inp = ""
104 | else:
105 | inp = type(inp).__name__
106 | super().__init__(f"{inp}.{message}" if inp else message)
107 |
108 |
109 | class SamariumImportError(SamariumError):
110 | pass
111 |
112 |
113 | class SamariumSyntaxError(SamariumError):
114 | pass
115 |
116 |
117 | class SamariumTypeError(SamariumError):
118 | pass
119 |
120 |
121 | class SamariumValueError(SamariumError):
122 | pass
123 |
124 |
125 | class SamariumIOError(SamariumError):
126 | pass
127 |
128 |
129 | class SamariumRecursionError(SamariumError):
130 | pass
131 |
--------------------------------------------------------------------------------
/samarium/imports.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from dataclasses import dataclass
4 | from enum import Enum
5 | from pathlib import Path
6 | from re import Pattern, compile, sub
7 | from typing import TYPE_CHECKING
8 |
9 | from samarium.classes import Attrs, Module
10 | from samarium.exceptions import SamariumImportError, SamariumSyntaxError
11 |
12 | if TYPE_CHECKING:
13 | from samarium.transpiler import Registry
14 |
15 |
16 | FORMATTERS = {
17 | r"\bsm_(\w+)\b": r"\g<1>",
18 | r"\.?Array\(\[": ".[",
19 | r"\]\)": "]",
20 | }
21 |
22 | MODULE_NAMES = [
23 | "collections",
24 | "datetime",
25 | "io",
26 | "iter",
27 | "math",
28 | "operator",
29 | "random",
30 | "string",
31 | "types",
32 | ]
33 |
34 |
35 | def parse_string(string: str) -> list[Mod]:
36 | string = format_string(string)
37 | for it in Import.__members__.values():
38 | imptype, pattern = it, it.value
39 | if pattern.match(string):
40 | break
41 | else:
42 | msg = "invalid import syntax"
43 | raise SamariumSyntaxError(msg)
44 | if imptype is Import.MODULE:
45 | mods = string.split(",")
46 | out = []
47 | for m in mods:
48 | name, _, alias = m.partition(":")
49 | out.append(Mod(name, alias or None))
50 | return out
51 | if imptype is Import.STAR:
52 | return [Mod(string.split(".")[0], None, objects=True)]
53 | if imptype is Import.OBJECT:
54 | mod, obj = string.split(".")
55 | obj, _, alias = obj.partition(":")
56 | return [Mod(mod, None, [Obj(obj, alias or None)])]
57 | mod, objects_ = string.split(".")
58 | objects = objects_.strip("[]").split(",")
59 | objs = []
60 | for o in objects:
61 | name, _, alias = o.partition(":")
62 | objs.append(Obj(name, alias or None))
63 | return [Mod(mod, None, objs)]
64 |
65 |
66 | def format_string(string: str) -> str:
67 | for pattern, repl in FORMATTERS.items():
68 | string = sub(pattern, repl, string)
69 | return string
70 |
71 |
72 | def merge_objects(reg: Registry, imported: Registry, module: Mod) -> dict[str, Attrs]:
73 | vars_ = reg.vars.copy()
74 | if module.objects is False:
75 | return vars_ | {f"sm_{module.alias}": Module(module.name, imported.vars)}
76 | if module.objects is True:
77 | return vars_ | {k: v for k, v in imported.vars.items() if k.startswith("sm_")}
78 | for obj in module.objects:
79 | try:
80 | vars_[f"sm_{obj.alias}"] = imported.vars[f"sm_{obj.name}"]
81 | except KeyError:
82 | msg = f"{obj.name} is not a member of the {module.name} module"
83 | raise SamariumImportError(msg) from None
84 | return vars_
85 |
86 |
87 | def regex(string: str) -> Pattern:
88 | return compile("^" + string.replace("W", r"\w+") + "$")
89 |
90 |
91 | def resolve_path(name: str, source: str) -> Path:
92 | try:
93 | path = Path(source).parent
94 | except IndexError: # REPL
95 | path = Path.cwd()
96 | paths = [e.name for e in path.iterdir()]
97 | if not (f"{name}.sm" in paths or f"{name}.py" in paths):
98 | if name not in MODULE_NAMES:
99 | msg = f"invalid module: {name}"
100 | raise SamariumImportError(msg)
101 | path = Path(__file__).resolve().parent / "modules"
102 | return path
103 |
104 |
105 | class Import(Enum):
106 | MODULE = regex(r"W(?::W)?(?:,W(?::W)?)*")
107 | STAR = regex(r"W\.\*")
108 | OBJECT = regex(r"W\.W(?::W)?")
109 | OBJECTS = regex(r"W\.\[W(?::W)?(?:,W(?::W)?)*\]")
110 |
111 |
112 | class ImportAttrs:
113 | name: str
114 | alias: str | None
115 |
116 | def __post_init__(self) -> None:
117 | if self.alias is None:
118 | self.alias = self.name
119 |
120 |
121 | @dataclass
122 | class Mod(ImportAttrs):
123 | name: str
124 | alias: str | None = None
125 | objects: list[Obj] | bool = False
126 |
127 |
128 | @dataclass
129 | class Obj(ImportAttrs):
130 | name: str
131 | alias: str | None = None
132 |
--------------------------------------------------------------------------------
/samarium/modules/collections.sm:
--------------------------------------------------------------------------------
1 | <=operator;
2 |
3 | @ ArithmeticArray {
4 | => array * {
5 | 'array: array;
6 | }
7 |
8 | apply op other * {
9 | * ArithmeticArray([op(i, other) ... i ->? 'array]);
10 | }
11 |
12 | & other * { * 'apply(operator.and, other); }
13 | + other * { * 'apply(operator.add, other); }
14 | -- other * { * 'apply(operator.div, other); }
15 | :: other * { * 'apply(operator.eq, other); }
16 | >: other * { * 'apply(operator.ge, other); }
17 | > other * { * 'apply(operator.gt, other); }
18 | <: other * { * 'apply(operator.le, other); }
19 | < other * { * 'apply(operator.lt, other); }
20 | --- other * { * 'apply(operator.mod, other); }
21 | ++ other * { * 'apply(operator.mul, other); }
22 | ::: other * { * 'apply(operator.ne, other); }
23 | | other * { * 'apply(operator.or, other); }
24 | +++ other * { * 'apply(operator.pow, other); }
25 | - other * { * 'apply(operator.sub, other); }
26 | ^ other * { * 'apply(operator.xor, other); }
27 |
28 | <<>> item * {
29 | out: 'array<- >;
30 | ? out?! :: <-types.Array {
31 | * ArithmeticArray(out);
32 | }
33 | * out;
34 | }
35 |
36 | <<>>: item value * { 'array<
- >: value; }
37 |
38 | $ * { * 'array$; }
39 | ? * { * / ? 'array ,, \; }
40 | ! * { * "A" + ""?!('array); }
41 | }
42 |
43 | @ Deque {
44 |
45 | => size? * {
46 | size <> -/;
47 | 'deque: [];
48 | 'size: size;
49 | }
50 |
51 | $ * { * 'deque$; }
52 | is_empty * { * ~~ '$; }
53 | is_full * { * '$ :: 'size; }
54 |
55 | throw_empty * {
56 | ? 'is_empty() {
57 | "deque is empty"!!!;
58 | }
59 | }
60 |
61 | throw_full * {
62 | ? 'is_full() {
63 | "deque is full (size " + ""?!('size) + ")"!!!;
64 | }
65 | }
66 |
67 | put_front item * {
68 | 'throw_full(); 'deque: [item] + 'deque;
69 | }
70 |
71 | put item * { 'throw_full(); 'deque+: [item]; }
72 |
73 | put_front_all items * {
74 | ... item ->? items {
75 | 'put_front(item);
76 | }
77 | }
78 |
79 | put_all items * {
80 | ... item ->? items {
81 | 'put(item);
82 | }
83 | }
84 |
85 | front * { * 'deque<<\>>; }
86 | back * { * 'deque<<-/>>; }
87 | ? * { * '$ > \; }
88 |
89 | get_front * {
90 | 'throw_empty();
91 | out: 'deque<<\>>;
92 | 'deque-: \;
93 | * out;
94 | }
95 |
96 | get * {
97 | 'throw_empty();
98 | out: 'deque<<-/>>;
99 | 'deque-: 'deque$-;
100 | * out;
101 | }
102 |
103 | ! * {
104 | <=string.wrap;
105 | <=types.String;
106 | ? '$ <: /\/ {
107 | items: ""?!('deque)<>;
108 | } ,, {
109 | front: ""?!('front());
110 | back: ""?!('back());
111 | ? 'front()?! :: String {
112 | front: wrap(front, "\"");
113 | }
114 | ? 'back()?! :: String {
115 | back: wrap(back, "\"");
116 | }
117 | items: ""?!(front) + ", ..., " + ""?!(back);
118 | }
119 | * "Deque(" + items + ")";
120 | }
121 | }
122 |
123 | @ Queue {
124 | => size? * {
125 | size <> -/;
126 | 'queue: [];
127 | 'size: size;
128 | }
129 |
130 | $ * { * 'queue$; }
131 | ->? element * { * element ->? 'queue; }
132 | is_empty * { * ~~ '$; }
133 | is_full * { * '$ :: 'size; }
134 |
135 | throw_empty * {
136 | ? 'is_empty() {
137 | "queue is empty"!!!;
138 | }
139 | }
140 |
141 | put item * {
142 | ? 'is_full() {
143 | "queue is full (size " + ""?!('size) + ")"!!!;
144 | }
145 | 'queue+: [item];
146 | }
147 |
148 | put_all items * {
149 | ... item ->? items {
150 | 'put(item);
151 | }
152 | }
153 |
154 | get * {
155 | 'throw_empty();
156 | out: 'queue<<\>>;
157 | 'queue-: \;
158 | * out;
159 | }
160 |
161 | first * { 'throw_empty(); * 'queue<<\>>; }
162 | last * { 'throw_empty(); * 'queue<<-/>>; }
163 | ? * { * '$>; }
164 |
165 | ! * {
166 | <=string.wrap;
167 | <=types.String;
168 | ? '$ <: /\/ {
169 | items: ""?!('queue)<>;
170 | } ,, {
171 | first: ""?!('first());
172 | last: ""?!('last());
173 | ? 'first()?! :: String {
174 | first: wrap(first, "\"");
175 | }
176 | ? 'last()?! :: String {
177 | last: wrap(last, "\"");
178 | }
179 | items: ""?!(first) + ", ..., " + ""?!(last);
180 | }
181 | * "Queue(" + items + ")";
182 | }
183 | }
184 |
185 | @ Set {
186 | => items? capacity? * {
187 | items <> [];
188 | 'items: -items;
189 | }
190 |
191 | $ * { * 'items$; }
192 |
193 | add value * {
194 | x: value ~~ ->? ';
195 | 'items|: [value];
196 | * x;
197 | }
198 |
199 | ? * { * ~~ 'is_empty(); }
200 |
201 | ! * {
202 | * "Set(" + ""?!('items)<> + ")";
203 | }
204 |
205 | remove value * { 'items-: [value]; }
206 |
207 | ->? value * {
208 | * <-iter.find('items, value)>:;
209 | }
210 |
211 | #check_type value * {
212 | ? value?! ::: Set {
213 | "expected Set, received " + ""?!(value?!)!!!;
214 | }
215 | }
216 |
217 | clear * { 'items: []; }
218 | is_empty * { * '$ :: \; }
219 | ::: other * { * ~~ ' :: other; }
220 | :: other * {
221 | sort: <-iter.sorted;
222 | * sort('items) :: sort(other.items);
223 | }
224 |
225 | | other * {
226 | '#check_type(other);
227 | * Set('items | other.items);
228 | }
229 |
230 | ^ other * {
231 | '#check_type(other);
232 | * Set('items ^ other.items);
233 | }
234 |
235 | & other * {
236 | '#check_type(other);
237 | * Set('items & other.items);
238 | }
239 |
240 | - other * {
241 | '#check_type(other);
242 | * Set('items -- other.items);
243 | }
244 |
245 | > other * {
246 | '#check_type(other);
247 |
248 | ? '$ <: other$ { * \; }
249 |
250 | ... value ->? other.items {
251 | ? value ~~ ->? ' { * \; }
252 | }
253 | * /;
254 | }
255 |
256 | >: other * { * ' :: other || ' > other; }
257 | }
258 |
259 | @ Stack {
260 | => size? * {
261 | size <> -/;
262 | 'stack: [];
263 | 'size: size;
264 | }
265 |
266 | push item * {
267 | ? 'is_full() {
268 | "stack is full (size " + ""?!('size) + ")"!!!;
269 | }
270 | 'stack+: [item];
271 | }
272 |
273 | push_all items * {
274 | ... element ->? items {
275 | 'push(element);
276 | }
277 | }
278 |
279 | $ * { * 'stack$; }
280 |
281 | pop * {
282 | ? 'is_empty() {
283 | "stack is empty"!!!;
284 | }
285 | out: 'stack<<-/>>;
286 | 'stack-: 'stack$-;
287 | * out;
288 | }
289 |
290 | peek * {
291 | ? 'is_empty() {
292 | "stack is empty"!!!;
293 | }
294 | * 'stack<<-/>>;
295 | }
296 |
297 | ? * { * 'stack$ > \; }
298 | is_full * { * 'size :: 'stack$; }
299 | is_empty * { * 'stack$ :: \; }
300 |
301 | ! * {
302 | ?? {
303 | top: 'peek();
304 | } !! {
305 | top:;
306 | }
307 | ? top?! :: <-types.String {
308 | top: <-string.wrap(""?!(top), "\"");
309 | }
310 | * "Stack(capacity:" + ""?!('size)
311 | + ", size:" + ""?!('stack$)
312 | + ", top:" + ""?!(top)
313 | + ")";
314 | }
315 | }
--------------------------------------------------------------------------------
/samarium/modules/datetime.sm:
--------------------------------------------------------------------------------
1 | <=types.String;
2 |
3 | MONTHS: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
4 | WEEKDAYS: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
5 |
6 | days_in_month year month * {
7 | ? month ->? [/, //, /\/, ///, /\\\, /\/\, //\\] {
8 | * /////;
9 | }
10 | ? month ->? [/\, //\, /\\/, /\//] {
11 | * ////\;
12 | }
13 | ? is_leap_year(year) {
14 | * ///\/;
15 | }
16 | * ///\\;
17 | }
18 |
19 | is_leap_year year * {
20 | ? year --- //\\/\\\\ {
21 | * /;
22 | } ,, ? year --- //\\/\\ {
23 | * \;
24 | } ,, ? (year --- /\\) {
25 | * /;
26 | } ,, {
27 | * \;
28 | }
29 | }
30 |
31 | month_name n * {
32 | ? n >: MONTHS$ {
33 | * "";
34 | } ,, ? n< {
35 | * "";
36 | } ,, {
37 | * MONTHS<>;
38 | }
39 | }
40 |
41 | sleep_minutes minutes * {
42 | sleep_seconds(minutes ++ ////\\);
43 | }
44 |
45 | sleep_seconds seconds * {
46 | ,., seconds ++ /////\/\\\;
47 | }
48 |
49 | timestamp_utc ts? * {
50 | 60_000: ///\/\/\\//\\\\\;
51 | 3_600_000: //\//\///\///\/\\\\\\\;
52 | ts <> @@;
53 | dtnow: @@@;
54 | tz: dtnow<<-/\..>>;
55 | offset: tz<<\>> ++ 3_600_000 + tz<>> ++ 60_000;
56 | * ts - offset;
57 | }
58 |
59 | weekday_name n * {
60 | ? n >: WEEKDAYS$ {
61 | * "";
62 | } ,, ? n< {
63 | * "";
64 | } ,, {
65 | * WEEKDAYS<>;
66 | }
67 | }
68 |
69 | @ DateTime {
70 | => Y? M? D? h? m? s? ms? tz? * {
71 | Y <>;
72 | ? :: Y {
73 | 'array: @@@;
74 | 'year: 'array<<\>>;
75 | 'month: 'array<>>;
76 | 'day: 'array<\>>;
77 | 'hour: 'array</>>;
78 | 'minute: 'array<\\>>;
79 | 'second: 'array<\/>>;
80 | 'millisecond: 'array</\>>;
81 | 'timezone: 'array<//..>>;
82 | } ,, {
83 | M <> /; D <> /;
84 | h <> \; m <> \; s <> \; ms <> \;
85 | tz <> [\, \];
86 | 'year: Y;
87 | 'month: M;
88 | 'day: D;
89 | 'hour: h;
90 | 'minute: m;
91 | 'second: s;
92 | 'millisecond: ms;
93 | 'timezone: tz;
94 | 'array: [Y, M, D, h, m, s, ms, tz<<\>>, tz<>>];
95 | }
96 | ? 'timezone ::: [\, \] {
97 | 'utc: DateTime(
98 | 'year, 'month, 'day, 'hour, 'minute, 'second, 'millisecond
99 | );
100 | } ,, {
101 | 'utc: ';
102 | }
103 | }
104 |
105 | - other * {
106 | diff: 'to_timestamp() - other.to_timestamp();
107 | 1000: /\/\ +++ //;
108 | 60: ////\\;
109 | 24: //\\\;
110 | ms: diff --- 1000; diff--: 1000;
111 | s: diff --- 60; diff--: 60;
112 | m: diff --- 60; diff--: 60;
113 | h: diff --- 24; diff--: 24;
114 | == `diff` is days
115 | * DTDiff([diff, h, m, s, ms]);
116 | }
117 |
118 | ! * {
119 | add0s n c * { * <-string.leftpad(String(n), c, "0"); }
120 | * "$Y-$M-$D $h:$m:$s.$z" --- {{
121 | "Y" -> 'year,
122 | "M" -> add0s('month, /\),
123 | "D" -> add0s('day, /\),
124 | "h" -> add0s('hour + 'timezone<<\>>, /\),
125 | "m" -> add0s('minute + 'timezone<>>, /\),
126 | "s" -> add0s('second, /\),
127 | "z" -> add0s('millisecond, //)
128 | }};
129 | }
130 |
131 | to_timestamp * {
132 | 1000: /\/\ +++ //;
133 | 60: ////\\;
134 |
135 | mod_array: 'array<<..-/\..>>;
136 | array: <-collections.ArithmeticArray(mod_array<<....-/>>);
137 | total: \;
138 |
139 | month_days: days_in_month('year, 'month);
140 | year_days: /\//\//\/ + is_leap_year('year);
141 |
142 | ... i ->? [/, 1000, 60, 60, //\\\, month_days, year_days] {
143 | array++: i;
144 | total+: array<<\>>;
145 | array: array<>;
146 | }
147 |
148 | total-: 'timezone<<\>> ++ 1000 ++ 60 ++ 60;
149 | total-: 'timezone<>> ++ 1000 ++ 60;
150 |
151 | * total;
152 | }
153 | }
154 |
155 | @ DTDiff {
156 | => data * {
157 | 'data: data;
158 | }
159 |
160 | ! * {
161 | add0s n c * { * <-string.leftpad(String(n), c, "0"); }
162 | D: 'data<<\>>;
163 | h: 'data<>>;
164 | m: 'data<\>>;
165 | s: 'data</>>;
166 | ms: 'data<\\>>;
167 | out: "";
168 | ? D { out+: ""?!(D) + " days"; }
169 | ? D && (~~ (h || m || s || ms)) { out+: ", "; }
170 | ? h { out+: add0s(h, /\) + ":"; }
171 | ? h || m { out+: add0s(m, /\) + ":"; }
172 | ? h || m || s { out+: add0s(s, /\); }
173 | ? ms { out+: "." + add0s(ms, //); }
174 | * <-string.strip_left(out, "0") || "0";
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/samarium/modules/io.sm:
--------------------------------------------------------------------------------
1 | <=math.to_hex;
2 | <=iter.[any, map];
3 | <=string.[join, split, strip];
4 | <=types.[Array, Number, Slice];
5 |
6 |
7 | @ Bytes {
8 | => value * {
9 | '#value: '#to_bytes(value);
10 | }
11 |
12 | #to_bytes v * {
13 | ? v?! :: ""?! {
14 | * [i% ... i ->? v];
15 | }
16 | ? v?! :: []?! {
17 | ? any([i?! ::: /?! ... i ->? v]) {
18 | "non-int found in the list"!!!;
19 | }
20 | * v;
21 | } ,, {
22 | "invalid type: " + ""?!(v?!)!!!;
23 | }
24 | }
25 |
26 | + other * {
27 | ? other?! :: Bytes {
28 | * Bytes('#value + other.export());
29 | }
30 | * Bytes('#value + '#to_bytes(other));
31 | }
32 |
33 | ! * {
34 | * join([to_hex(i) ... i ->? '#value], " ");
35 | }
36 |
37 | export * { * '#value; }
38 |
39 | export_string * {
40 | * join([i% ... i ->? '#value]);
41 | }
42 | }
43 |
44 | inputcast prompt? * {
45 | prompt <> "";
46 | * #autocast(prompt???);
47 | }
48 |
49 | #autocast value * {
50 | value: strip(value);
51 | ? ~~ value { *; }
52 | ? ".." ->? value {
53 | bits: [Integer(i) ? i ,, ... i ->? split(value, "..")];
54 | ? bits$ > // {
55 | "too many arguments for slice"!!!;
56 | }
57 | * Slice(**bits);
58 | }
59 | ? "=" ->? value {
60 | items: [split(i, "=") ... i ->? split(value, ",")];
61 | * {{strip(k) -> #autocast(v) ... k, v ->? items}};
62 | }
63 | ... sep ->? ", " {
64 | ? sep ->? value {
65 | * Array(map(#autocast, split(value, sep)));
66 | }
67 | }
68 | ?? { * Integer(value); }
69 | !! { * value; }
70 | }
71 |
72 | read_until target? * {
73 | target <> "";
74 | s: "";
75 | .. {
76 | i: ???;
77 | s+: i;
78 | ? i :: target { <- }
79 | s+: "\n";
80 | }
81 | * s;
82 | }
--------------------------------------------------------------------------------
/samarium/modules/iter.sm:
--------------------------------------------------------------------------------
1 | accumulate array function * {
2 | prev: array<<\>>;
3 | ** prev;
4 | ... e ->? array<> {
5 | prev: function(prev, e);
6 | ** prev;
7 | }
8 | }
9 |
10 | all array * {
11 | ... i ->? array {
12 | ? ~~ i {
13 | * \;
14 | }
15 | }
16 | * /;
17 | }
18 |
19 | any array * {
20 | ... i ->? array {
21 | ? i {
22 | * /;
23 | }
24 | }
25 | * \;
26 | }
27 |
28 | chunks array size * {
29 | ... i ->? <<..array$..size>> {
30 | ** array<>;
31 | }
32 | }
33 |
34 | count array target * {
35 | ? target ~~ ->? array { * \; }
36 | s: \;
37 | ... e ->? array {
38 | s+: e :: target;
39 | }
40 | * s;
41 | }
42 |
43 | cycle iter * {
44 | saved: [e ... e ->? iter];
45 | .. {
46 | ... e ->? saved {
47 | ** e;
48 | }
49 | }
50 | }
51 |
52 | drop_while array function * {
53 | i: \;
54 | ... e ->? array {
55 | i+:;
56 | ? ~~ function(e) {
57 | ** e;
58 | <-
59 | }
60 | }
61 | ... e ->? array<> {
62 | ** e;
63 | }
64 | }
65 |
66 | filter function array * {
67 | ? function$ :: / {
68 | array: [[**e] ... e ->? array];
69 | }
70 | ... e ->? array {
71 | ? function(**e) {
72 | ** e;
73 | }
74 | }
75 | }
76 |
77 | filter_false function array * {
78 | flip arg * { * ~~ function(arg); }
79 | * filter(flip, array);
80 | }
81 |
82 | find array target * {
83 | index: \;
84 | ? array?! :: <-types.String {
85 | .. index + target$ <: array$ {
86 | ? array<> :: target {
87 | * index;
88 | }
89 | index+:;
90 | }
91 | } ,, {
92 | ... e ->? array {
93 | ? e :: target {
94 | * index;
95 | }
96 | index+:;
97 | }
98 | }
99 | * -/;
100 | }
101 |
102 |
103 | find_all array target * {
104 | ? array?! :: <-types.String && target$ > / {
105 | ... i ->? <<..array$>> {
106 | substring: array<>;
107 | ? substring :: target {
108 | ** i;
109 | }
110 | }
111 | } ,, {
112 | ... i, v ->? <<>> >< array {
113 | ? v :: target {
114 | ** i;
115 | }
116 | }
117 | }
118 | }
119 |
120 | flatten array depth? * {
121 | depth <>;
122 | ? depth ::: && depth< {
123 | "depth cannot be negative"!!!;
124 | }
125 | x: [i ... i ->? array];
126 |
127 | ? depth :: \ {
128 | * x;
129 | }
130 |
131 | ? depth :: {
132 | old: x;
133 | new: #flatten(x);
134 | .. new ::: old {
135 | old: [i ... i ->? new];
136 | new: #flatten(old);
137 | }
138 | * new;
139 | } ,, {
140 | flattened: x;
141 | ... _ ->? <<..depth>> {
142 | flattened: #flatten(flattened);
143 | }
144 | * flattened;
145 | }
146 | }
147 |
148 | #flatten array * {
149 | flattened: [];
150 | ... i ->? array {
151 | ? i?! :: []?! {
152 | flattened+: i;
153 | } ,, {
154 | flattened+: [i];
155 | }
156 | }
157 | * flattened;
158 | }
159 |
160 | map function array * {
161 | ? function$ :: / {
162 | array: [[e] ... e ->? array];
163 | }
164 | ... e ->? array {
165 | ** function(**e);
166 | }
167 | }
168 |
169 | pairwise array * {
170 | ... i ->? <<..array$ - />> {
171 | ** array<>;
172 | }
173 | }
174 |
175 | reduce function array * {
176 | prev: array<<\>>;
177 | ... e ->? array<> {
178 | prev: function(prev, e);
179 | }
180 | * prev;
181 | }
182 |
183 | reverse array * {
184 | i: array$-;
185 | .. i>: {
186 | ** array<>;
187 | i-:;
188 | }
189 | }
190 |
191 | sorted array key? * {
192 | key <> #dummy;
193 | decorated: [[key(v), v] ... v ->? array];
194 | d_sorted: #quicksort(decorated);
195 | * [v ... _, v ->? d_sorted];
196 | }
197 |
198 | #dummy x * { * x; }
199 |
200 | #quicksort array * {
201 | x: [i ... i ->? array];
202 | ? x$ < /\ { * x; }
203 | pivot: x<<\>>;
204 | less: [i ... i ->? x<> ? i <: pivot];
205 | greater: [i ... i ->? x<> ? i > pivot];
206 | * #quicksort(less) + [pivot] + #quicksort(greater);
207 | }
208 |
209 | take_while array function * {
210 | ... e ->? array {
211 | ? function(e) {
212 | ** e;
213 | ->
214 | }
215 | <-
216 | }
217 | }
218 |
219 | zip_longest fill arrays... * {
220 | longest: <-math.max([i$ ... i ->? arrays]);
221 | ... i ->? <<..longest>> {
222 | ... it ->? arrays {
223 | ?? {
224 | ** it<>;
225 | } !! {
226 | ** fill;
227 | }
228 | }
229 | }
230 | }
--------------------------------------------------------------------------------
/samarium/modules/math.sm:
--------------------------------------------------------------------------------
1 | E: /\`/\//\//////\\\\/\/\/\\\/\//\\\/\/\\\/\/\///\//\/\\/\/\/\\//\/\/\/\//////\///\\\/\/\//\\\/\\\\\\\/\\/;
2 | PHI: /`/\\////\\\//\///\////\\//\///\\/\///////\/\\/\/\\/////\\\\\/\/\/////\\///\\///\\//\\\\\\\//\\\\\\/\/;
3 | PI: //`\\/\\/\\\\//////\//\/\/\/\\\/\\\/\\\\/\//\/\\\//\\\\/\\\//\/\\//\\\/\\//\\\//\\//\\\/\/\\\/\///\\\\\;
4 | TAU: PI++;
5 |
6 |
7 | abs x * {
8 | ? x < \ { * -x; }
9 | * x;
10 | }
11 |
12 | ceil x * {
13 | ? is_int(x) { * x; }
14 | * x$+;
15 | }
16 |
17 | factorial n * {
18 | ? n < \ {
19 | "factorial is defined only for non-negative numbers"!!!;
20 | }
21 | ? ~~is_int(n) {
22 | "n has to be an integer"!!!;
23 | }
24 | o: /;
25 | .. n > \ {
26 | o++: n;
27 | n-: /;
28 | }
29 | * o;
30 | }
31 |
32 | floor x * {
33 | * x$;
34 | }
35 |
36 | gcd a b * {
37 | .. b { a, b: b, a --- b; }
38 | * abs(a);
39 | }
40 |
41 | is_int x * {
42 | * x :: x$;
43 | }
44 |
45 | lcm a b * {
46 | ?? { * abs(a ++ b) -- gcd(a, b); }
47 | !! { * \; }
48 | }
49 |
50 | max array b... * {
51 | ? b { * max([array] + b); }
52 | o: array<<\>>;
53 | ... e ->? array {
54 | ? e > o { o: e; }
55 | }
56 | * o;
57 | }
58 |
59 | min array b... * {
60 | ? b { * min([array] + b); }
61 | o: array<<\>>;
62 | ... e ->? array {
63 | ? e < o { o: e; }
64 | }
65 | * o;
66 | }
67 |
68 | product array * {
69 | o: /;
70 | ... e ->? array { o++: e; }
71 | * o;
72 | }
73 |
74 | shl a b * {
75 | * a ++ /\ +++ b;
76 | }
77 |
78 | shr a b * {
79 | * a -- /\ +++ b;
80 | }
81 |
82 | sqrt x * {
83 | ? x < \ {
84 | "sqrt is defined only for non-negative numbers"!!!;
85 | }
86 | * x +++ `/;
87 | }
88 |
89 | sum array start? * {
90 | start <> \;
91 | o: start;
92 | ... e ->? array { o+: e; }
93 | * o;
94 | }
95 |
96 | #to_base n base base_name charset * {
97 | ? ~~ is_int(n) {
98 | "cannot convert a non-integer to $0" --- base_name!!!;
99 | }
100 | div: (n -- base)$;
101 | mod: n --- base;
102 | ? ~~ div { * charset<>; }
103 | * #to_base(div, base, base_name, charset) + charset<>;
104 | }
105 |
106 | to_bin n * {
107 | * #to_base(n, /\, "bin", "01");
108 | }
109 |
110 | to_hex n * {
111 | * #to_base(n, /\\\\, "hex", <-string.HEXDIGITS);
112 | }
113 |
114 | to_oct n * {
115 | * #to_base(n, /\\\, "oct", <-string.OCTDIGITS);
116 | }
117 |
118 | is_prime n * {
119 | ? ~~is_int(n) { * \; }
120 | ? n <: // { * n > /; }
121 | ? n --- :: \ || n --- // :: \ { * \; }
122 | i: /\/;
123 | .. i ++ i <: n {
124 | ? n --- i :: \ || n --- (i + /\) :: \ { * \; }
125 | i+: //\;
126 | }
127 | * /;
128 | }
129 |
130 | #r: <-pystd.round_;
131 |
132 | round n ndigits? * {
133 | ndigits <>;
134 | * #r(n, ndigits);
135 | }
--------------------------------------------------------------------------------
/samarium/modules/operator.sm:
--------------------------------------------------------------------------------
1 | add x y * {
2 | * x + y;
3 | }
4 |
5 | and x y * {
6 | * x & y;
7 | }
8 |
9 | cast x * {
10 | * x%;
11 | }
12 |
13 | div x y * {
14 | * x -- y;
15 | }
16 |
17 | eq x y * {
18 | * x :: y;
19 | }
20 |
21 | ge x y * {
22 | * x >: y;
23 | }
24 |
25 | gt x y * {
26 | * x > y;
27 | }
28 |
29 | has x y * {
30 | * y ->? x;
31 | }
32 |
33 | hash x * {
34 | * x##;
35 | }
36 |
37 | le x y * {
38 | * x <: y;
39 | }
40 |
41 | lt x y * {
42 | * x < y;
43 | }
44 |
45 | mod x y * {
46 | * x --- y;
47 | }
48 |
49 | mul x y * {
50 | * x ++ y;
51 | }
52 |
53 | not x * {
54 | * ~x;
55 | }
56 |
57 | ne x y * {
58 | * x ::: y;
59 | }
60 |
61 | or x y * {
62 | * x | y;
63 | }
64 |
65 | pow x y * {
66 | * x +++ y;
67 | }
68 |
69 | random x * {
70 | * x??;
71 | }
72 |
73 | special x * {
74 | * x$;
75 | }
76 |
77 | sub x y * {
78 | * x - y;
79 | }
80 |
81 | to_bit x * {
82 | * / ? x ,, \;
83 | }
84 |
85 | to_string x * {
86 | * ""?!(x);
87 | }
88 |
89 | xor x y * {
90 | * x ^ y;
91 | }
--------------------------------------------------------------------------------
/samarium/modules/pystd.py:
--------------------------------------------------------------------------------
1 | # ruff: noqa: INP001
2 | from __future__ import annotations
3 |
4 | from samarium.python import export
5 |
6 |
7 | @export
8 | def round_(x: float, ndigits: int | None = None) -> float:
9 | return round(x, ndigits)
10 |
--------------------------------------------------------------------------------
/samarium/modules/random.sm:
--------------------------------------------------------------------------------
1 | choices array k * {
2 | * [array?? ... _ ->? <<..k>>];
3 | }
4 |
5 | randint start end * {
6 | * (end - start+)?? + start;
7 | }
8 |
9 | sample array k * {
10 | ? k > array$ {
11 | "the sample cannot be bigger than the array"!!!;
12 | }
13 | o: [];
14 | ? array?! :: <-types.Slice {
15 | old_array: []?!(array);
16 | } ,, {
17 | old_array: array<<>>;
18 | }
19 | .. o$ < k {
20 | e: old_array??;
21 | o+: [e];
22 | old_array-: [e];
23 | }
24 | * o;
25 | }
26 |
27 | shuffle array * {
28 | ? array?! :: <-types.Slice {
29 | "cannot shuffle slices"!!!;
30 | }
31 | shuffled_array: [];
32 | old_array: array<<>>;
33 | .. old_array$> {
34 | e: old_array??;
35 | shuffled_array+: [e];
36 | old_array-: [e];
37 | }
38 | * shuffled_array;
39 | }
--------------------------------------------------------------------------------
/samarium/modules/string.sm:
--------------------------------------------------------------------------------
1 | UPPERCASE: "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
2 | LOWERCASE: "abcdefghijklmnopqrstuvwxyz";
3 | LETTERS: UPPERCASE + LOWERCASE;
4 |
5 | OCTDIGITS: "01234567";
6 | DIGITS: OCTDIGITS + "89";
7 | HEXDIGITS: DIGITS + "abcdef";
8 |
9 | PUNCTUATION: "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
10 | WHITESPACE: " \t\n\r\f\v";
11 |
12 | PRINTABLE: LETTERS + DIGITS + PUNCTUATION + WHITESPACE;
13 |
14 | capitalize string * {
15 | ? ~~ string { * string; }
16 | * to_upper(string<<\>>) + to_lower(string<>);
17 | }
18 |
19 | center string length char? * {
20 | char <> " ";
21 | ? char$ > / {
22 | "char has to be of length 1"!!!;
23 | }
24 | index: \;
25 | .. string$ < length {
26 | ? index {
27 | string: char + string;
28 | } ,, {
29 | string+: char;
30 | }
31 | index+:;
32 | index---:;
33 | }
34 | * string;
35 | }
36 |
37 | ends_with string suffix * {
38 | ? suffix$ > string$ { * \; }
39 | ? suffix :: string { * /; }
40 | * string<<-suffix$..>> :: suffix;
41 | }
42 |
43 | is_alphabetic string * {
44 | * is_in_group(string, LETTERS);
45 | }
46 |
47 | is_alphanumeric string * {
48 | * is_in_group(string, LETTERS + DIGITS);
49 | }
50 |
51 | is_capitalized string * {
52 | * string :: capitalize(string);
53 | }
54 |
55 | is_decimal string * {
56 | * is_in_group(string, DIGITS);
57 | }
58 |
59 | is_hexadecimal string * {
60 | * is_in_group(string, HEXDIGITS);
61 | }
62 |
63 | is_in_group string group * {
64 | ... char ->? string {
65 | ? char ~~ ->? group { * \; }
66 | }
67 | * /;
68 | }
69 |
70 | is_lower string * {
71 | * string :: to_lower(string);
72 | }
73 |
74 | is_octal string * {
75 | * is_in_group(string, OCTDIGITS);
76 | }
77 |
78 | is_title string * {
79 | * string :: title(string);
80 | }
81 |
82 | is_upper string * {
83 | * string :: to_upper(string);
84 | }
85 |
86 | is_wrapped string chars * {
87 | * starts_with(string, chars) && ends_with(string, chars);
88 | }
89 |
90 | join iterable delimiter? * {
91 | delimiter <> "";
92 | o: "";
93 | ... e ->? iterable {
94 | o+: ""?!(e);
95 | o+: delimiter;
96 | }
97 | ? delimiter {
98 | o: o<<..-delimiter$>>;
99 | }
100 | * o;
101 | }
102 |
103 | leftpad string length char? * {
104 | char <> " ";
105 | * pad(string, length, char) + string;
106 | }
107 |
108 | ordinal n * {
109 | 10: /\/\;
110 | ords: {{/ -> "st", /\ -> "nd", // -> "rd"}};
111 | ? n --- (10 ++ 10) ~~ ->? <\\../\/\/>> && n --- 10 ->? ords {
112 | * "$0$1" --- [n, ords<>];
113 | }
114 | * "$0th" --- [n];
115 | }
116 |
117 | pad string length char? * {
118 | char <> " ";
119 | ? char$ > / {
120 | "char has to be of length 1"!!!;
121 | }
122 | ? length < string$ { * string; }
123 | * char ++ (length - string$);
124 | }
125 |
126 | replace string replacements count? * {
127 | count <> -/;
128 |
129 | replace string from to count * {
130 | empty * {
131 | ? ~~ from { * to; }
132 | * string;
133 | }
134 |
135 | interleave count * {
136 | out: to;
137 | count-:;
138 | ... i ->? string {
139 | out+: i;
140 | ? count {
141 | out+: to;
142 | count-:;
143 | }
144 | }
145 | * out;
146 | }
147 |
148 | del_char count * {
149 | out: "";
150 | ... i ->? string {
151 | ? count && i :: from {
152 | count-:;
153 | } ,, {
154 | out+: i;
155 | }
156 | }
157 | * out;
158 | }
159 |
160 | replace_substring count * {
161 | string_copy: string;
162 | out: "";
163 | find: <-iter.find;
164 | .. count && from ->? string_copy {
165 | i: find(string_copy, from);
166 | out+: string_copy<<..i>>;
167 | out+: to;
168 | string_copy: string_copy<>;
169 | count-:;
170 | }
171 | * out + string_copy;
172 | }
173 |
174 | replace_char count * {
175 | out: "";
176 | ... i ->? string {
177 | ? i :: from && count {
178 | out+: to;
179 | count-:;
180 | } ,, {
181 | out+: i;
182 | }
183 | }
184 | * out;
185 | }
186 |
187 | ? ~~ string {
188 | * empty();
189 | } ,, ? ~~ from {
190 | * interleave(count);
191 | } ,, ? from$ :: / {
192 | ? ~~ to {
193 | * del_char(count);
194 | }
195 | * replace_char(count);
196 | } ,, {
197 | * replace_substring(count);
198 | }
199 | }
200 |
201 | ? replacements?! ::: <-types.Table {
202 | "invalid type for replacements: " + ""?!(replacements?!)!!!;
203 | }
204 | replacements: {{k -> v ... k, v ->? replacements >< replacements$}};
205 | ? ~~ <-iter.all([count, replacements]) {
206 | * string;
207 | }
208 | ... f, t ->? replacements >< replacements$ {
209 | string: replace(string, f, t, count);
210 | }
211 | * string;
212 | }
213 |
214 | rightpad string length char? * {
215 | char <> " ";
216 | * string + pad(string, length, char);
217 | }
218 |
219 | split string separator? * {
220 | separator <> " ";
221 | ? ~~ separator { ["empty separator", "use []?!(string) instead"]!!!; }
222 | ? separator?! :: []?! {
223 | first: separator<<\>>;
224 | rest: separator<>;
225 | string: replace(string, {{i -> first ... i ->? rest}});
226 | separator: first;
227 | }
228 | ? separator ~~ ->? string { * [string]; }
229 | ? separator$ :: / {
230 | * #split_char(string, separator);
231 | }
232 | out: [];
233 | <=iter.find;
234 | .. {
235 | idx: find(string, separator);
236 | ? idx :: -/ { <- }
237 | out+: [string<<..idx>>];
238 | string: string<>;
239 | }
240 | * out + [string];
241 | }
242 |
243 | #split_char string separator * {
244 | out: [];
245 | temp: "";
246 | ... char ->? string {
247 | ? char :: separator {
248 | out+: [temp];
249 | temp: "";
250 | } ,, {
251 | temp+: char;
252 | }
253 | }
254 | * out + [temp];
255 | }
256 |
257 | split_lines string * {
258 | * split(
259 | replace(string, {{"\r\n" -> "\n"}}),
260 | []?!("\r\n\v\f\x1c\x1d\x1e\x85\u2028\u2029")
261 | );
262 | }
263 |
264 | starts_with string prefix * {
265 | ? prefix$ > string$ { * \; }
266 | ? prefix :: string { * /; }
267 | * string<<..prefix$>> :: prefix;
268 | }
269 |
270 | strip string chars? * {
271 | chars <> " ";
272 | * strip_left(strip_right(string, chars), chars);
273 | }
274 |
275 | strip_left string prefix * {
276 | .. starts_with(string, prefix) {
277 | string: string<>;
278 | }
279 | * string;
280 | }
281 |
282 | strip_right string suffix * {
283 | .. ends_with(string, suffix) {
284 | string: string<<..-suffix$>>;
285 | }
286 | * string;
287 | }
288 |
289 | swapcase string * {
290 | out: "";
291 | ... char ->? string {
292 | ? is_upper(char) {
293 | out+: to_lower(char);
294 | } ,, {
295 | out+: to_upper(char);
296 | }
297 | }
298 | * out;
299 | }
300 |
301 | title string * {
302 | * join([capitalize(w) ... w ->? split(string)], " ");
303 | }
304 |
305 | to_lower string * {
306 | ? string?! ::: <-types.String {
307 | "invalid type: " + ""?!(string?!)!!!;
308 | }
309 | out: "";
310 | ... char ->? string {
311 | ? char% ->? <\\\\\/../\//\/\>> {
312 | out+: (char% + /\\\\\)%;
313 | } ,, {
314 | out+: char;
315 | }
316 | }
317 | * out;
318 | }
319 |
320 | to_upper string * {
321 | ? string?! ::: <-types.String {
322 | "invalid type: " + ""?!(string?!)!!!;
323 | }
324 | out: "";
325 | ... char ->? string {
326 | ? char% ->? </\\\\/..////\/\>> {
327 | out+: (char% - /\\\\\)%;
328 | } ,, {
329 | out+: char;
330 | }
331 | }
332 | * out;
333 | }
334 |
335 | wrap string wrapper * {
336 | * wrapper + string + wrapper;
337 | }
--------------------------------------------------------------------------------
/samarium/modules/types.sm:
--------------------------------------------------------------------------------
1 | Array: []?!;
2 | Number: \?!;
3 | Null: (||)?!;
4 | Slice: <<>>?!;
5 | String: ""?!;
6 | Table: {{}}?!;
7 | Zip: ("" >< "")?!;
8 |
9 |
10 | @ Boolean {
11 | => value * {
12 | ? value?! :: ""?! {
13 | 'value: {{"true" -> /, "false" -> \}}<<<-string.to_lower(value)>>;
14 | } ,, {
15 | 'value: / ? value ,, \;
16 | }
17 | }
18 |
19 | ! * { * "true" ? 'value ,, "false"; }
20 | ? * { * 'value; }
21 |
22 | val obj * {
23 | ? obj?! :: '?! { * obj.value; }
24 | * obj;
25 | }
26 |
27 | :: other * {
28 | * Boolean('value :: 'val(other));
29 | }
30 |
31 | > other * { * Boolean('value > 'val(other)); }
32 |
33 |
34 | + other * {
35 | out: 'value + 'val(other);
36 | ? out > / {
37 | * out;
38 | } ,, {
39 | * Boolean(out);
40 | }
41 | }
42 |
43 | - other * {
44 | out: 'value - 'val(other);
45 | ? out< {
46 | * out;
47 | } ,, {
48 | * Boolean(out);
49 | }
50 | }
51 |
52 | ++ other * {
53 | * Boolean('value ++ 'val(other));
54 | }
55 |
56 | -- other * {
57 | * Boolean('value -- 'val(other));
58 | }
59 |
60 | +++ other * {
61 | * Boolean('value +++ 'val(other));
62 | }
63 |
64 | --- other * {
65 | * Boolean('value --- 'val(other));
66 | }
67 |
68 | & other * { * Boolean('value & 'val(other)); }
69 | ^ other * { * Boolean('value ^ 'val(other)); }
70 | | other * { * Boolean('value | 'val(other)); }
71 |
72 | ~ * { * Boolean(\ ? 'value ,, /); }
73 | }
74 |
75 | @ UUID4 {
76 | => * {
77 | hex: <-string.join(<-random.choices(<-string.HEXDIGITS, /\\\\\));
78 | hex</\\>>: "4";
79 | hex<\\\\>>: "89ab"??;
80 | 'hex: hex;
81 | 'dec: Integer("x:" + hex);
82 | }
83 |
84 | ! * {
85 | h: 'hex;
86 | 8: /\\\;
87 | 12: //\\;
88 | 16: /\\\\;
89 | 20: /\/\\;
90 | * <-string.join(
91 | [h<<..8>>, h<<8..12>>, h<<12..16>>, h<<16..20>>, h<<20..>>],
92 | "-"
93 | );
94 | }
95 | }
96 |
97 | @ Frozen {
98 | => val * {
99 | t: val?!;
100 | ? t ~~ ->? [Array, Table] {
101 | "type $0 is already frozen" --- [t]!!!;
102 | }
103 | ? t :: Array {
104 | '#val: val<<>>;
105 | } ,, {
106 | '#val: {{k -> v ... k, v ->? val >< val$}};
107 | }
108 | '#t: t;
109 | }
110 |
111 | ## * { * <-iter.reduce(<-operator.xor, [//\/\\/\\\/\\\\/\\\\\/] + '#val); }
112 | ! * { * "Frozen($0)" --- ""?!('#val); }
113 | ->? element * { * element ->? '#val; }
114 | ... * { ... i ->? '#val { ** i; } }
115 | <<>> index * { * '#val<>; }
116 | >< other * { * '#val >< other; }
117 | :: other * { * '#val :: other.#val; }
118 | ? * { * / ? '#val ,, \; }
119 | $ * { * '#val$; }
120 | }
121 |
--------------------------------------------------------------------------------
/samarium/python.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from collections.abc import Callable
4 | from collections.abc import Iterable as PyIterable
5 | from enum import Enum as PyEnum
6 | from io import BufferedIOBase, BufferedReader, BufferedWriter, TextIOWrapper
7 | from types import FunctionType
8 |
9 | from samarium.classes import (
10 | NULL,
11 | Array,
12 | Attrs,
13 | Enum,
14 | File,
15 | Function,
16 | Iterator,
17 | Mode,
18 | Null,
19 | Num,
20 | Number,
21 | Slice,
22 | String,
23 | Table,
24 | Zip,
25 | )
26 | from samarium.utils import get_type_name
27 |
28 |
29 | class SliceRange:
30 | def __init__(self, slice_: Slice) -> None:
31 | self._slice = slice_
32 |
33 | @property
34 | def slice(self) -> slice:
35 | return self._slice.val
36 |
37 | @property
38 | def range(self) -> range:
39 | return self._slice.range
40 |
41 |
42 | def to_python(obj: object) -> object:
43 | if isinstance(obj, (String, Number, Zip, File)):
44 | return obj.val
45 | if isinstance(obj, Null):
46 | return None
47 | if isinstance(obj, Array):
48 | return [to_python(i) for i in obj.val]
49 | if isinstance(obj, Table):
50 | return {to_python(k): to_python(v) for k, v in obj.val.items()}
51 | if isinstance(obj, Slice):
52 | return SliceRange(obj)
53 | if isinstance(obj, Enum):
54 | o = {k.removeprefix("sm_"): to_python(v) for k, v in obj.members.items()}
55 | return PyEnum(obj.name, o)
56 | if isinstance(obj, Iterator):
57 | return map(to_python, obj)
58 | msg = f"Conversion for type {type(obj).__name__!r} not found"
59 | raise TypeError(msg)
60 |
61 |
62 | def to_samarium(obj: object) -> Attrs:
63 | if isinstance(obj, (int, bool, float)):
64 | return Num(obj)
65 | if isinstance(obj, str):
66 | return String(obj)
67 | if obj is None:
68 | return NULL
69 | if isinstance(obj, FunctionType):
70 | return Function(obj)
71 | if isinstance(obj, (list, tuple, set)):
72 | return Array([to_samarium(i) for i in obj])
73 | if isinstance(obj, dict):
74 | return Table({to_samarium(k): to_samarium(v) for k, v in obj.items()})
75 | if isinstance(obj, (range, slice)):
76 | return Slice(
77 | to_samarium(None if obj.start == 0 else obj.start),
78 | to_samarium(obj.stop),
79 | to_samarium(None if obj.step == 1 else obj.step),
80 | )
81 | if isinstance(obj, SliceRange):
82 | return obj._slice
83 | if isinstance(obj, (TextIOWrapper, BufferedWriter, BufferedReader)):
84 | return File(
85 | obj, Mode(obj.mode).name, obj.name, binary=isinstance(obj, BufferedIOBase)
86 | )
87 | if isinstance(obj, zip):
88 | return Zip(*obj)
89 | if isinstance(obj, type) and issubclass(obj, PyEnum):
90 | o = {f"sm_{k}": to_samarium(v) for k, v in obj.__members__.items()}
91 | return Enum(f"sm_{obj.__name__}", **o)
92 | if isinstance(obj, PyEnum):
93 | return to_samarium(obj.value)
94 | if isinstance(obj, PyIterable):
95 | return Iterator(obj)
96 | msg = f"Conversion for type {type(obj).__name__!r} not found"
97 | raise TypeError(msg)
98 |
99 |
100 | def export(func: Callable) -> Callable[..., Attrs]:
101 | """Wraps a Python function to be used in Samarium"""
102 |
103 | if not isinstance(func, FunctionType):
104 | msg = f"cannot export a non-function type {get_type_name(func)!r}"
105 | raise TypeError(msg)
106 |
107 | def wrapper(*_args: Attrs) -> Attrs:
108 | return to_samarium(func(*map(to_python, _args)))
109 |
110 | f = Function(wrapper)
111 | f.__pyexported__ = True
112 |
113 | return f
114 |
--------------------------------------------------------------------------------
/samarium/repl.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import json
4 | import string
5 | import sys
6 | from ast import literal_eval
7 | from contextlib import redirect_stderr, redirect_stdout, suppress
8 | from dataclasses import dataclass
9 | from datetime import datetime, timedelta
10 | from os import get_terminal_size
11 | from pathlib import Path
12 | from time import time
13 | from typing import TYPE_CHECKING, TypedDict
14 |
15 | if TYPE_CHECKING:
16 | from collections.abc import Iterator
17 |
18 | if sys.platform not in ("win32", "cygwin"):
19 | # used by input() to provide elaborate line editing & history features
20 | import readline # noqa: F401
21 |
22 | from crossandra import CrossandraError
23 |
24 | from samarium import core
25 | from samarium.exceptions import DAHLIA, SamariumSyntaxError, handle_exception
26 | from samarium.runtime import Runtime
27 | from samarium.tokenizer import tokenize
28 | from samarium.transpiler import Registry, match_brackets
29 | from samarium.utils import __version__
30 |
31 | TIME = {"t", "time"}
32 | HELP = {"?", "h", "help"}
33 | QUIT = {"!", "exit", "q", "quit", "exit!", "q!", "quit!"}
34 | COMMANDS = (
35 | {
36 | "3",
37 | "clear",
38 | "color",
39 | "debug",
40 | "restore",
41 | "run",
42 | "session",
43 | "undo",
44 | }
45 | | HELP
46 | | TIME
47 | | QUIT
48 | )
49 | NO_ARG_SESSION_SUBCMDS = {"delete-all", "list", "restore"}
50 |
51 | COLOR_TO_CODE = {
52 | "blue": "&1",
53 | "green": "&2",
54 | "cyan": "&3",
55 | "red": "&4",
56 | "purple": "&5",
57 | "orange": "&6",
58 | "light gray": "&7",
59 | "gray": "&8",
60 | "light blue": "&9",
61 | "black": "&0",
62 | "lime": "&a",
63 | "turquoise": "&b",
64 | "light red": "&c",
65 | "pink": "&d",
66 | "yellow": "&e",
67 | "default": "",
68 | }
69 | CODE_TO_COLOR = {v: k for k, v in COLOR_TO_CODE.items()}
70 |
71 | CACHE_DIR = Path.home() / ".cache" / "samarium"
72 | CONFIG_FILE = Path.home() / ".config" / "samarium" / "repl.yaml"
73 | PS1 = "--> "
74 | PS2 = " > "
75 |
76 |
77 | class Command:
78 | def __init__(
79 | self, *aliases: str, arg: str = "", sep: str = "|", msg: str = ""
80 | ) -> None:
81 | self.aliases = aliases
82 | self.arg = arg
83 | self.sep = sep
84 | self.msg = msg
85 |
86 | def __str__(self) -> str:
87 | return (
88 | self.sep.join(f"&e{a}&R" for a in self.aliases) + f" &7{self.arg}&R"
89 | ).ljust(28 + 4 * len(self.aliases)).lstrip() + self.msg
90 |
91 |
92 | HELP_TEXT = "\n".join(
93 | map(
94 | str,
95 | (
96 | Command("?", "h", "help", msg="shows this message"),
97 | Command("exit", "q", "quit", msg="saves the session and quits the repl"),
98 | Command("!", "exit!", "q!", "quit!", msg="force quits the repl"),
99 | Command("session", msg="manages sessions, see &2:? session&R for details"),
100 | Command("clear", msg="clears the screen"),
101 | Command(
102 | "color", msg="changes the prompt color, see &2:? color&R for details"
103 | ),
104 | Command("debug", msg="toggles debug mode"),
105 | Command("restore", msg="restores the previous session"),
106 | Command("t", "time", msg="times the execution of the following statement"),
107 | Command("undo", msg="undoes the last statement"),
108 | ),
109 | )
110 | )
111 |
112 | COLOR_HELP_TEXT = """
113 | &ecolor &7[color]&R
114 | &oproviding no color will reset it to the default one&R
115 |
116 | use &e:color save&R to save the current color to your config
117 |
118 | &00&R|&0black&R
119 | &11&R|&1blue&R
120 | &22&R|&2green&R
121 | &33&R|&3cyan&R
122 | &44&R|&4red&R
123 | &55&R|&5purple&R
124 | &66&R|&6orange&R
125 | &77&R|&7light gray&R
126 | &88&R|&8gray&R
127 | &99&R|&9light blue&R
128 | &aa&R|&alime&R
129 | &bb&R|&bturquoise&R
130 | &cc&R|&clight red&R
131 | &dd&R|&dpink&R
132 | &ee&R|&eyellow&R
133 | """
134 |
135 | SESSIONS_HELP_TEXT = "\n".join(
136 | map(
137 | str,
138 | (
139 | Command("session autosave", msg="toggles session autosave on exit"),
140 | Command("session delete-all", msg="deletes all saved sessions"),
141 | Command(
142 | "session lifetime",
143 | arg="[time]",
144 | msg=(
145 | "updates session lifetime (how long they stay cached)\n"
146 | f"{' ' * 24}&ocurrent value is shown when run without an argument&R"
147 | ),
148 | ),
149 | Command("session list", msg="lists all sessions and their sizes"),
150 | Command("session load", arg="", msg="loads a session"),
151 | Command("session restore", msg="restores the last unnamed session"),
152 | Command("session save", arg="[name]", msg="saves a session"),
153 | ),
154 | )
155 | )
156 |
157 | HELP_TEXT_INDEX = {
158 | "": HELP_TEXT,
159 | "session": SESSIONS_HELP_TEXT,
160 | "sessions": SESSIONS_HELP_TEXT,
161 | "color": COLOR_HELP_TEXT,
162 | }
163 |
164 |
165 | @dataclass
166 | class REPLConfig:
167 | color: str = ""
168 | autosave: bool = True
169 | session_lifetime: float = 30.0
170 |
171 | @classmethod
172 | def load(cls) -> REPLConfig:
173 | lines = CONFIG_FILE.read_text().splitlines()
174 | data = {
175 | k.strip(): literal_eval(v.strip())
176 | for k, _, v in (line.partition(":") for line in lines)
177 | }
178 | return cls(**data)
179 |
180 | def save(self) -> None:
181 | content = "\n".join(f"{k}: {v!r}" for k, v in vars(self).items())
182 | CONFIG_FILE.write_text(content)
183 |
184 |
185 | class SessionData(TypedDict):
186 | color: str
187 | debug: bool
188 | history: list[str]
189 |
190 |
191 | class Session:
192 | ALLOWED_CHARS = string.ascii_letters + string.digits + "-_"
193 |
194 | def __init__(
195 | self, color: str, *, debug: bool, history: list[str] | None = None
196 | ) -> None:
197 | self.color = color
198 | self.debug = debug
199 | self.history = history or []
200 |
201 | @property
202 | def color(self) -> str:
203 | return self._color
204 |
205 | @color.setter
206 | def color(self, value: str) -> None:
207 | if not value:
208 | self._color = ""
209 | elif (code := value.removeprefix("&")) in "0123456789abcde":
210 | self._color = f"&{code}"
211 | elif value in COLOR_TO_CODE:
212 | self._color = COLOR_TO_CODE[value]
213 | else:
214 | msg = f"unknown color {value!r}"
215 | raise ValueError(msg)
216 |
217 | @property
218 | def color_name(self) -> str:
219 | return CODE_TO_COLOR.get(self.color, "default")
220 |
221 | @property
222 | def kwargs(self) -> SessionData:
223 | return {
224 | "color": self.color_name,
225 | "debug": self.debug,
226 | "history": self.history,
227 | }
228 |
229 | def copy(self) -> Session:
230 | kw = self.kwargs
231 | kw["color"] = self._color
232 | s = Session(**kw)
233 | s.history = s.history.copy()
234 | return s
235 |
236 | def save(self, name: str = "") -> None:
237 | filename = name or datetime.now().strftime("%Y%m%d%H%M%S")
238 | data = json.dumps(self.kwargs)
239 | if not CACHE_DIR.exists():
240 | CACHE_DIR.mkdir(parents=True, exist_ok=True)
241 | (CACHE_DIR / f"{filename}.json").write_text(data)
242 |
243 | @classmethod
244 | def load(cls, name: str) -> Session:
245 | return cls(**json.loads((CACHE_DIR / f"{name}.json").read_text()))
246 |
247 | @staticmethod
248 | def is_valid_name(name: str) -> bool:
249 | return all(char in Session.ALLOWED_CHARS for char in name)
250 |
251 |
252 | class REPL:
253 | def __init__(self, *, debug: bool) -> None:
254 | if not CONFIG_FILE.exists():
255 | CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
256 | CONFIG_FILE.touch()
257 | self.config = REPLConfig.load()
258 | cache_cleanup(self.config.session_lifetime)
259 | self.registry = Registry(globals())
260 | self.session = Session(
261 | COLOR_TO_CODE.get(self.config.color, ""),
262 | debug=debug,
263 | )
264 |
265 | def load_session(self, session: Session, *, reset_registry: bool = False) -> None:
266 | if reset_registry:
267 | self.registry = Registry(globals())
268 | with (
269 | Path("/dev/null").open("w") as dev_null,
270 | redirect_stdout(dev_null),
271 | redirect_stderr(dev_null),
272 | ):
273 | for stmt in session.history:
274 | core.run(
275 | stmt,
276 | self.registry,
277 | "",
278 | debug=session.debug,
279 | load_template=False,
280 | repl=True,
281 | )
282 | self.session = session
283 |
284 | def finish(self) -> None:
285 | self.save()
286 | sys.exit()
287 |
288 | def save(self) -> None:
289 | if self.config.autosave:
290 | self.session.save()
291 |
292 | def handle_cmd(self, cmd: str, arg: str) -> str | None:
293 | if cmd in QUIT:
294 | if cmd.endswith("!"):
295 | sys.exit()
296 | self.finish()
297 | elif cmd in HELP:
298 | try:
299 | DAHLIA.print(HELP_TEXT_INDEX[arg].strip())
300 | except KeyError:
301 | repl_err(f"unknown help subsection {arg!r}")
302 | elif cmd == "color":
303 | if arg == "save":
304 | self.config.color = self.session.color_name
305 | self.config.save()
306 | return None
307 | try:
308 | self.session.color = arg
309 | except ValueError as e:
310 | repl_err(str(e))
311 | elif cmd == "debug":
312 | self.session.debug = not self.session.debug
313 | elif cmd in TIME:
314 | return arg
315 | elif cmd == "run":
316 | if not arg:
317 | repl_err("missing file")
318 | return None
319 | try:
320 | src = Path(arg).read_text()
321 | except FileNotFoundError:
322 | repl_err(f"file {arg!r} not found")
323 | return None
324 | core.run(
325 | src, self.registry, "", debug=self.session.debug, load_template=False
326 | )
327 | elif cmd == "undo":
328 | s = self.session.copy()
329 | s.history.pop()
330 | self.load_session(s, reset_registry=True)
331 | elif cmd == "session":
332 | if not arg:
333 | repl_err("missing subcommand")
334 | return None
335 | try:
336 | subcmd, arg = arg.split()
337 | except ValueError:
338 | subcmd, arg = arg, ""
339 | if subcmd in NO_ARG_SESSION_SUBCMDS and arg:
340 | repl_err(f"expected no arguments for subcommand {subcmd!r}")
341 | return None
342 | if subcmd == "delete-all":
343 | sessions = tuple(CACHE_DIR.glob("*"))
344 | if not sessions:
345 | repl_err("no sessions to delete")
346 | return None
347 | if (
348 | DAHLIA.input(
349 | "Are you sure you want to delete all"
350 | f" {len(sessions)} sessions? &7(Y/n) "
351 | ).casefold()
352 | == "n"
353 | ):
354 | return None
355 | size = 0
356 | for session in sessions:
357 | size += session.stat().st_size
358 | session.unlink()
359 | DAHLIA.print(f"&aRemoved {fmt_size(size)} of session files")
360 | elif subcmd == "autosave":
361 | if arg:
362 | if (arg := arg.casefold()) not in ("true", "false"):
363 | repl_err(f"invalid value {arg!r} (must be true/false)")
364 | return None
365 | self.config.autosave = arg == "true"
366 | self.config.save()
367 | if self.config.autosave:
368 | DAHLIA.print("&aAutosave enabled")
369 | else:
370 | DAHLIA.print("&cAutosave disabled")
371 | elif subcmd == "list":
372 | total_size = 0
373 | term_size = min(get_terminal_size().columns - 8, 48)
374 | for session in CACHE_DIR.glob("*"):
375 | size = session.stat().st_size
376 | total_size += size
377 | DAHLIA.print(
378 | f"{truncate(session.name, term_size)} &e({fmt_size(size)})"
379 | )
380 | DAHLIA.print("-" * term_size)
381 | DAHLIA.print(f"&l{'Total':<{term_size + 1}} &e{fmt_size(total_size)}\n")
382 | elif subcmd == "save":
383 | if not Session.is_valid_name(arg):
384 | repl_err(
385 | f"invalid session name {arg!r} "
386 | "(allowed chars: A..Z a..z 0..9 _ -])"
387 | )
388 | return None
389 | if (CACHE_DIR / f"{arg}.json").exists() and input(
390 | f"Session {arg!r} already exists. Replace? (y/N) "
391 | ).casefold() != "y":
392 | return None
393 | self.session.save(arg)
394 | DAHLIA.print(f"Saved session {arg!r}")
395 | elif subcmd == "load":
396 | if not Session.is_valid_name(arg):
397 | repl_err(
398 | f"invalid session name {arg!r} "
399 | "(allowed chars: A..Z a..z 0..9 _ -])"
400 | )
401 | return None
402 | if not (CACHE_DIR / f"{arg}.json").exists():
403 | repl_err(f"session {arg!r} not found")
404 | return None
405 | self.load_session(Session.load(arg))
406 | elif subcmd == "lifetime":
407 | if not arg:
408 | lifetime = str(self.config.session_lifetime).removesuffix(".0")
409 | DAHLIA.print(f"Current session lifetime is {lifetime} days")
410 | return None
411 | try:
412 | self.config.session_lifetime = float(arg)
413 | except ValueError:
414 | repl_err(f"invalid lifetime {arg!r} (must be a float)")
415 | return None
416 | self.config.save()
417 | DAHLIA.print(f"&aUpdated session lifetime to {arg} days")
418 | elif subcmd == "restore":
419 | try:
420 | most_recent_session = max(unnamed_sessions(), key=lambda p: p[1])
421 | except ValueError:
422 | repl_err("no sessions to restore")
423 | return None
424 | self.load_session(Session.load(most_recent_session[0].stem))
425 | DAHLIA.print(f"&aRestored latest session ({most_recent_session[1]})")
426 | else:
427 | repl_err(f"unknown session subcommand {subcmd!r}")
428 | elif cmd == "clear":
429 | print("\033[2J\033[H", end="")
430 | else: # :3
431 | DAHLIA.print("&dnya~")
432 | return None
433 |
434 | def read(self) -> str:
435 | stmt = ""
436 | prompt = PS1
437 | while True:
438 | stmt += DAHLIA.input(self.session.color + prompt)
439 | if not stmt:
440 | continue
441 | stmt += ";"
442 | try:
443 | error, stack = match_brackets(tokenize(stmt, repl=True))
444 | except CrossandraError as e:
445 | if '"' in str(e):
446 | error, stack = 1, []
447 | else:
448 | handle_exception(SamariumSyntaxError(str(e)))
449 | raise RuntimeError from None
450 |
451 | if not error:
452 | return stmt
453 | if error == -1:
454 | handle_exception(
455 | SamariumSyntaxError(
456 | f"missing opening bracket for {stack[-1].value}"
457 | )
458 | )
459 | raise RuntimeError from None
460 |
461 | prompt = PS2
462 | stmt = stmt[:-1] + "\n"
463 |
464 | def run(self) -> None:
465 | DAHLIA.print(
466 | f"{self.session.color}Samarium {__version__}"
467 | + " [DEBUG]" * self.session.debug
468 | )
469 | Runtime.repl = True
470 | while True:
471 | try:
472 | time_code = None
473 | stmt = self.read()
474 | if is_command(stmt):
475 | cmd = stmt[1:-1]
476 | if not cmd:
477 | repl_err("missing command")
478 | continue
479 | cmd, _, arg = cmd.partition(" ")
480 | if cmd not in COMMANDS:
481 | repl_err(f"unknown command {cmd!r}")
482 | continue
483 | time_code = self.handle_cmd(cmd, arg)
484 | if time_code is None:
485 | continue
486 | time_code += ";"
487 | elif not stmt.strip("; ").isidentifier():
488 | self.session.history.append(stmt)
489 | start = time()
490 | core.run(
491 | time_code or stmt,
492 | self.registry,
493 | "",
494 | debug=self.session.debug,
495 | load_template=False,
496 | repl=True,
497 | )
498 | if time_code:
499 | DAHLIA.print(f"&2{time() - start:.3f} seconds&R")
500 | self.registry.output = ""
501 | except RuntimeError:
502 | pass
503 | except KeyboardInterrupt:
504 | print()
505 | except (EOFError, SystemExit):
506 | break
507 | self.finish()
508 |
509 |
510 | def cache_cleanup(lifetime: float) -> None:
511 | threshold = datetime.now() - timedelta(days=lifetime)
512 | for file, date in unnamed_sessions():
513 | if date < threshold:
514 | file.unlink()
515 |
516 |
517 | def fmt_size(size: float) -> str:
518 | units = iter("KMGTP")
519 | u = ""
520 | while size > 1000:
521 | size /= 1000
522 | u = next(units)
523 | return f"{size:.1f}{u}B"
524 |
525 |
526 | def is_command(stmt: str) -> bool:
527 | if len(stmt) > 1:
528 | return stmt[0] == ":" != stmt[1]
529 | return True
530 |
531 |
532 | def repl_err(msg: str) -> None:
533 | DAHLIA.print(f"&4[REPLError] {msg}", file=sys.stderr)
534 |
535 |
536 | def truncate(string: str, length: int) -> str:
537 | if len(string) > length:
538 | return string[: length - 3] + "..."
539 | return string.ljust(length)
540 |
541 |
542 | def unnamed_sessions() -> Iterator[tuple[Path, datetime]]:
543 | for file in CACHE_DIR.glob("*"):
544 | with suppress(ValueError):
545 | yield (file, datetime.strptime(file.stem, "%Y%m%d%H%M%S"))
546 |
--------------------------------------------------------------------------------
/samarium/runtime.py:
--------------------------------------------------------------------------------
1 | class Runtime:
2 | repl = False
3 |
--------------------------------------------------------------------------------
/samarium/template.txt:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | __file__ = "{{SOURCE}}"
4 | {{CODE}}
5 | if __name__ == "samarium":
6 | try:
7 | entry
8 | except NameError:
9 | sys.exit()
10 | argc = entry.special()
11 | is_class = isinstance(entry, type)
12 | if is_class:
13 | argc -= 1
14 | if not argc.val:
15 | ex = entry()
16 | elif argc.val == 1:
17 | ex = entry(Array(map(String, sys.argv[1:])))
18 | else:
19 | raise exceptions.SamariumSyntaxError(
20 | "entry function should take 0 or 1 arguments"
21 | )
22 | if not is_class:
23 | sys.exit(ex.val)
24 |
--------------------------------------------------------------------------------
/samarium/tokenizer.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import re
4 | import sys
5 | from typing import Union, cast
6 |
7 | from crossandra import Crossandra, CrossandraError, Rule, common
8 |
9 | from samarium.exceptions import SamariumSyntaxError, handle_exception
10 | from samarium.tokens import Token
11 | from samarium.utils import convert_float
12 |
13 |
14 | def to_number(string: str) -> int | float:
15 | string = string.replace("/", "1").replace("\\", "0")
16 | if string == ".":
17 | string = "0."
18 | return convert_float(string, base=2, sep="`")
19 |
20 |
21 | Tokenlike = Union[Token, str, int]
22 |
23 | SM_BIT = r"[\\\/]"
24 |
25 | crossandra = Crossandra(
26 | Token,
27 | ignore_whitespace=True,
28 | rules=[
29 | Rule(r"==<.*>==", flags=re.M | re.S, ignore=True),
30 | Rule(r"==[^\n]*", flags=re.M | re.S, ignore=True),
31 | Rule(
32 | common.DOUBLE_QUOTED_STRING.pattern,
33 | lambda x: x.replace("\n", r"\n"),
34 | flags=re.S,
35 | ),
36 | Rule(rf"{SM_BIT}+`?{SM_BIT}*|`{SM_BIT}*", to_number),
37 | Rule(r"\w+"),
38 | ],
39 | )
40 |
41 |
42 | def tokenize(code: str, *, repl: bool = False) -> list[Tokenlike]:
43 | try:
44 | return cast(list[Tokenlike], crossandra.tokenize(code))
45 | except CrossandraError as e:
46 | if repl:
47 | raise
48 | errmsg = str(e)
49 | if '"' in errmsg:
50 | errmsg = "unclosed string literal"
51 | handle_exception(SamariumSyntaxError(errmsg))
52 | sys.exit()
53 |
--------------------------------------------------------------------------------
/samarium/tokens.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 |
3 |
4 | class Token(Enum):
5 | # Arithmetic
6 | ADD = "+"
7 | SUB = "-"
8 | MUL = "++"
9 | DIV = "--"
10 | POW = "+++"
11 | MOD = "---"
12 |
13 | # Comparison
14 | GE = ">:"
15 | GT = ">"
16 | LE = "<:"
17 | LT = "<"
18 | EQ = "::"
19 | NE = ":::"
20 |
21 | # Logical and Membership
22 | AND = "&&"
23 | IN = "->?"
24 | NOT = "~~"
25 | OR = "||"
26 | XOR = "^^" # TODO(trag1c): Implement in ≤2025 (optional)
27 |
28 | # Bitwise
29 | BAND = "&"
30 | BNOT = "~"
31 | BOR = "|"
32 | BXOR = "^"
33 |
34 | # Parens, Brackets and Braces
35 | BRACKET_OPEN = "["
36 | BRACKET_CLOSE = "]"
37 |
38 | BRACE_OPEN = "{"
39 | BRACE_CLOSE = "}"
40 |
41 | PAREN_OPEN = "("
42 | PAREN_CLOSE = ")"
43 |
44 | TABLE_OPEN = "{{"
45 | TABLE_CLOSE = "}}"
46 |
47 | # Control Flow
48 | CATCH = "!!"
49 | ELSE = ",,"
50 | FOR = "..."
51 | FROM = "<-"
52 | IF = "?"
53 | IMPORT = "<="
54 | THROW = "!!!"
55 | TO = "->"
56 | TRY = "??"
57 | WHILE = ".."
58 |
59 | # OOP / Functions
60 | CLASS = "@"
61 | DATACLASS = "@!"
62 | DEFAULT = "<>"
63 | FUNCTION = "*"
64 | INSTANCE = "'"
65 | ENTRY = "=>"
66 | YIELD = "**"
67 |
68 | # Slicing
69 | SLICE_OPEN = "<<"
70 | SLICE_CLOSE = ">>"
71 |
72 | # Object Manipulation
73 | CAST = "%"
74 | SPECIAL = "$"
75 | EXIT = "=>!"
76 | HASH = "##"
77 | PARENT = "!?"
78 | TYPE = "?!"
79 | READLINE = "???"
80 | PRINT = "!"
81 |
82 | # File I/O
83 | FILE_CREATE = "?~>"
84 | FILE_APPEND = "&~~>"
85 | FILE_READ = "<~~"
86 | FILE_WRITE = "~~>"
87 | FILE_READ_WRITE = "<~>"
88 | FILE_BINARY_APPEND = "&%~>"
89 | FILE_BINARY_READ = "<~%"
90 | FILE_BINARY_WRITE = "%~>"
91 | FILE_BINARY_READ_WRITE = "<%>"
92 | FILE_QUICK_APPEND = "&~>"
93 | FILE_QUICK_READ = "<~"
94 | FILE_QUICK_WRITE = "~>"
95 | FILE_QUICK_BINARY_APPEND = "&%>"
96 | FILE_QUICK_BINARY_READ = "<%"
97 | FILE_QUICK_BINARY_WRITE = "%>"
98 |
99 | # Other
100 | ENUM = "#"
101 | ASSIGN = ":"
102 | ATTR = "."
103 | UNIX_STMP = "@@"
104 | ARR_STMP = "@@@"
105 | END = ";"
106 | SEP = ","
107 | SLEEP = ",.,"
108 | ZIP = "><"
109 |
110 |
111 | FILE_IO_TOKENS = [
112 | token for name, token in Token.__members__.items() if name.startswith("FILE_")
113 | ]
114 |
115 | OPEN_TOKENS = [
116 | Token.BRACKET_OPEN,
117 | Token.BRACE_OPEN,
118 | Token.PAREN_OPEN,
119 | Token.TABLE_OPEN,
120 | Token.SLICE_OPEN,
121 | ]
122 |
123 | CLOSE_TOKENS = [
124 | Token.BRACKET_CLOSE,
125 | Token.BRACE_CLOSE,
126 | Token.PAREN_CLOSE,
127 | Token.TABLE_CLOSE,
128 | Token.SLICE_CLOSE,
129 | ]
130 |
--------------------------------------------------------------------------------
/samarium/utils.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from re import sub
4 | from typing import TYPE_CHECKING, Any, ClassVar, Generic, TypeVar
5 |
6 | from samarium.exceptions import SamariumTypeError, SamariumValueError
7 |
8 | if TYPE_CHECKING:
9 | from collections.abc import Callable
10 |
11 | __version__ = "0.6.2"
12 |
13 | T = TypeVar("T")
14 |
15 |
16 | KT = TypeVar("KT")
17 | VT = TypeVar("VT")
18 |
19 |
20 | class ClassProperty(Generic[T]):
21 | def __init__(self, func: Callable[..., T]) -> None:
22 | self.func = func
23 |
24 | def __get__(self, obj: object, owner: object = None) -> T:
25 | if obj is None:
26 | obj = owner
27 | return self.func(obj)
28 |
29 |
30 | class Singleton:
31 | _instances: ClassVar[dict[type[Singleton], Singleton]] = {}
32 |
33 | def __new__(cls, *args: Any, **kwargs: Any) -> Singleton:
34 | if cls not in cls._instances:
35 | cls._instances[cls] = super().__new__(cls, *args, **kwargs)
36 | return cls._instances[cls]
37 |
38 |
39 | def sysexit(*args: Any) -> None:
40 | if len(args) > 1:
41 | msg = "=>! only takes one argument"
42 | raise SamariumTypeError(msg)
43 | code = args[0].val if args else 0
44 | if not isinstance(code, (int, str)):
45 | msg = "=>! only accepts integers and strings"
46 | raise SamariumTypeError(msg)
47 | raise SystemExit(code)
48 |
49 |
50 | def convert_float(string: str, *, base: int, sep: str = ".") -> int | float:
51 | exp_sep = "p" if base == 16 else "e"
52 | if not (sep in string or exp_sep in string):
53 | return int(string, base)
54 | float_part, _, exp_part = string.partition(exp_sep)
55 | int_, _, dec = float_part.partition(sep)
56 | out = int(int_ or "0", base) + sum(
57 | int(v, base) * base**~i for i, v in enumerate(dec)
58 | )
59 | if exp_part:
60 | try:
61 | exponent = int(exp_part, 10 if base == 16 else base)
62 | except ValueError:
63 | msg = f"invalid exponent: {exp_part}"
64 | raise SamariumValueError(msg) from None
65 | out *= (2 if base == 16 else base) ** exponent
66 | return out
67 |
68 |
69 | def parse_number(string: str) -> tuple[int | float, bool]:
70 | string = string.strip()
71 | orig = string
72 | neg = len(string)
73 | b = "d"
74 | if ":" in string:
75 | b, _, string = string.partition(":")
76 | if b not in "box":
77 | msg = f"{b} is not a valid base"
78 | raise SamariumValueError(msg)
79 | base = {"b": 2, "o": 8, "x": 16, "d": 10}[b]
80 | string = string.lstrip("-")
81 | neg = -2 * ((neg - len(string)) % 2) + 1
82 |
83 | try:
84 | num = neg * convert_float(string, base=base)
85 | except ValueError:
86 | no_prefix = orig[2:] if orig[1] == ":" else orig
87 | msg = f'invalid string for Number with base {base}: "{no_prefix}"'
88 | raise SamariumValueError(msg) from None
89 | else:
90 | return num, isinstance(num, int) or num.is_integer()
91 |
92 |
93 | def smformat(string: str, fields: str | list[Any] | dict[Any, Any]) -> str:
94 | if isinstance(fields, str):
95 | fields = [fields]
96 | it = fields.items() if isinstance(fields, dict) else enumerate(fields)
97 | for k, v in it:
98 | string = sub(rf"(? str:
103 | return obj.__name__.removeprefix("sm_")
104 |
105 |
106 | def get_type_name(obj: Any) -> str:
107 | return type(obj).__name__.removeprefix("sm_")
108 |
--------------------------------------------------------------------------------