@mojir/lits 2.1.35 → 2.1.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +939 -165
- package/dist/cli/cli/src/modules/index.d.ts +4 -0
- package/dist/cli/cli/src/modules/sys/index.d.ts +3 -0
- package/dist/cli/cli.js +214 -57
- package/dist/cli/src/evaluator/ContextStack.d.ts +2 -2
- package/dist/cli/src/modules/index.d.ts +4 -0
- package/dist/cli/src/modules/sys/index.d.ts +3 -0
- package/dist/cli/src/parser/types.d.ts +3 -0
- package/dist/index.esm.js +135 -34
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +135 -34
- package/dist/index.js.map +1 -1
- package/dist/lits.iife.js +135 -34
- package/dist/lits.iife.js.map +1 -1
- package/dist/src/evaluator/ContextStack.d.ts +2 -2
- package/dist/src/parser/types.d.ts +3 -0
- package/dist/testFramework.esm.js +135 -34
- package/dist/testFramework.esm.js.map +1 -1
- package/dist/testFramework.js +135 -34
- package/dist/testFramework.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# Lits
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A functional language with algebraic notation and JavaScript interoperability.
|
|
4
4
|
|
|
5
5
|
Try it in the [Lits Playground](https://mojir.github.io/lits/).
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
9
|
- **Pure functional language** - Variables cannot be changed, ensuring predictable behavior and easier reasoning about code
|
|
10
|
+
- **Expression-based syntax** - Everything in Lits is an expression that returns a value; there are no statements, making the language highly composable and consistent
|
|
11
|
+
- **Fully serializable** - Every value returned from Lits evaluation, including functions and regexps, is serializable as JSON
|
|
10
12
|
- **JavaScript interoperability** - JavaScript values and functions can easily be exposed in Lits
|
|
11
13
|
- **First-class functions** - Functions are treated as values that can be passed to other functions
|
|
12
14
|
- **Algebraic notation** - All operators can be used as functions, and functions that take two parameters can be used as operators
|
|
@@ -56,7 +58,7 @@ The REPL provides an interactive environment where you can experiment with Lits
|
|
|
56
58
|
Here's a simple example to get you started:
|
|
57
59
|
|
|
58
60
|
```lits
|
|
59
|
-
// Defining a function
|
|
61
|
+
// Defining a function - note that everything returns a value
|
|
60
62
|
let square = x -> x * x;
|
|
61
63
|
|
|
62
64
|
// Using the function
|
|
@@ -68,10 +70,33 @@ let squares = [1, 2, 3, 4, 5] map square;
|
|
|
68
70
|
// => [1, 4, 9, 16, 25]
|
|
69
71
|
|
|
70
72
|
// Using operator as a function
|
|
71
|
-
|
|
73
|
+
+(1, 2, 3, 4, 5);
|
|
72
74
|
// => 15
|
|
73
75
|
```
|
|
74
76
|
|
|
77
|
+
## Expression-Based Language
|
|
78
|
+
|
|
79
|
+
In Lits, everything is an expression that evaluates to a value. This means:
|
|
80
|
+
|
|
81
|
+
```lits
|
|
82
|
+
// Conditional expressions always return a value
|
|
83
|
+
let a = 10;
|
|
84
|
+
let result = if a > 0 then "positive" else "non-positive" end;
|
|
85
|
+
|
|
86
|
+
// Function definitions are expressions that return the function
|
|
87
|
+
let add = (a, b) -> a + b;
|
|
88
|
+
|
|
89
|
+
// Even variable bindings return the bound value
|
|
90
|
+
let x = let y = 5; // x becomes 5
|
|
91
|
+
|
|
92
|
+
// Blocks are expressions - they return the last expression's value
|
|
93
|
+
let value = {
|
|
94
|
+
let temp = 42;
|
|
95
|
+
temp * 2 + 1 // => 85
|
|
96
|
+
};
|
|
97
|
+
```
|
|
98
|
+
This expression-based design makes Lits highly composable and eliminates the statement/expression distinction found in many other languages.
|
|
99
|
+
|
|
75
100
|
## Basic Syntax
|
|
76
101
|
|
|
77
102
|
### Data Types
|
|
@@ -87,24 +112,231 @@ let sum = +([1, 2, 3, 4, 5]);
|
|
|
87
112
|
|
|
88
113
|
// Strings
|
|
89
114
|
"Hello, world!";
|
|
115
|
+
"String with \"escapes\"";
|
|
90
116
|
|
|
91
117
|
// Booleans
|
|
92
118
|
true;
|
|
93
119
|
false;
|
|
94
120
|
|
|
95
|
-
//
|
|
121
|
+
// Functions
|
|
122
|
+
(x -> x * 2); // Anonymous function
|
|
123
|
+
let add = (a, b) -> a + b; // Named function
|
|
124
|
+
|
|
125
|
+
// Regular expressions
|
|
126
|
+
#"[a-z]+";
|
|
127
|
+
#"\d{3}-\d{3}-\d{4}";
|
|
128
|
+
|
|
129
|
+
// null
|
|
96
130
|
null;
|
|
131
|
+
```
|
|
97
132
|
|
|
98
|
-
|
|
99
|
-
|
|
133
|
+
#### Arrays (General Collections)
|
|
134
|
+
|
|
135
|
+
Arrays are the primary collection type in Lits, supporting mixed data types:
|
|
136
|
+
|
|
137
|
+
```lits
|
|
138
|
+
// Basic arrays
|
|
139
|
+
[1, 2, 3, 4, 5];
|
|
140
|
+
["apple", "banana", "orange"];
|
|
141
|
+
[true, 42, "mixed types"];
|
|
142
|
+
|
|
143
|
+
// Nested arrays
|
|
144
|
+
[[1, 2], [3, 4], [5, 6]];
|
|
145
|
+
[{name: "Alice"}, {name: "Bob"}];
|
|
146
|
+
|
|
147
|
+
// Array operations
|
|
148
|
+
let numbers = [1, 2, 3, 4, 5];
|
|
149
|
+
numbers[0]; // => 1 (indexing)
|
|
150
|
+
count(numbers); // => 5 (length)
|
|
151
|
+
first(numbers); // => 1
|
|
152
|
+
last(numbers); // => 5
|
|
153
|
+
rest(numbers); // => [2, 3, 4, 5]
|
|
154
|
+
|
|
155
|
+
// Functional array operations
|
|
156
|
+
numbers map -> $ * 2; // => [2, 4, 6, 8, 10]
|
|
157
|
+
numbers filter odd?; // => [1, 3, 5]
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
#### Vectors (Number Arrays)
|
|
100
161
|
|
|
101
|
-
|
|
162
|
+
A vector is simply a non-empty array containing only numbers. The `vec:` namespace provides mathematical operations specifically for these number arrays:
|
|
163
|
+
|
|
164
|
+
```lits
|
|
165
|
+
// Vectors are just number arrays
|
|
166
|
+
[1, 2, 3, 4, 5]; // This is a vector
|
|
167
|
+
[3.14, 2.71, 1.41]; // This is also a vector
|
|
168
|
+
[1, "hello", 3]; // This is NOT a vector (mixed types)
|
|
169
|
+
[]; // This is NOT a vector (empty)
|
|
170
|
+
|
|
171
|
+
// Vector creation functions
|
|
172
|
+
vec:zeros(5); // => [0, 0, 0, 0, 0]
|
|
173
|
+
vec:ones(3); // => [1, 1, 1]
|
|
174
|
+
vec:linspace(0, 10, 5); // => [0, 2.5, 5, 7.5, 10]
|
|
175
|
+
vec:fill(4, 3.14); // => [3.14, 3.14, 3.14, 3.14]
|
|
176
|
+
vec:generate(5, -> $ * 2); // => [0, 2, 4, 6, 8]
|
|
177
|
+
|
|
178
|
+
// Vector mathematical operations (use lin: namespace for vector math)
|
|
179
|
+
lin:dot([1, 2, 3], [4, 5, 6]); // => 32 (dot product)
|
|
180
|
+
lin:euclidean-norm([3, 4]); // => 5.0 (Euclidean norm/magnitude)
|
|
181
|
+
lin:normalize([3, 4]); // => [0.6, 0.8] (unit vector)
|
|
182
|
+
lin:euclidean-distance([0, 0], [3, 4]); // => 5.0 (Euclidean distance)
|
|
183
|
+
|
|
184
|
+
// Vector statistical operations
|
|
185
|
+
vec:sum([1, 2, 3, 4]); // => 10
|
|
186
|
+
vec:mean([1, 2, 3, 4]); // => 2.5
|
|
187
|
+
vec:median([1, 2, 3, 4, 5]); // => 3
|
|
188
|
+
vec:stdev([1, 2, 3, 4]); // => 1.29... (standard deviation)
|
|
189
|
+
vec:variance([1, 2, 3, 4]); // => 1.67... (variance)
|
|
190
|
+
|
|
191
|
+
// Vector analysis
|
|
192
|
+
vec:min([3, 1, 4, 1, 5]); // => 1
|
|
193
|
+
vec:max([3, 1, 4, 1, 5]); // => 5
|
|
194
|
+
vec:min-index([3, 1, 4]); // => 1 (index of minimum)
|
|
195
|
+
vec:max-index([3, 1, 4]); // => 2 (index of maximum)
|
|
196
|
+
|
|
197
|
+
// Cumulative operations
|
|
198
|
+
vec:cumsum([1, 2, 3, 4]); // => [1, 3, 6, 10]
|
|
199
|
+
vec:cumprod([1, 2, 3, 4]); // => [1, 2, 6, 24]
|
|
200
|
+
|
|
201
|
+
// Vector predicates
|
|
202
|
+
vec:increasing?([1, 1, 2, 3, 4]); // => true
|
|
203
|
+
vec:strictly-increasing?([1, 1, 2, 3, 4]); // => true
|
|
204
|
+
|
|
205
|
+
// Structural equality works with all vectors
|
|
206
|
+
[1, 2, 3] == [1, 2, 3]; // => true
|
|
207
|
+
[1, 2] == [1, 2, 3]; // => false
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
#### Matrices (2D Vectors)
|
|
211
|
+
|
|
212
|
+
A matrix is a 2D array where each row is a vector (non-empty array of numbers) and all rows have the same length. The `mat:` namespace provides linear algebra operations for these structures:
|
|
213
|
+
|
|
214
|
+
```lits
|
|
215
|
+
// Matrices are 2D number arrays with consistent row lengths
|
|
216
|
+
[[1, 2], [3, 4]]; // This is a 2x2 matrix
|
|
217
|
+
[[1, 2, 3], [4, 5, 6]]; // This is a 2x3 matrix
|
|
218
|
+
[[1, 2], [3, 4, 5]]; // This is NOT a matrix (inconsistent row length)
|
|
219
|
+
[[1, "hello"], [3, 4]]; // This is NOT a matrix (contains non-numbers)
|
|
220
|
+
[[]]; // This is NOT a matrix (contains empty row)
|
|
221
|
+
|
|
222
|
+
// Basic matrix operations
|
|
223
|
+
let matrixA = [[1, 2], [3, 4]];
|
|
224
|
+
let matrixB = [[5, 6], [7, 8]];
|
|
225
|
+
|
|
226
|
+
mat:mul(matrixA, matrixB); // => [[19, 22], [43, 50]] (multiplication)
|
|
227
|
+
mat:det(matrixA); // => -2 (determinant)
|
|
228
|
+
mat:inv(matrixA); // => [[-2, 1], [1.5, -0.5]] (inverse)
|
|
229
|
+
mat:trace(matrixA); // => 5 (trace - sum of diagonal)
|
|
230
|
+
|
|
231
|
+
// Matrix construction
|
|
232
|
+
mat:hilbert(3); // => 3x3 Hilbert matrix
|
|
233
|
+
mat:vandermonde([1, 2, 3]); // => Vandermonde matrix from vector
|
|
234
|
+
mat:band(4, 1, 1); // => 4x4 band matrix
|
|
235
|
+
|
|
236
|
+
// Matrix properties and predicates
|
|
237
|
+
mat:symmetric?([[1, 2], [2, 1]]); // => true
|
|
238
|
+
mat:invertible?([[1, 2], [3, 4]]); // => true
|
|
239
|
+
mat:square?([[1, 2], [3, 4]]); // => true
|
|
240
|
+
mat:diagonal?([[1, 0], [0, 2]]); // => true
|
|
241
|
+
mat:identity?([[1, 0], [0, 1]]); // => true
|
|
242
|
+
|
|
243
|
+
// Advanced matrix operations
|
|
244
|
+
mat:adj(matrixA); // => [[4, -2], [-3, 1]] (adjugate)
|
|
245
|
+
mat:cofactor(matrixA); // => cofactor matrix
|
|
246
|
+
mat:minor(matrixA, 0, 1); // => minor by removing row 0, col 1
|
|
247
|
+
mat:frobenius-norm(matrixA); // => Frobenius norm
|
|
248
|
+
mat:1-norm(matrixA); // => 1-norm (max column sum)
|
|
249
|
+
mat:inf-norm(matrixA); // => infinity norm (max row sum)
|
|
250
|
+
mat:max-norm(matrixA); // => max norm (largest absolute element)
|
|
251
|
+
|
|
252
|
+
// Matrix analysis
|
|
253
|
+
mat:rank(matrixA); // => matrix rank
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
#### Objects (Maps)
|
|
257
|
+
|
|
258
|
+
Objects store key-value pairs:
|
|
259
|
+
|
|
260
|
+
```lits
|
|
261
|
+
// Object creation
|
|
102
262
|
{ name: "John", age: 30 };
|
|
263
|
+
{ "key with spaces": "value", count: 42 };
|
|
103
264
|
|
|
104
|
-
//
|
|
105
|
-
|
|
265
|
+
// Nested objects
|
|
266
|
+
{
|
|
267
|
+
person: { name: "Alice", age: 25 },
|
|
268
|
+
scores: [95, 87, 92],
|
|
269
|
+
active: true
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
// Object operations
|
|
273
|
+
let user = { name: "Bob", age: 30, city: "NYC" };
|
|
274
|
+
get(user, "name"); // => "Bob"
|
|
275
|
+
assoc(user, "age", 31); // => new object with age updated
|
|
276
|
+
dissoc(user, "city"); // => new object without city
|
|
277
|
+
keys(user); // => ["name", "age", "city"]
|
|
278
|
+
vals(user); // => ["Bob", 30, "NYC"]
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
#### Type Predicates
|
|
282
|
+
|
|
283
|
+
Lits provides predicate functions to check data types at runtime:
|
|
284
|
+
|
|
285
|
+
```lits
|
|
286
|
+
// Basic type predicates
|
|
287
|
+
number?(42); // => true
|
|
288
|
+
string?("hello"); // => true
|
|
289
|
+
boolean?(true); // => true
|
|
290
|
+
function?(x -> x * 2); // => true
|
|
291
|
+
regexp?(#"[a-z]+"); // => true
|
|
292
|
+
array?([1, 2, 3]); // => true
|
|
293
|
+
object?({name: "Alice"}); // => true
|
|
294
|
+
null?(null); // => true
|
|
295
|
+
|
|
296
|
+
// Specialized array predicates
|
|
297
|
+
vector?([1, 2, 3]); // => true (non-empty number array)
|
|
298
|
+
vector?([1, "hello", 3]); // => false (mixed types)
|
|
299
|
+
vector?([]); // => false (empty)
|
|
300
|
+
|
|
301
|
+
matrix?([[1, 2], [3, 4]]); // => true (2D number array, consistent rows)
|
|
302
|
+
matrix?([[1, 2], [3]]); // => false (inconsistent row lengths)
|
|
303
|
+
matrix?([[]]); // => false (contains empty row)
|
|
304
|
+
|
|
305
|
+
// Collection predicates
|
|
306
|
+
seq?([1, 2, 3]); // => true (sequences: strings and arrays)
|
|
307
|
+
seq?("hello"); // => true
|
|
308
|
+
seq?({a: 1}); // => false
|
|
309
|
+
|
|
310
|
+
coll?([1, 2, 3]); // => true (collections: strings, arrays, objects)
|
|
311
|
+
coll?("hello"); // => true
|
|
312
|
+
coll?({a: 1}); // => true
|
|
313
|
+
coll?(42); // => false
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
#### Type Hierarchy
|
|
317
|
+
|
|
318
|
+
The type predicates follow a logical hierarchy:
|
|
319
|
+
|
|
320
|
+
```lits
|
|
321
|
+
// If something is a matrix, it's also a vector and an array
|
|
322
|
+
let mat = [[1, 2], [3, 4]];
|
|
323
|
+
matrix?(mat); // => true
|
|
324
|
+
vector?(mat); // => true (matrix is a special vector)
|
|
325
|
+
array?(mat); // => true (vector is a special array)
|
|
326
|
+
|
|
327
|
+
// If something is a vector, it's also an array
|
|
328
|
+
let vec = [1, 2, 3];
|
|
329
|
+
vector?(vec); // => true
|
|
330
|
+
array?(vec); // => true
|
|
331
|
+
|
|
332
|
+
// But not all arrays are vectors
|
|
333
|
+
let arr = [1, "hello", 3];
|
|
334
|
+
array?(arr); // => true
|
|
335
|
+
vector?(arr); // => false (contains non-numbers)
|
|
106
336
|
```
|
|
107
337
|
|
|
338
|
+
Each data type is immutable by design - operations return new values rather than modifying existing ones, ensuring predictable behavior and easier reasoning about code.
|
|
339
|
+
|
|
108
340
|
### Mathematical Constants
|
|
109
341
|
|
|
110
342
|
Lits provides predefined mathematical constants:
|
|
@@ -153,37 +385,130 @@ let y = {
|
|
|
153
385
|
|
|
154
386
|
#### Destructuring
|
|
155
387
|
|
|
388
|
+
##### Basic Object Destructuring
|
|
389
|
+
|
|
156
390
|
```lits
|
|
157
391
|
// Object destructuring
|
|
158
392
|
let { name, age } = { name: "John", age: 30 };
|
|
159
393
|
// name => "John", age => 30
|
|
160
394
|
```
|
|
161
395
|
|
|
396
|
+
```lits
|
|
397
|
+
// With default values
|
|
398
|
+
let { name = "Unknown", age = 0 } = { name: "John" };
|
|
399
|
+
// name => "John", age => 0
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
```lits
|
|
403
|
+
// Renaming with 'as'
|
|
404
|
+
let { name as userName } = { name: "Dave" };
|
|
405
|
+
// userName => "Dave"
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
```lits
|
|
409
|
+
// Multiple renames
|
|
410
|
+
let { firstName as name, age as years } = { firstName: "Eve", age: 28 };
|
|
411
|
+
// name => "Eve", years => 28
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
##### Advanced Destructuring Patterns
|
|
415
|
+
|
|
416
|
+
```lits
|
|
417
|
+
// Complex nested destructuring with defaults and renaming
|
|
418
|
+
let {
|
|
419
|
+
name as userName = "Guest",
|
|
420
|
+
profile: {
|
|
421
|
+
age = 0,
|
|
422
|
+
contact: { email as userEmail = "none" }
|
|
423
|
+
},
|
|
424
|
+
settings = { theme: "light" },
|
|
425
|
+
scores as userScores = [],
|
|
426
|
+
...others
|
|
427
|
+
} = { name: "Sam", profile: { contact: {} } };
|
|
428
|
+
// userName => "Sam", age => 0, userEmail => "none", etc.
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
```lits
|
|
432
|
+
// Combining array and object destructuring
|
|
433
|
+
let [{ name }, { age }] = [{ name: "Tina" }, { age: 33 }];
|
|
434
|
+
// name => "Tina", age => 33
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
```lits
|
|
438
|
+
// Object with array property destructuring
|
|
439
|
+
let { name, scores: [one, two] } = { name: "Uma", scores: [85, 92] };
|
|
440
|
+
// name => "Uma", one => 85, two => 92
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
##### Array Destructuring
|
|
444
|
+
|
|
162
445
|
```lits
|
|
163
446
|
// Array destructuring
|
|
164
|
-
let [a, b] = [1, 2, 3, 4];
|
|
165
|
-
// a =>
|
|
447
|
+
let [, , a, b] = [1, 2, 3, 4];
|
|
448
|
+
// a => 3, b => 4
|
|
166
449
|
```
|
|
167
450
|
|
|
168
451
|
```lits
|
|
169
|
-
//
|
|
170
|
-
let
|
|
171
|
-
//
|
|
452
|
+
// Array destructuring with defaults
|
|
453
|
+
let [one, two = 2] = [1];
|
|
454
|
+
// one => 1, two => 2
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
```lits
|
|
458
|
+
// Skipping elements
|
|
459
|
+
let [x, , z] = [1, 2, 3];
|
|
460
|
+
// x => 1, z => 3
|
|
172
461
|
```
|
|
173
462
|
|
|
463
|
+
##### Rest Patterns
|
|
464
|
+
|
|
174
465
|
```lits
|
|
175
|
-
//
|
|
466
|
+
// Array rest pattern
|
|
176
467
|
let [head, ...tail] = [1, 2, 3, 4];
|
|
177
468
|
// head => 1, tail => [2, 3, 4]
|
|
178
469
|
```
|
|
179
470
|
|
|
180
471
|
```lits
|
|
181
|
-
//
|
|
182
|
-
let
|
|
183
|
-
|
|
472
|
+
// Object rest pattern
|
|
473
|
+
let { name, ...otherProps } = { name: "John", age: 30, city: "NYC" };
|
|
474
|
+
// name => "John", otherProps => { age: 30, city: "NYC" }
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
```lits
|
|
478
|
+
// Empty rest patterns
|
|
479
|
+
let [only, ...empty] = [1];
|
|
480
|
+
// only => 1, empty => []
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
##### Function Parameter Destructuring
|
|
484
|
+
|
|
485
|
+
```lits
|
|
486
|
+
// Basic parameter destructuring
|
|
487
|
+
let greet = ({ name }) -> "Hello, " ++ name;
|
|
488
|
+
greet({ name: "Pat" });
|
|
489
|
+
// => "Hello, Pat"
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
```lits
|
|
493
|
+
// With defaults in parameters
|
|
494
|
+
let greet2 = ({ name = "friend" }) -> "Hello, " ++ name;
|
|
495
|
+
greet2({});
|
|
496
|
+
// => "Hello, friend"
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
```lits
|
|
500
|
+
// Nested parameter destructuring
|
|
501
|
+
let processUser = ({ profile: { name, age }}) ->
|
|
502
|
+
name ++ " is " ++ str(age);
|
|
503
|
+
processUser({ profile: { name: "Quinn", age: 29 }});
|
|
504
|
+
// => "Quinn is 29"
|
|
505
|
+
```
|
|
184
506
|
|
|
185
|
-
|
|
186
|
-
//
|
|
507
|
+
```lits
|
|
508
|
+
// Array parameter destructuring
|
|
509
|
+
let processCoords = ([x, y]) -> x + y;
|
|
510
|
+
processCoords([3, 4]);
|
|
511
|
+
// => 7
|
|
187
512
|
```
|
|
188
513
|
|
|
189
514
|
### Functions
|
|
@@ -220,55 +545,80 @@ let factorial = n ->
|
|
|
220
545
|
#### If/Unless
|
|
221
546
|
|
|
222
547
|
```lits
|
|
223
|
-
//
|
|
224
|
-
let x = !:random-int(0, 20); // Random number between 0 and 19
|
|
548
|
+
let x = 15; // Fixed value for compilation
|
|
225
549
|
|
|
226
550
|
if x > 10 then
|
|
227
551
|
"large"
|
|
228
552
|
else
|
|
229
553
|
"small"
|
|
230
554
|
end;
|
|
231
|
-
// => "large"
|
|
555
|
+
// => "large"
|
|
232
556
|
|
|
233
557
|
// If without else returns null
|
|
234
558
|
if false then "never" end;
|
|
235
559
|
// => null
|
|
236
560
|
|
|
237
561
|
// Unless (inverted if)
|
|
238
|
-
|
|
562
|
+
let y = 8;
|
|
563
|
+
unless y > 10 then
|
|
239
564
|
"small"
|
|
240
565
|
else
|
|
241
566
|
"large"
|
|
242
567
|
end;
|
|
243
|
-
// => "small"
|
|
568
|
+
// => "small"
|
|
244
569
|
```
|
|
245
570
|
|
|
246
571
|
#### Cond
|
|
247
572
|
|
|
248
573
|
```lits
|
|
249
|
-
let x =
|
|
574
|
+
let x = 12;
|
|
250
575
|
|
|
251
576
|
// Multi-branch conditional
|
|
252
577
|
cond
|
|
253
578
|
case x < 5 then "small"
|
|
254
579
|
case x < 10 then "medium"
|
|
255
580
|
case x < 15 then "large"
|
|
256
|
-
|
|
257
|
-
|
|
581
|
+
case true then "extra large" // default case
|
|
582
|
+
end;
|
|
583
|
+
// => "large"
|
|
584
|
+
|
|
585
|
+
// Cond with complex conditions
|
|
586
|
+
let urgent = true;
|
|
587
|
+
let important = false;
|
|
588
|
+
let priority = cond
|
|
589
|
+
case urgent && important then "critical"
|
|
590
|
+
case urgent then "high"
|
|
591
|
+
case important then "medium"
|
|
592
|
+
case true then "low"
|
|
593
|
+
end;
|
|
594
|
+
// => "high"
|
|
258
595
|
```
|
|
259
596
|
|
|
260
597
|
#### Switch
|
|
261
598
|
|
|
262
599
|
```lits
|
|
263
|
-
let x =
|
|
600
|
+
let x = 1;
|
|
264
601
|
|
|
265
602
|
// Switch on value
|
|
266
603
|
switch x
|
|
267
604
|
case 0 then "zero"
|
|
268
605
|
case 1 then "one"
|
|
269
606
|
case 2 then "two"
|
|
270
|
-
end
|
|
271
|
-
// => "
|
|
607
|
+
end;
|
|
608
|
+
// => "one"
|
|
609
|
+
|
|
610
|
+
// Switch with multiple cases
|
|
611
|
+
let userInput = "help";
|
|
612
|
+
let exit = () -> "exiting";
|
|
613
|
+
let showHelp = () -> "showing help";
|
|
614
|
+
let saveData = () -> "saving data";
|
|
615
|
+
|
|
616
|
+
switch userInput
|
|
617
|
+
case "quit" then exit()
|
|
618
|
+
case "help" then showHelp()
|
|
619
|
+
case "save" then saveData()
|
|
620
|
+
end;
|
|
621
|
+
// => "showing help"
|
|
272
622
|
```
|
|
273
623
|
|
|
274
624
|
### Loops and Iteration
|
|
@@ -280,15 +630,15 @@ end
|
|
|
280
630
|
for (x in [1, 2, 3, 4]) -> x * 2;
|
|
281
631
|
// => [2, 4, 6, 8]
|
|
282
632
|
|
|
283
|
-
// With filtering
|
|
633
|
+
// With filtering (when clause)
|
|
284
634
|
for (x in [1, 2, 3, 4] when odd?(x)) -> x * 2;
|
|
285
635
|
// => [2, 6]
|
|
286
636
|
|
|
287
|
-
// With while
|
|
637
|
+
// With early termination (while clause)
|
|
288
638
|
for (x in [1, 2, 3, 4] while x < 3) -> x * 2;
|
|
289
639
|
// => [2, 4]
|
|
290
640
|
|
|
291
|
-
// With let bindings
|
|
641
|
+
// With let bindings for intermediate calculations
|
|
292
642
|
for (x in [1, 2, 3] let doubled = x * 2) -> doubled + 1;
|
|
293
643
|
// => [3, 5, 7]
|
|
294
644
|
|
|
@@ -296,6 +646,20 @@ for (x in [1, 2, 3] let doubled = x * 2) -> doubled + 1;
|
|
|
296
646
|
for (x in [1, 2], y in [10, 20]) -> x + y;
|
|
297
647
|
// => [11, 21, 12, 22]
|
|
298
648
|
|
|
649
|
+
// Complex for comprehensions with multiple conditions
|
|
650
|
+
for (
|
|
651
|
+
i in range(10)
|
|
652
|
+
let ii = i ^ 2
|
|
653
|
+
while ii < 40
|
|
654
|
+
when ii % 3 == 0,
|
|
655
|
+
j in range(10)
|
|
656
|
+
when j % 2 == 1
|
|
657
|
+
) -> ii + j;
|
|
658
|
+
|
|
659
|
+
// Using previous bindings in subsequent iterations
|
|
660
|
+
for (x in [1, 2], y in [x, 2 * x]) -> x * y;
|
|
661
|
+
// => [1, 2, 4, 8]
|
|
662
|
+
|
|
299
663
|
// Object iteration
|
|
300
664
|
for (entry in { a: 1, b: 2 } let [key, value] = entry) -> key ++ ":" ++ str(value);
|
|
301
665
|
// => ["a:1", "b:2"]
|
|
@@ -319,8 +683,41 @@ loop (n = 5, acc = 1) -> {
|
|
|
319
683
|
else
|
|
320
684
|
recur(n - 1, acc * n)
|
|
321
685
|
end
|
|
322
|
-
}
|
|
686
|
+
};
|
|
323
687
|
// => 120 (factorial of 5)
|
|
688
|
+
|
|
689
|
+
// Complex loop with multiple variables
|
|
690
|
+
loop (items = [1, 2, 3, 4, 5], sum = 0, cnt = 0) -> {
|
|
691
|
+
if empty?(items) then
|
|
692
|
+
{ sum: sum, average: sum / cnt }
|
|
693
|
+
else
|
|
694
|
+
recur(rest(items), sum + first(items), cnt + 1)
|
|
695
|
+
end
|
|
696
|
+
};
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
### Recursion with Recur
|
|
700
|
+
|
|
701
|
+
#### Function Recursion
|
|
702
|
+
|
|
703
|
+
```lits
|
|
704
|
+
// Simple recursive function with recur
|
|
705
|
+
let factorial = (n) -> {
|
|
706
|
+
if n <= 1 then
|
|
707
|
+
1
|
|
708
|
+
else
|
|
709
|
+
n * recur(n - 1)
|
|
710
|
+
end
|
|
711
|
+
};
|
|
712
|
+
|
|
713
|
+
// Tail-recursive function
|
|
714
|
+
let sumToN = (n, acc = 0) -> {
|
|
715
|
+
if zero?(n) then
|
|
716
|
+
acc
|
|
717
|
+
else
|
|
718
|
+
recur(n - 1, acc + n)
|
|
719
|
+
end
|
|
720
|
+
};
|
|
324
721
|
```
|
|
325
722
|
|
|
326
723
|
### Error Handling
|
|
@@ -329,6 +726,7 @@ loop (n = 5, acc = 1) -> {
|
|
|
329
726
|
|
|
330
727
|
```lits
|
|
331
728
|
// Basic try/catch
|
|
729
|
+
let riskyOperation = () -> throw("Something went wrong");
|
|
332
730
|
try
|
|
333
731
|
riskyOperation()
|
|
334
732
|
catch
|
|
@@ -341,6 +739,16 @@ try
|
|
|
341
739
|
catch (error)
|
|
342
740
|
"Error: " ++ error.message
|
|
343
741
|
end;
|
|
742
|
+
|
|
743
|
+
// Try-catch for graceful degradation
|
|
744
|
+
let parseData = () -> { value: 42 };
|
|
745
|
+
let process = (val) -> val * 2;
|
|
746
|
+
try
|
|
747
|
+
let { value } = parseData();
|
|
748
|
+
process(value)
|
|
749
|
+
catch
|
|
750
|
+
"Using default value"
|
|
751
|
+
end;
|
|
344
752
|
```
|
|
345
753
|
|
|
346
754
|
#### Throw
|
|
@@ -348,18 +756,147 @@ end;
|
|
|
348
756
|
```lits
|
|
349
757
|
// Throwing errors
|
|
350
758
|
try
|
|
351
|
-
throw("Custom error message")
|
|
759
|
+
throw("Custom error message")
|
|
352
760
|
catch
|
|
353
761
|
"Caught an error"
|
|
354
762
|
end;
|
|
355
763
|
|
|
356
|
-
//
|
|
764
|
+
// Custom error messages in functions
|
|
357
765
|
let divide = (a, b) ->
|
|
358
766
|
if zero?(b) then
|
|
359
|
-
throw("
|
|
767
|
+
throw("Cannot divide by zero")
|
|
360
768
|
else
|
|
361
769
|
a / b
|
|
362
770
|
end;
|
|
771
|
+
|
|
772
|
+
// Conditional error throwing
|
|
773
|
+
let validateAge = (age) ->
|
|
774
|
+
cond
|
|
775
|
+
case age < 0 then throw("Age cannot be negative")
|
|
776
|
+
case age > 150 then throw("Age seems unrealistic")
|
|
777
|
+
case true then age
|
|
778
|
+
end;
|
|
779
|
+
```
|
|
780
|
+
|
|
781
|
+
### Block Expressions
|
|
782
|
+
|
|
783
|
+
```lits
|
|
784
|
+
// Block for grouping expressions
|
|
785
|
+
let computeX = () -> 5;
|
|
786
|
+
let computeY = () -> 10;
|
|
787
|
+
let processResult = (z) -> z * 2;
|
|
788
|
+
|
|
789
|
+
let result = {
|
|
790
|
+
let x = computeX();
|
|
791
|
+
let y = computeY();
|
|
792
|
+
let z = x * y;
|
|
793
|
+
processResult(z)
|
|
794
|
+
};
|
|
795
|
+
|
|
796
|
+
// Block with side effects
|
|
797
|
+
let loadData = () -> [1, 2, 3];
|
|
798
|
+
let processData = (data) -> data map -> $ * 2;
|
|
799
|
+
|
|
800
|
+
{
|
|
801
|
+
write!("Starting process...");
|
|
802
|
+
let data = loadData();
|
|
803
|
+
let processed = processData(data);
|
|
804
|
+
write!("Process completed");
|
|
805
|
+
processed
|
|
806
|
+
}
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
### Array and Object Construction
|
|
810
|
+
|
|
811
|
+
#### Array Construction
|
|
812
|
+
|
|
813
|
+
```lits
|
|
814
|
+
// Array literal
|
|
815
|
+
[1, 2, 3, 4];
|
|
816
|
+
|
|
817
|
+
// Array function
|
|
818
|
+
array(1, 2, 3, 4);
|
|
819
|
+
|
|
820
|
+
// With spread
|
|
821
|
+
let small-set = [3, 4, 5];
|
|
822
|
+
[1, 2, ...small-set, 6];
|
|
823
|
+
// => [1, 2, 3, 4, 5, 6]
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
#### Array Spread
|
|
827
|
+
|
|
828
|
+
```lits
|
|
829
|
+
// Spread in array literals
|
|
830
|
+
let combined = [1, 2, ...[3, 4, 5], 6];
|
|
831
|
+
// => [1, 2, 3, 4, 5, 6]
|
|
832
|
+
|
|
833
|
+
// Multiple spreads
|
|
834
|
+
let start = [1, 2];
|
|
835
|
+
let middle = [3, 4];
|
|
836
|
+
let stop = [5, 6];
|
|
837
|
+
let result = [...start, ...middle, ...stop];
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
#### Object Construction
|
|
841
|
+
|
|
842
|
+
```lits
|
|
843
|
+
// Object literal with static keys
|
|
844
|
+
{ name: "John", age: 30 };
|
|
845
|
+
|
|
846
|
+
// Object literal with dynamic keys using bracket notation
|
|
847
|
+
let keyName = "dynamic";
|
|
848
|
+
{ [keyName]: "value", ["computed" ++ "Key"]: 42 };
|
|
849
|
+
// => { dynamic: "value", computedKey: 42 }
|
|
850
|
+
|
|
851
|
+
// Object function
|
|
852
|
+
object("name", "John", "age", 30);
|
|
853
|
+
|
|
854
|
+
// With spread
|
|
855
|
+
let defaults = { type: "Person", active: true };
|
|
856
|
+
{
|
|
857
|
+
...defaults,
|
|
858
|
+
name: "John",
|
|
859
|
+
age: 30
|
|
860
|
+
};
|
|
861
|
+
// => { type: "Person", active: true, name: "John", age: 30 }
|
|
862
|
+
|
|
863
|
+
// Combining static and dynamic keys
|
|
864
|
+
let propName = "score";
|
|
865
|
+
{
|
|
866
|
+
id: 123,
|
|
867
|
+
[propName]: 95,
|
|
868
|
+
["level" ++ "Number"]: 5
|
|
869
|
+
};
|
|
870
|
+
// => { id: 123, score: 95, levelNumber: 5 }
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
#### Object Spread
|
|
874
|
+
|
|
875
|
+
```lits
|
|
876
|
+
// Object spread for merging
|
|
877
|
+
let person = {
|
|
878
|
+
name: "John",
|
|
879
|
+
age: 30
|
|
880
|
+
};
|
|
881
|
+
|
|
882
|
+
let employee = {
|
|
883
|
+
...person,
|
|
884
|
+
id: "E123",
|
|
885
|
+
department: "Engineering"
|
|
886
|
+
};
|
|
887
|
+
// => { name: "John", age: 30, id: "E123", department: "Engineering" }
|
|
888
|
+
|
|
889
|
+
// Spread with override
|
|
890
|
+
let defaults = {
|
|
891
|
+
name: "Default Name",
|
|
892
|
+
theme: "light",
|
|
893
|
+
active: true
|
|
894
|
+
};
|
|
895
|
+
|
|
896
|
+
let updated = {
|
|
897
|
+
...defaults,
|
|
898
|
+
name: "Custom Name" // Override defaults.name
|
|
899
|
+
};
|
|
363
900
|
```
|
|
364
901
|
|
|
365
902
|
### Logical Operators
|
|
@@ -385,77 +922,134 @@ true || "never reached"; // => true
|
|
|
385
922
|
```lits
|
|
386
923
|
// Null coalescing operator
|
|
387
924
|
null ?? "default"; // => "default"
|
|
388
|
-
undefined ?? "default"; // => "default"
|
|
389
925
|
0 ?? "default"; // => 0 (only null/undefined are coalesced)
|
|
390
926
|
false ?? "default"; // => false
|
|
391
927
|
"" ?? "default"; // => ""
|
|
392
928
|
```
|
|
393
929
|
|
|
394
|
-
###
|
|
930
|
+
### Ternary Operator
|
|
395
931
|
|
|
396
932
|
```lits
|
|
397
|
-
//
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
933
|
+
// Conditional expression
|
|
934
|
+
let age = 25;
|
|
935
|
+
let result = age >= 18 ? "adult" : "minor";
|
|
936
|
+
|
|
937
|
+
// Nested ternary
|
|
938
|
+
let score = 85;
|
|
939
|
+
let category = score >= 90 ? "A" : score >= 80 ? "B" : "C";
|
|
940
|
+
|
|
941
|
+
// With complex expressions
|
|
942
|
+
let isLoggedIn = () -> true;
|
|
943
|
+
let hasPermission = () -> true;
|
|
944
|
+
let status = isLoggedIn() && hasPermission() ? "authorized" : "unauthorized";
|
|
404
945
|
```
|
|
405
946
|
|
|
406
|
-
|
|
947
|
+
## Variable Names
|
|
407
948
|
|
|
408
|
-
|
|
949
|
+
Lits is generous with variable naming conventions, allowing a wide range of characters that would be invalid in many other programming languages.
|
|
409
950
|
|
|
410
|
-
|
|
411
|
-
// Array literal
|
|
412
|
-
[1, 2, 3, 4];
|
|
951
|
+
### Basic Rules
|
|
413
952
|
|
|
414
|
-
|
|
415
|
-
array(1, 2, 3, 4);
|
|
953
|
+
Variable names in Lits can contain almost any character except for a small set of reserved symbols. The only restrictions are:
|
|
416
954
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
955
|
+
**Illegal characters anywhere in a variable name:**
|
|
956
|
+
- Parentheses: `(` `)`
|
|
957
|
+
- Brackets: `[` `]`
|
|
958
|
+
- Braces: `{` `}`
|
|
959
|
+
- Quotes: `'` `"` `` ` ``
|
|
960
|
+
- Punctuation: `,` `.` `;`
|
|
961
|
+
- Whitespace: spaces, newlines, tabs
|
|
422
962
|
|
|
423
|
-
|
|
963
|
+
**Additional restrictions for the first character:**
|
|
964
|
+
- Cannot start with digits `0-9`
|
|
965
|
+
|
|
966
|
+
### Unicode and Emoji Support
|
|
967
|
+
|
|
968
|
+
Beyond these minimal restrictions, Lits supports Unicode characters, including emojis, in variable names:
|
|
424
969
|
|
|
425
970
|
```lits
|
|
426
|
-
//
|
|
427
|
-
|
|
971
|
+
// Unicode characters are welcome
|
|
972
|
+
let résultat = 42;
|
|
973
|
+
let naïve = "simple approach";
|
|
974
|
+
let coöperation = "working together";
|
|
975
|
+
|
|
976
|
+
// Emojis work too!
|
|
977
|
+
let 😅 = "grinning face with sweat";
|
|
978
|
+
let 🚀 = "rocket ship";
|
|
979
|
+
let result = 😅 ++ " " ++ 🚀;
|
|
980
|
+
// => "grinning face with sweat rocket ship"
|
|
981
|
+
```
|
|
428
982
|
|
|
429
|
-
|
|
430
|
-
object("name", "John", "age", 30);
|
|
983
|
+
### Quoted Variable Names
|
|
431
984
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
985
|
+
For cases where you need to use the normally illegal characters in variable names, Lits supports quoted variable names using single quotes:
|
|
986
|
+
|
|
987
|
+
```lits
|
|
988
|
+
// Variables with spaces and special characters
|
|
989
|
+
let 'A strange variable' = 42;
|
|
990
|
+
let 'user.name' = "John Doe";
|
|
991
|
+
let 'data[0]' = "first element";
|
|
992
|
+
let 'function()' = "callable";
|
|
993
|
+
|
|
994
|
+
// Access them the same way
|
|
995
|
+
'A strange variable' + 8;
|
|
996
|
+
// => 50
|
|
440
997
|
```
|
|
441
998
|
|
|
442
|
-
###
|
|
999
|
+
### Practical Examples
|
|
443
1000
|
|
|
444
|
-
|
|
1001
|
+
Here are some examples showcasing the flexibility of Lits variable naming:
|
|
445
1002
|
|
|
446
1003
|
```lits
|
|
447
|
-
//
|
|
448
|
-
let
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
1004
|
+
// Mathematical notation with Greek letters (avoiding reserved symbols)
|
|
1005
|
+
let α = 0.5;
|
|
1006
|
+
let β = 1.2;
|
|
1007
|
+
let γ = 2.0;
|
|
1008
|
+
let Δ = β - α;
|
|
1009
|
+
|
|
1010
|
+
// Descriptive names with special characters
|
|
1011
|
+
let user-name = "alice";
|
|
1012
|
+
let is-valid? = true;
|
|
1013
|
+
let counter! = 0;
|
|
1014
|
+
|
|
1015
|
+
// Mixed styles
|
|
1016
|
+
let dataSet₁ = [1, 2, 3];
|
|
1017
|
+
let dataSet₂ = [4, 5, 6];
|
|
1018
|
+
let 🔧config = { debug: true };
|
|
1019
|
+
```
|
|
1020
|
+
|
|
1021
|
+
### Important: Operator Spacing
|
|
454
1022
|
|
|
455
|
-
|
|
456
|
-
|
|
1023
|
+
Due to Lits' flexible variable naming, **operators must be separated by whitespace**. This is crucial to understand:
|
|
1024
|
+
|
|
1025
|
+
```lits
|
|
1026
|
+
// This is a variable name, NOT addition!
|
|
1027
|
+
let x+1 = 42;
|
|
1028
|
+
let result1 = x+1; // => 42
|
|
1029
|
+
|
|
1030
|
+
// To perform addition, use spaces
|
|
1031
|
+
let x = 5;
|
|
1032
|
+
let result2 = x + 1; // => 6
|
|
1033
|
+
|
|
1034
|
+
// More examples of what looks like operations but are actually variable names
|
|
1035
|
+
let a-b = "subtraction variable";
|
|
1036
|
+
let c*d = "multiplication variable";
|
|
1037
|
+
let e/f = "division variable";
|
|
1038
|
+
let g<h = "comparison variable";
|
|
1039
|
+
|
|
1040
|
+
// To use these as actual operations, add spaces
|
|
1041
|
+
let a = 10;
|
|
1042
|
+
let b = 3;
|
|
1043
|
+
let a-sum = a + b; // Addition
|
|
1044
|
+
let a-diff = a - b; // Subtraction
|
|
1045
|
+
let a-prod = a * b; // Multiplication
|
|
1046
|
+
let a-quot = a / b; // Division
|
|
1047
|
+
let a-comp = a < b; // Comparison
|
|
457
1048
|
```
|
|
458
1049
|
|
|
1050
|
+
Without whitespace, Lits treats the entire sequence as a single variable identifier. This applies to all operators, including comparison operators, logical operators, and arithmetic operators.
|
|
1051
|
+
|
|
1052
|
+
This flexibility allows for expressive and readable code while maintaining the functional programming paradigm that Lits embodies.
|
|
459
1053
|
## Operators and Functions
|
|
460
1054
|
|
|
461
1055
|
### Algebraic Notation
|
|
@@ -495,6 +1089,123 @@ let divide = /(10, _);
|
|
|
495
1089
|
divide(2); // => 5
|
|
496
1090
|
```
|
|
497
1091
|
|
|
1092
|
+
### Data Types as Functions
|
|
1093
|
+
|
|
1094
|
+
Lits allows arrays, objects, numbers, and strings to be used as functions. This creates elegant, flexible code where data structures become accessors.
|
|
1095
|
+
|
|
1096
|
+
#### Arrays and Numbers as Index Accessors
|
|
1097
|
+
|
|
1098
|
+
Arrays can be called with an index to get an element, and numbers can be called with collections to access that index:
|
|
1099
|
+
|
|
1100
|
+
```lits
|
|
1101
|
+
let arr = [10, 20, 30, 40];
|
|
1102
|
+
|
|
1103
|
+
// Array as function (accessing by index)
|
|
1104
|
+
arr(0); // => 10
|
|
1105
|
+
arr(2); // => 30
|
|
1106
|
+
|
|
1107
|
+
// Number as function (accessing array at that index)
|
|
1108
|
+
2(arr); // => 30 (same as arr(2))
|
|
1109
|
+
0(arr); // => 10 (same as arr(0))
|
|
1110
|
+
```
|
|
1111
|
+
|
|
1112
|
+
#### Strings and Numbers for Character Access
|
|
1113
|
+
|
|
1114
|
+
Similar to arrays, strings support indexed access in both directions:
|
|
1115
|
+
|
|
1116
|
+
```lits
|
|
1117
|
+
let name = "Albert";
|
|
1118
|
+
|
|
1119
|
+
// String as function (accessing character by index)
|
|
1120
|
+
name(0); // => "A"
|
|
1121
|
+
name(2); // => "b"
|
|
1122
|
+
|
|
1123
|
+
// Number as function (accessing string at that index)
|
|
1124
|
+
2(name); // => "b" (same as name(2))
|
|
1125
|
+
4(name); // => "r" (same as name(4))
|
|
1126
|
+
```
|
|
1127
|
+
|
|
1128
|
+
#### Objects and Strings as Property Accessors
|
|
1129
|
+
|
|
1130
|
+
Objects can be called with property names, and strings can be called with objects to access properties:
|
|
1131
|
+
|
|
1132
|
+
```lits
|
|
1133
|
+
let person = { foo: 1, bar: 2, name: "John" };
|
|
1134
|
+
|
|
1135
|
+
// Object as function (accessing property by key)
|
|
1136
|
+
person("foo"); // => 1
|
|
1137
|
+
person("name"); // => "John"
|
|
1138
|
+
|
|
1139
|
+
// String as function (accessing object property)
|
|
1140
|
+
"foo"(person); // => 1 (same as person("foo"))
|
|
1141
|
+
"bar"(person); // => 2 (same as person("bar"))
|
|
1142
|
+
```
|
|
1143
|
+
|
|
1144
|
+
#### Powerful Higher-Order Function Applications
|
|
1145
|
+
|
|
1146
|
+
This feature makes higher-order functions incredibly flexible. You can pass data directly as accessor functions:
|
|
1147
|
+
|
|
1148
|
+
```lits
|
|
1149
|
+
let data = [
|
|
1150
|
+
{ name: "Alice", score: 95 },
|
|
1151
|
+
{ name: "Bob", score: 87 },
|
|
1152
|
+
{ name: "Carol", score: 92 }
|
|
1153
|
+
];
|
|
1154
|
+
|
|
1155
|
+
// Extract names using string as function
|
|
1156
|
+
data map "name";
|
|
1157
|
+
// => ["Alice", "Bob", "Carol"]
|
|
1158
|
+
|
|
1159
|
+
// Extract scores using string as function
|
|
1160
|
+
data map "score";
|
|
1161
|
+
// => [95, 87, 92]
|
|
1162
|
+
|
|
1163
|
+
// Get second element of multiple arrays using number as function
|
|
1164
|
+
let arrays = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
|
|
1165
|
+
arrays map 1;
|
|
1166
|
+
// => [2, 5, 8]
|
|
1167
|
+
|
|
1168
|
+
// Access nested data
|
|
1169
|
+
let records = [
|
|
1170
|
+
{ values: [10, 20, 30] },
|
|
1171
|
+
{ values: [40, 50, 60] },
|
|
1172
|
+
{ values: [70, 80, 90] }
|
|
1173
|
+
];
|
|
1174
|
+
|
|
1175
|
+
// Get first value from each record's values array
|
|
1176
|
+
records map "values" map 0;
|
|
1177
|
+
// => [10, 40, 70]
|
|
1178
|
+
```
|
|
1179
|
+
|
|
1180
|
+
#### Practical Examples
|
|
1181
|
+
|
|
1182
|
+
```lits
|
|
1183
|
+
// Matrix column extraction
|
|
1184
|
+
let matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
|
|
1185
|
+
matrix map 1; // => [2, 5, 8] (second column)
|
|
1186
|
+
|
|
1187
|
+
// Object property extraction
|
|
1188
|
+
let users = [
|
|
1189
|
+
{ id: 1, active: true },
|
|
1190
|
+
{ id: 2, active: false },
|
|
1191
|
+
{ id: 3, active: true }
|
|
1192
|
+
];
|
|
1193
|
+
users map "active"; // => [true, false, true]
|
|
1194
|
+
|
|
1195
|
+
// String character extraction
|
|
1196
|
+
let words = ["hello", "world", "test"];
|
|
1197
|
+
words map 0; // => ["h", "w", "t"] (first characters)
|
|
1198
|
+
|
|
1199
|
+
// Complex data navigation
|
|
1200
|
+
let sales = [
|
|
1201
|
+
{ quarter: "Q1", regions: { north: 100, south: 200 } },
|
|
1202
|
+
{ quarter: "Q2", regions: { north: 150, south: 180 } }
|
|
1203
|
+
];
|
|
1204
|
+
sales map "regions" map "north"; // => [100, 150]
|
|
1205
|
+
```
|
|
1206
|
+
|
|
1207
|
+
This feature eliminates the need for verbose accessor functions and makes data transformation pipelines more concise and readable.
|
|
1208
|
+
|
|
498
1209
|
### Parameter Order
|
|
499
1210
|
|
|
500
1211
|
Lits favors subject-first parameter order for better operator chaining:
|
|
@@ -513,7 +1224,7 @@ The pipe operator `|>` passes the result of the left expression as the first arg
|
|
|
513
1224
|
|
|
514
1225
|
```lits
|
|
515
1226
|
// Without pipe operator
|
|
516
|
-
reduce(map(filter([1, 2, 3, 4, 5, 6], odd?), -> $
|
|
1227
|
+
reduce(map(filter([1, 2, 3, 4, 5, 6], odd?), -> $ * $), +, 0);
|
|
517
1228
|
|
|
518
1229
|
// With pipe operator (much more readable)
|
|
519
1230
|
[1, 2, 3, 4, 5, 6]
|
|
@@ -546,6 +1257,85 @@ reduce(map(filter([1, 2, 3, 4, 5, 6], odd?), -> $ + $), +, 0);
|
|
|
546
1257
|
// => 18 (even numbers [2, 4] -> [6, 12] -> sum = 18)
|
|
547
1258
|
```
|
|
548
1259
|
|
|
1260
|
+
### Operator Precedence
|
|
1261
|
+
|
|
1262
|
+
Lits follows a specific operator precedence order that determines how expressions are evaluated. Operators with higher precedence are evaluated first. When operators have the same precedence, they are evaluated left-to-right.
|
|
1263
|
+
|
|
1264
|
+
Here's the complete precedence table, from highest to lowest:
|
|
1265
|
+
|
|
1266
|
+
| Precedence | Operator(s) | Description | Example |
|
|
1267
|
+
|------------|-------------|-------------|---------|
|
|
1268
|
+
| 12 | `^` | Exponentiation | `2 ^ 3 ^ 2` → `2 ^ (3 ^ 2)` → `512` |
|
|
1269
|
+
| 11 | `*` `/` `%` | Multiplication, Division, Remainder | `6 + 4 * 2` → `6 + 8` → `14` |
|
|
1270
|
+
| 10 | `+` `-` | Addition, Subtraction | `10 - 3 + 2` → `7 + 2` → `9` |
|
|
1271
|
+
| 9 | `<<` `>>` `>>>` | Bit shift operations | `8 >> 1 + 1` → `8 >> 2` → `2` |
|
|
1272
|
+
| 8 | `++` | String concatenation | `"a" ++ "b" ++ "c"` → `"abc"` |
|
|
1273
|
+
| 7 | `<` `<=` `≤` `>` `>=` `≥` | Comparison operators | `3 + 2 > 4` → `5 > 4` → `true` |
|
|
1274
|
+
| 6 | `==` `!=` `≠` | Equality operators | `2 * 3 == 6` → `6 == 6` → `true` |
|
|
1275
|
+
| 5 | `&` `xor` `\|` | Bitwise operations | `4 \| 2 & 1` → `4 \| 0` → `4` |
|
|
1276
|
+
| 4 | `&&` `\|\|` `??` | Logical operations | `true && false \|\| true` → `false \|\| true` → `true` |
|
|
1277
|
+
| 3 | *function operators* | Binary functions used as operators | `5 max 3 + 2` → `5 max 5` → `5` |
|
|
1278
|
+
| 2 | `\|>` | Pipe operator | `[1,2] \|> map(_, inc) \|> sum` |
|
|
1279
|
+
| 1 | `?` `:` | Conditional (ternary) operator | `true ? 1 + 2 : 3` → `true ? 3 : 3` → `3` |
|
|
1280
|
+
|
|
1281
|
+
#### Examples of Precedence in Action
|
|
1282
|
+
|
|
1283
|
+
```lits
|
|
1284
|
+
// Exponentiation has highest precedence
|
|
1285
|
+
2 + 3 ^ 2; // => 2 + 9 = 11 (not 5^2 = 25)
|
|
1286
|
+
|
|
1287
|
+
// Multiplication before addition
|
|
1288
|
+
2 + 3 * 4; // => 2 + 12 = 14 (not 5*4 = 20)
|
|
1289
|
+
|
|
1290
|
+
// String concatenation before comparison
|
|
1291
|
+
"a" ++ "b" == "ab"; // => "ab" == "ab" = true
|
|
1292
|
+
|
|
1293
|
+
// Comparison before logical AND
|
|
1294
|
+
3 > 2 && 1 < 2; // => true && true = true
|
|
1295
|
+
|
|
1296
|
+
// Pipe has very low precedence
|
|
1297
|
+
[1, 2, 3] |> map(_, inc) |> vec:sum; // Evaluates left to right
|
|
1298
|
+
|
|
1299
|
+
// Conditional has lowest precedence
|
|
1300
|
+
true ? 2 + 3 : 4 + 5; // => true ? 5 : 9 = 5
|
|
1301
|
+
```
|
|
1302
|
+
|
|
1303
|
+
#### Using Parentheses
|
|
1304
|
+
|
|
1305
|
+
When in doubt, or to make your intent clear, use parentheses to override precedence:
|
|
1306
|
+
|
|
1307
|
+
```lits
|
|
1308
|
+
// Without parentheses (follows precedence)
|
|
1309
|
+
2 + 3 * 4; // => 14
|
|
1310
|
+
|
|
1311
|
+
// With parentheses (explicit grouping)
|
|
1312
|
+
(2 + 3) * 4; // => 20
|
|
1313
|
+
|
|
1314
|
+
// Complex expression with explicit grouping
|
|
1315
|
+
let a = 2;
|
|
1316
|
+
let b = 3;
|
|
1317
|
+
let c = 4;
|
|
1318
|
+
let d = true;
|
|
1319
|
+
let e = false;
|
|
1320
|
+
let f = 10;
|
|
1321
|
+
let g = 5;
|
|
1322
|
+
((a + b) * c) > (d && e ? f : g) // => (5 * 4) > (false ? 10 : 5) = 20 > 5 = true;
|
|
1323
|
+
```
|
|
1324
|
+
|
|
1325
|
+
#### Associativity
|
|
1326
|
+
|
|
1327
|
+
Most operators are left-associative, meaning they evaluate from left to right when they have the same precedence:
|
|
1328
|
+
|
|
1329
|
+
```lits
|
|
1330
|
+
10 - 5 - 2; // => (10 - 5) - 2 = 3 (not 10 - (5 - 2) = 7)
|
|
1331
|
+
"a" ++ "b" ++ "c"; // => ("a" ++ "b") ++ "c" = "abc"
|
|
1332
|
+
```
|
|
1333
|
+
|
|
1334
|
+
**Exception**: Exponentiation (`^`) is right-associative:
|
|
1335
|
+
```lits
|
|
1336
|
+
2 ^ 3 ^ 2 // => 2 ^ (3 ^ 2) = 2 ^ 9 = 512 (not (2 ^ 3) ^ 2 = 64)
|
|
1337
|
+
```
|
|
1338
|
+
|
|
549
1339
|
## Built-in Functions
|
|
550
1340
|
|
|
551
1341
|
Lits comes with a comprehensive standard library of functions for:
|
|
@@ -562,13 +1352,34 @@ Lits comes with a comprehensive standard library of functions for:
|
|
|
562
1352
|
|
|
563
1353
|
For a complete reference of all available functions with examples, visit the [Lits Playground](https://mojir.github.io/lits/) where you can explore the interactive documentation and try functions in real-time.
|
|
564
1354
|
|
|
1355
|
+
## Serialization
|
|
1356
|
+
|
|
1357
|
+
A unique feature of Lits is that every result from evaluation is fully serializable as JSON, including functions and regular expressions:
|
|
1358
|
+
|
|
1359
|
+
```lits
|
|
1360
|
+
// Functions are serializable
|
|
1361
|
+
let myFunction = x -> x * 2;
|
|
1362
|
+
|
|
1363
|
+
// Regular expressions are serializable
|
|
1364
|
+
let myRegex = #"[a-z]+";
|
|
1365
|
+
|
|
1366
|
+
// Complex data structures with functions are serializable
|
|
1367
|
+
let config = {
|
|
1368
|
+
transform: x -> x * 3,
|
|
1369
|
+
pattern: #"\d+",
|
|
1370
|
+
data: [1, 2, 3]
|
|
1371
|
+
};
|
|
1372
|
+
|
|
1373
|
+
// All of these can be serialized to JSON and later deserialized
|
|
1374
|
+
// back into working Lits values, preserving their functionality
|
|
1375
|
+
```
|
|
1376
|
+
|
|
565
1377
|
## Modules and Exports
|
|
566
1378
|
|
|
567
1379
|
```lits
|
|
568
1380
|
// Export variables and functions
|
|
569
1381
|
export let pi = 3.14159;
|
|
570
1382
|
export let square = x -> x * x;
|
|
571
|
-
|
|
572
1383
|
// Exported values become available to other modules
|
|
573
1384
|
```
|
|
574
1385
|
|
|
@@ -577,14 +1388,9 @@ export let square = x -> x * x;
|
|
|
577
1388
|
### Factorial
|
|
578
1389
|
|
|
579
1390
|
```lits
|
|
580
|
-
let factorial = n ->
|
|
581
|
-
if n <= 1 then
|
|
582
|
-
1
|
|
583
|
-
else
|
|
584
|
-
n * self(n - 1)
|
|
585
|
-
end;
|
|
1391
|
+
let factorial = n -> n <= 1 ? 1 : n * self(n - 1);
|
|
586
1392
|
|
|
587
|
-
factorial(5) // => 120
|
|
1393
|
+
factorial(5); // => 120
|
|
588
1394
|
```
|
|
589
1395
|
|
|
590
1396
|
### Array Processing
|
|
@@ -605,99 +1411,67 @@ let oddSum = numbers
|
|
|
605
1411
|
// => 25
|
|
606
1412
|
```
|
|
607
1413
|
|
|
608
|
-
###
|
|
1414
|
+
### String Processing
|
|
609
1415
|
|
|
610
1416
|
```lits
|
|
611
|
-
let
|
|
612
|
-
|
|
613
|
-
//
|
|
614
|
-
let
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
//
|
|
620
|
-
let
|
|
621
|
-
|>
|
|
622
|
-
|>
|
|
1417
|
+
let text = "Hello, World! How are you today?";
|
|
1418
|
+
|
|
1419
|
+
// Word count
|
|
1420
|
+
let wordCount = text
|
|
1421
|
+
|> split(_, #"\s+")
|
|
1422
|
+
|> count;
|
|
1423
|
+
// => 6
|
|
1424
|
+
|
|
1425
|
+
// Uppercase words longer than 4 characters
|
|
1426
|
+
let longWords = text
|
|
1427
|
+
|> split(_, #"\s+")
|
|
1428
|
+
|> filter(_, -> count($) > 4)
|
|
1429
|
+
|> map(_, upper-case);
|
|
1430
|
+
// => ["HELLO,", "WORLD!", "TODAY?"]
|
|
623
1431
|
```
|
|
624
1432
|
|
|
625
|
-
###
|
|
626
|
-
|
|
627
|
-
#### Switch (for matching against a single value)
|
|
1433
|
+
### Data Transformation
|
|
628
1434
|
|
|
629
1435
|
```lits
|
|
630
|
-
let
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
let
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
end ?? "Unknown Status";
|
|
648
|
-
```
|
|
649
|
-
|
|
650
|
-
#### Cond (for testing different conditions)
|
|
651
|
-
|
|
652
|
-
```lits
|
|
653
|
-
let describe = x ->
|
|
654
|
-
cond
|
|
655
|
-
case null?(x) then "nothing"
|
|
656
|
-
case number?(x) then "a number: " ++ str(x)
|
|
657
|
-
case string?(x) then "text: " ++ x
|
|
658
|
-
case array?(x) then "list of " ++ str(count(x)) ++ " items"
|
|
659
|
-
end ?? "unknown type";
|
|
660
|
-
|
|
661
|
-
describe(42); // => "a number: 42"
|
|
662
|
-
describe([1,2,3]); // => "list of 3 items"
|
|
663
|
-
|
|
664
|
-
// Grade ranges
|
|
665
|
-
let gradeToLetter = score ->
|
|
666
|
-
cond
|
|
667
|
-
case score >= 90 then "A"
|
|
668
|
-
case score >= 80 then "B"
|
|
669
|
-
case score >= 70 then "C"
|
|
670
|
-
case score >= 60 then "D"
|
|
671
|
-
end ?? "F";
|
|
672
|
-
|
|
673
|
-
gradeToLetter(85); // => "B"
|
|
674
|
-
gradeToLetter(55); // => "F"
|
|
1436
|
+
let users = [
|
|
1437
|
+
{ name: "Alice", age: 30, department: "Engineering" },
|
|
1438
|
+
{ name: "Bob", age: 25, department: "Marketing" },
|
|
1439
|
+
{ name: "Charlie", age: 35, department: "Engineering" }
|
|
1440
|
+
];
|
|
1441
|
+
|
|
1442
|
+
// Group by department and get average age
|
|
1443
|
+
let grouped = users |> group-by(_, "department");
|
|
1444
|
+
let departmentAges = grouped
|
|
1445
|
+
|> entries(_)
|
|
1446
|
+
|> map(_, ([dept, people]) -> {
|
|
1447
|
+
let ages = people |> map(_, "age");
|
|
1448
|
+
let total = ages |> reduce(_, +, 0);
|
|
1449
|
+
[dept, total / count(ages)]
|
|
1450
|
+
})
|
|
1451
|
+
|> (pairs -> zipmap(map(pairs, 0), map(pairs, 1)));
|
|
1452
|
+
// => { "Engineering": 32.5, "Marketing": 25 }
|
|
675
1453
|
```
|
|
676
1454
|
|
|
677
1455
|
## JavaScript Interoperability
|
|
678
1456
|
|
|
679
|
-
|
|
1457
|
+
### Using Lits in JavaScript
|
|
680
1458
|
|
|
681
1459
|
```javascript
|
|
682
|
-
import { Lits } from 'lits';
|
|
1460
|
+
import { Lits } from '@mojir/lits';
|
|
683
1461
|
|
|
684
|
-
// Create a Lits instance
|
|
685
1462
|
const lits = new Lits();
|
|
686
1463
|
|
|
687
|
-
//
|
|
688
|
-
const
|
|
689
|
-
console.log(
|
|
1464
|
+
// Basic usage
|
|
1465
|
+
const result1 = lits.run('+(1, 2, 3)');
|
|
1466
|
+
console.log(result1); // 6
|
|
690
1467
|
|
|
691
|
-
//
|
|
1468
|
+
// Provide JavaScript values
|
|
692
1469
|
const result2 = lits.run('name ++ " is " ++ str(age)', {
|
|
693
|
-
values: {
|
|
694
|
-
name: "John",
|
|
695
|
-
age: 30
|
|
696
|
-
}
|
|
1470
|
+
values: { name: 'John', age: 30 }
|
|
697
1471
|
});
|
|
698
1472
|
console.log(result2); // "John is 30"
|
|
699
1473
|
|
|
700
|
-
// Expose JavaScript functions
|
|
1474
|
+
// Expose JavaScript functions
|
|
701
1475
|
const result3 = lits.run('myAlert("Hello from Lits!")', {
|
|
702
1476
|
jsFunctions: {
|
|
703
1477
|
myAlert: {
|