about summary refs log tree commit diff stats
path: root/js/scripting-lang/README.md
diff options
context:
space:
mode:
Diffstat (limited to 'js/scripting-lang/README.md')
-rw-r--r--js/scripting-lang/README.md467
1 files changed, 87 insertions, 380 deletions
diff --git a/js/scripting-lang/README.md b/js/scripting-lang/README.md
index a3cda8b..5890a06 100644
--- a/js/scripting-lang/README.md
+++ b/js/scripting-lang/README.md
@@ -1,43 +1,51 @@
-# Scripting Language
+# Baba Yaga
+## A Scripting Language
 
-A combinator-based scripting language with functional programming features, pattern matching, and a comprehensive standard library.
+Baba Yaga is a combinator-based scripting language that aims to be dangerously functional-brained, has minimal syntax, an intuitive approach to pattern matching, and hopefully enough of a standard library to be useful...but not too much of a standard library to be difficult to recall.
 
-## Overview
+This whole thing started as an aesthetic curiosity, and continued on from there. I wanted to be able to do pattern matching like this: 
 
-This is a functional scripting language that translates all operations into function calls to standard library combinators. The language supports:
-
-- **Function Definitions**: Arrow syntax with lexical scoping
-- **Pattern Matching**: When expressions with wildcards and nested expressions
-- **Tables**: Array-like and key-value entries with boolean keys
-- **Function References**: @ operator for higher-order programming
-- **IO Operations**: Input, output, and assertions
-- **Standard Library**: Complete set of arithmetic, comparison, logical, and higher-order combinators
-- **Table Enhancements**: APL-inspired element-wise operations and immutable table operations
+```plaintext
+factorial : n -> 
+  when n is
+    0 then 1
+    _ then n * (factorial (n - 1));
+```
 
-## Quick Start
+I've implemented a whole bunch of [forths](https://git.sr.ht/~eli_oat/chupacabra), and a couple schemes, but never have I ever implemented something like a "regular" programming language. And, while, an [ML-flavored](https://en.wikipedia.org/wiki/Standard_ML) programming language isn't exactly regular, this has grown from an aesthetic curiosity to a full-blown aesthetic indulgence.
 
-### Usage
-```bash
-# Run a script file
-node lang.js your-script.txt
+Baba Yaga supports...
 
-# Or with Bun
-bun lang.js your-script.txt
-```
+- **Function definitions** using arrow syntax with lexical scoping
+- **Pattern matching** with a single `when ... is ... then` expression that handles wildcards and arbitrarily nested features
+- **Tables** inspired by Lua's tables, with array-like and key-value entries, including boolean keys
+- **Function references** using an `@` operator for higher-order programming
+- **IO Operations** including input, output, and assertions, plus an `..emit` and `..listen` pattern for interfacing a functional core with the outside world. This contains side effects to a very limited surface area
+- **Standard Library** with a complete set of arithmetic, comparison, logical, and higher-order combinators...I think (let me know if I'm missing anything)
+- **APL-style operations** with element-wise and immutable table operations so that you can use broadcasting instead of looping
+- **Function composition** with `compose`, `pipe`, and `via` operators, supporting a bunch of different ways to chain functions together
+- **Currying by default** - all functions are automatically curried
+- **Combinator-based architecture** everything is "just" a function call or reference under the hood...because this supposedly made parsing easier...but I'm not yet totally sold on that reasoning
 
-### Example Script
-```javascript
-// Basic arithmetic
+## Example Script
+```plaintext
+/* Basic arithmetic */
 result : 5 + 3 * 2;
 ..out result;
 
-// Function definition
+/* Function definition */
 factorial : n -> 
   when n is
     0 then 1
     _ then n * (factorial (n - 1));
 
-// Pattern matching
+/* Function composition */
+double : x -> x * 2;
+increment : x -> x + 1;
+composed : compose @double @increment 5;
+..out composed;
+
+/* Pattern matching */
 classify : x y -> 
   when x y is
     0 0 then "both zero"
@@ -45,42 +53,39 @@ classify : x y ->
     _ 0 then "y is zero"
     _ _ then "neither zero";
 
-// Tables
-person : {name: "Alice", age: 30, active: true};
+/* Tables */
+person : {name: "Baba Yaga", age: 99, active: true};
 ..out person.name;
 ..out person["age"];
 
-// Function composition
-double : x -> x * 2;
-increment : x -> x + 1;
-composed : compose @double @increment 5;
-..out composed;  // Output: 12
-
-// Table enhancements
 numbers : {1, 2, 3, 4, 5};
 doubled : map @double numbers;
-..out doubled[1];  // Output: 2
+..out doubled[1]; 
 
-// APL-style element-wise operations
+/* APL-style element-wise operations over tables */
 table1 : {a: 1, b: 2, c: 3};
 table2 : {a: 10, b: 20, c: 30};
 sum : each @add table1 table2;
-..out sum.a;  // Output: 11
+..out sum.a;
 ```
 
-## Language Features
+Baba Yaga files should use either the `.txt` file extension, or the `.baba` extension.
+
+## Key Features
 
 ### Function Application
-Functions are applied using juxtaposition (space-separated):
-```javascript
-f x          // Apply function f to argument x
-f x y        // Apply f to x, then apply result to y
-f (g x)      // Apply g to x, then apply f to result
+
+Functions are applied using juxtaposition (space-separated), and you can use parentheses to help disambiguate precedence: 
+```plaintext
+f x          /* Apply function f to argument x */
+f x y        /* Apply f to x, then apply result to y */
+f (g x)      /* Apply g to x, then apply f to result */
 ```
 
 ### Pattern Matching
+
 Use `when` expressions for pattern matching:
-```javascript
+```plaintext
 result : when value is
   0 then "zero"
   1 then "one"
@@ -88,311 +93,55 @@ result : when value is
 ```
 
 ### Tables
+
 Create and access data structures:
-```javascript
-// Array-like
+```plaintext
+/* Array-like */
 numbers : {1, 2, 3, 4, 5};
 
-// Key-value pairs
-person : {name: "Alice", age: 30, active: true};
+/* Key-value pairs */
+person : {name: "Beatrice", age: 26};
 
-// Boolean keys
+/* Boolean keys */
 flags : {true: "enabled", false: "disabled"};
-
-// Chained access
-nested : {user: {profile: {name: "Bob"}}};
-name : nested.user.profile.name;
-
-// Embedded functions
-calculator : {
-  add: x y -> x + y,
-  double: x -> x * 2,
-  classify: x -> when x is 0 then "zero" _ then "non-zero"
-};
 ```
 
 ### Function References
-Use `@` to reference functions:
-```javascript
+
+Use the `@` to make reference to functions as arguments for other functions: 
+```plaintext
 numbers : {1, 2, 3, 4, 5};
 doubled : map @double numbers;
 ```
 
 ## Combinators and Higher-Order Functions
 
-The language provides a comprehensive set of combinators for functional programming. Understanding when to use each one is key to writing idiomatic code.
+Baba Yaga tries to provide a comprehensive set of combinators for functional programming:
 
 ### Core Combinators
 
-#### `map(f, x)` - Transform Elements
-**Purpose**: Apply a function to each element of a collection
-**Use when**: You want to transform every element in a table or array
-**Returns**: New collection with transformed elements
+- `map f x` - Transform elements in collections
+- `filter p x` - Select elements based on predicates  
+- `reduce f init x` - Accumulate values into a single result
+- `each f x` - Multi-argument element-wise operations
 
-```javascript
-// Transform numbers
-double : x -> x * 2;
-numbers : {1, 2, 3, 4, 5};
-doubled : map @double numbers;
-// Result: {2, 4, 6, 8, 10}
+### Function Composition
 
-// Transform table values
-person : {name: "Alice", age: 30, city: "NYC"};
-uppercase : map @identity person;
-// Result: {name: "Alice", age: 30, city: "NYC"}
-```
+- `compose f g` - Right-to-left composition (mathematical style)
+- `pipe f g` - Left-to-right composition (pipeline style)
+- `via` - Kinda like `.` composition syntax: `f via g via h`
 
-#### `filter(p, x)` - Select Elements
-**Purpose**: Keep only elements that satisfy a predicate
-**Use when**: You want to remove elements based on a condition
-**Returns**: New collection with filtered elements
-
-```javascript
-// Filter numbers
-is_even : x -> x % 2 = 0;
-numbers : {1, 2, 3, 4, 5, 6};
-evens : filter @is_even numbers;
-// Result: {2, 4, 6}
-
-// Filter table entries
-is_high : x -> x > 2;
-data : {a: 1, b: 2, c: 3, d: 4};
-high_values : filter @is_high data;
-// Result: {c: 3, d: 4}
-```
-
-#### `reduce(f, init, x)` - Accumulate Values
-**Purpose**: Combine all elements into a single value
-**Use when**: You want to compute a summary or aggregate
-**Returns**: Single accumulated value
-
-```javascript
-// Sum all numbers
-numbers : {1, 2, 3, 4, 5};
-total : reduce @add 0 numbers;
-// Result: 15
-
-// Sum all numbers
-numbers : {1, 2, 3, 4, 5};
-total : reduce @add 0 numbers;
-// Result: 15
-```
-
-#### `each(f, x)` - Multi-Argument Element-Wise
-**Purpose**: Apply a function to corresponding elements from multiple collections
-**Use when**: You want to combine elements from multiple tables or tables with scalars
-**Returns**: New collection with combined elements
-
-```javascript
-// Add corresponding elements
-table1 : {a: 1, b: 2, c: 3};
-table2 : {a: 10, b: 20, c: 30};
-sum : each @add table1 table2;
-// Result: {a: 11, b: 22, c: 33}
-
-// Add scalar to each element
-numbers : {1, 2, 3, 4, 5};
-incremented : each @add numbers 10;
-// Result: {11, 12, 13, 14, 15}
-```
-
-**Important**: `each` is designed for multi-argument operations. For single-table operations, use `map`.
-
-### Function Composition Combinators
+### Table Operations
 
-#### `compose(f, g)` - Right-to-Left Composition
-**Purpose**: Combine functions so the output of one becomes the input of another
-**Use when**: You want to chain transformations from right to left
-**Returns**: New function that applies g then f
-
-```javascript
-double : x -> x * 2;
-increment : x -> x + 1;
-double_then_increment : compose @increment @double;
-result : double_then_increment 5;
-// Result: 11 (5 * 2 = 10, then 10 + 1 = 11)
-```
-
-#### `pipe(f, g)` - Left-to-Right Composition
-**Purpose**: Combine functions so the output of one becomes the input of another
-**Use when**: You want to chain transformations from left to right
-**Returns**: New function that applies f then g
-
-```javascript
-double : x -> x * 2;
-increment : x -> x + 1;
-increment_then_double : pipe @increment @double;
-result : increment_then_double 5;
-// Result: 12 (5 + 1 = 6, then 6 * 2 = 12)
-```
+All table operations are immutable so they return new tables. 
 
-#### `apply(f, x)` - Function Application
-**Purpose**: Apply a function to an argument
-**Use when**: You need explicit function application
-**Returns**: Result of applying f to x
-
-```javascript
-add_ten : x -> x + 10;
-result : apply @add_ten 5;
-// Result: 15
-```
-
-### Utility Combinators
-
-#### `identity(x)` - Identity Function
-**Purpose**: Return the input unchanged
-**Use when**: You need a no-op function or placeholder
-**Returns**: The input value
-
-```javascript
-// Useful in composition or as default
-default_transform : identity;
-result : default_transform "hello";
-// Result: "hello"
-```
-
-#### `constant(x, y)` - Constant Function
-**Purpose**: Always return the same value regardless of input
-**Use when**: You need a function that ignores its argument
-**Returns**: The constant value
-
-```javascript
-always_five : constant 5;
-result : always_five "anything";
-// Result: 5
-```
-
-#### `curry(f, x, y)` - Partial Application
-**Purpose**: Create a new function with some arguments pre-filled
-**Use when**: You want to create specialized versions of functions
-**Returns**: New function with remaining parameters
-
-```javascript
-add : x y -> x + y;
-add_five : curry @add 5;
-result : add_five 3;
-// Result: 8
-```
-
-### Table-Specific Combinators (`t.` namespace)
-
-The `t.` namespace provides immutable table operations that always return new tables.
-
-#### `t.map(f, table)` - Table-Specific Map
-**Purpose**: Apply function to each value in a table
-**Use when**: You want to transform table values while preserving keys
-**Returns**: New table with transformed values
-
-```javascript
-double : x -> x * 2;
-person : {name: "Alice", age: 30, city: "NYC"};
-doubled_person : t.map @double person;
-// Result: {name: "Alice", age: 60, city: "NYC"}
-```
-
-#### `t.filter(p, table)` - Table-Specific Filter
-**Purpose**: Keep only table entries that satisfy a predicate
-**Use when**: You want to remove table entries based on a condition
-**Returns**: New table with filtered entries
-
-```javascript
-is_high : x -> x > 2;
-data : {a: 1, b: 2, c: 3, d: 4};
-high_values : t.filter @is_high data;
-// Result: {c: 3, d: 4}
-```
-
-#### `t.set(table, key, value)` - Immutable Set
-**Purpose**: Create a new table with a key set to a value
-**Use when**: You want to update a table without modifying the original
-**Returns**: New table with the updated key
-
-```javascript
-person : {name: "Alice", age: 30};
-updated_person : t.set person "age" 31;
-// Result: {name: "Alice", age: 31}
-// Original person table unchanged
-```
-
-#### `t.delete(table, key)` - Immutable Delete
-**Purpose**: Create a new table with a key removed
-**Use when**: You want to remove a key without modifying the original
-**Returns**: New table without the specified key
-
-```javascript
-person : {name: "Alice", age: 30, city: "NYC"};
-person_no_age : t.delete person "age";
-// Result: {name: "Alice", city: "NYC"}
-// Original person table unchanged
-```
-
-#### `t.merge(table1, table2)` - Immutable Merge
-**Purpose**: Combine two tables, with table2 values taking precedence
-**Use when**: You want to combine tables without modifying either
-**Returns**: New table with combined entries
-
-```javascript
-base : {name: "Alice", age: 30};
-updates : {age: 31, city: "NYC"};
-merged : t.merge base updates;
-// Result: {name: "Alice", age: 31, city: "NYC"}
-```
-
-#### `t.get(table, key, defaultValue)` - Safe Access
-**Purpose**: Get a value from a table with a default if key doesn't exist
-**Use when**: You want safe table access that won't throw errors
-**Returns**: The value or default
-
-```javascript
-person : {name: "Alice", age: 30};
-name : t.get person "name" "Unknown";
-city : t.get person "city" "Unknown";
-// name = "Alice", city = "Unknown"
-```
-
-#### `t.has(table, key)` - Key Existence Check
-**Purpose**: Check if a key exists in a table
-**Use when**: You want to test for key existence before accessing
-**Returns**: Boolean indicating if key exists
-
-```javascript
-person : {name: "Alice", age: 30};
-has_name : t.has person "name";     // true
-has_city : t.has person "city";     // false
-```
-
-#### `t.length(table)` - Table Size
-**Purpose**: Get the number of key-value pairs in a table
-**Use when**: You need to know the size of a table
-**Returns**: Number of entries
-
-```javascript
-person : {name: "Alice", age: 30, city: "NYC"};
-size : t.length person;
-// Result: 3
-```
+- `t.map`, `t.filter`, `t.set`, `t.delete`, `t.merge`, `t.get`, `t.has`, `t.length`
 
 ### When to Use Which Combinator
 
-#### `map` vs `t.map`
-- **Use `map`**: When working with any collection (tables, arrays, or when you want the most general solution)
-- **Use `t.map`**: When specifically working with tables and you want to emphasize table operations
+- Use `map` for general collections, `t.map` to emphasize table operations
+- Use `map` for single-table transformations, `each` for combining multiple collections.
 
-#### `each` vs `map`
-- **Use `each`**: For multi-argument element-wise operations (combining multiple tables or tables with scalars)
-- **Use `map`**: For single-table transformations
-
-#### `each` vs `apply`
-- **Use `each`**: For element-wise operations across collections
-- **Use `apply`**: For explicit function application to single values
-
-#### `compose` vs `pipe`
-- **Use `compose`**: When you think of transformations as "g then f" (mathematical notation)
-- **Use `pipe`**: When you think of transformations as "f then g" (pipeline notation)
-
-#### `t.set` vs direct assignment
-- **Use `t.set`**: When you want immutability (original table unchanged)
-- **Use direct assignment**: When you want to modify the original table (if supported)
 
 ### Standard Library
 
@@ -405,83 +154,41 @@ The language includes a comprehensive standard library:
 **Enhanced**: `identity`, `constant`, `flip`, `on`, `both`, `either`  
 **Table Operations**: `t.map`, `t.filter`, `t.set`, `t.delete`, `t.merge`, `t.get`, `t.has`, `t.length`
 
-## Key Language Takeaways
-
-- **Function application with negative arguments requires parentheses:**
-  - Example: `f (-5)` applies `f` to `-5`.
-- **Infix minus (`-`) is always parsed as subtraction:**
-  - Example: `3 - 4` is parsed as `subtract(3, 4)`.
-- **Ambiguous syntax like `f -5` is not supported:**
-  - Use parentheses for negative arguments in function application.
-- **Table operations are immutable:**
-  - All `t.` namespace operations return new tables, never modify existing ones.
-- **`each` is for multi-argument operations:**
-  - Use `map` for single-table transformations, `each` for combining multiple collections.
-
-These rules ensure that function application and infix operators are unambiguous and match functional language conventions.
-
 ## Architecture
 
-The language uses a combinator-based architecture where all operations are translated to function calls:
+Baba Yaga uses a combinator-based architecture where all operations are translated to function calls:
 
 1. **Lexer**: Converts source code into tokens
 2. **Parser**: Translates tokens into AST, converting operators to combinator calls
 3. **Interpreter**: Executes combinator functions from the standard library
 
-This approach eliminates parsing ambiguity while preserving syntax and enabling powerful functional programming patterns.
+The idea behind this approach is that it should eliminate parsing ambiguity while preserving syntax and enabling functional programming patterns like currying and composition, but the implementation is likely a little bit janky.
 
 ## Testing
 
-Run the complete test suite:
+Run the complete test suite!
 ```bash
 ./run_tests.sh
 ```
 
-All 23 tests should pass, covering:
-- Basic lexer and parser functionality
-- Arithmetic and comparison operations
-- Function definitions and calls
-- Pattern matching and case expressions
-- Table literals and access
-- Standard library functions
-- Error handling and edge cases
-- Table enhancements and combinators
-- Integration tests
-
-## Development
-
-### Project Structure
-```
-scripting-lang/
-├── lang.js          # Main interpreter and standard library
-├── lexer.js         # Lexical analysis
-├── parser.js        # Parsing and AST generation
-├── tests/           # Test files (.txt format)
-├── design/          # Architecture and design documentation
-│   ├── ARCHITECTURE.md
-│   ├── README.md
-│   └── HISTORY/     # Historical implementation records
-└── docs/            # Generated documentation
-```
+This assumes you are using bun, but I've also tested extensively with both node and the browser, too. I haven't validated it, yet, but I think Baba Yaga should work with quickjs, too.
 
 ### Debug Mode
-Enable debug output for development:
+
+Enable debug output for development using the flag `DEBUG=1` or `DEBUG=2`:
 ```bash
 DEBUG=1 node lang.js your-script.txt
 ```
 
-### Adding Features
-The language is designed to be extensible. To add new features:
+This'll output a lot of debug info, including the AST (as JSON). 
 
-1. **Add tokens** in `lexer.js`
-2. **Add parsing logic** in `parser.js`
-3. **Add evaluation logic** in `lang.js`
-4. **Add tests** in `tests/`
-5. **Update documentation**
+### Adding Features
 
-## Documentation
+If you wanna add language features, the easiest way to do it is to see if you can implement them in Baba Yaga script, if you need to get into the guts of the thing, though, you wanna follow this pattern, 
 
-- **`design/ARCHITECTURE.md`**: Detailed architecture overview
-- **`design/README.md`**: Design principles and patterns
-- **`design/HISTORY/`**: Implementation journey and decisions
-- **`WHAT-IS-THIS.md`**: Context and usage guide
\ No newline at end of file
+1. Add tests in `tests/`
+2. Add tokens in `lexer.js`
+3. Add parsing logic in `parser.js`
+4. Add evaluation logic in `lang.js`
+5. Validate against the tests you added earlier
+6. Update documentation
\ No newline at end of file