Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ resolver = "2"
members = [
"apiel",
"apiel-cli",
"apiel-wasm",
]

[workspace.dependencies]
Expand Down
307 changes: 188 additions & 119 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,119 +1,188 @@
# apiel
Apiel is a small subset of the [APL programming language](https://en.wikipedia.org/wiki/APL_(programming_language)) implemented in Rust.

The ultimate goal of the project is to export a macro that allows evaluating APL expressions from Rust code, providing a way to solve some problems in a very conscise manner.

## Array languages

APL was the first language in an "Array programming" or "Iversonian" paradigm. These languages are closer to mathematical notation than to C-like programming languages. The concepts proposed by APL inspired many similar languages, influenced the development of the functional programming paradigm, and had a giant impact on programming as a whole.

## Approach

The project utilizes [Yacc](https://en.wikipedia.org/wiki/Yacc) **(Yet-Another-Compiler-Compiler)** implementation in Rust through [grmtools](https://github.com/softdevteam/grmtools) to build the lexer and parser.

`apiel.l` contains the tokens for the **lexer**, `apiel.y` describes the **Yacc grammar**. The build.rs generaters Rust code for the lexer and parser generator.

My main entry point is apiel/src/parse/mod.rs. There is `fn parse_and_evaluate()` that runs `parse::eval()` (located in /parse/eval.rs) on the expression passed to it. The `parse::eval()` contains a single match expression that performs operations on the data contained in the `Expr` enumeration according to the expression type (it always calls parse::eval() recursively for `lhs` and `rhs` of the expression). The `Expr` enumeration is defined in `aliel.y`. Ech variant of the Expr usually contains a `Span` identifying where it's located in the original input, and boxed arguments, which allows for unlimited recursion inside the expression.

## Usage

```cargo run``` or ```RUST_LOG=debug cargo run``` for debugging output.

Enter commands in the terminal.

List of supported glyphs and operations:

| Glyph | Monadic operation | Impl. | Dyadic operation | Impl.
| --- | ---------------- | ----------- | ----------- | ----------- |
| + | Conjugate | ✅* | Addition | ✅
| - | Negate | ✅ | Subtraction | ✅
| × | Direction | ✅ | Multiplication | ✅
| ÷ | Reciprocal | ✅ | Division | ✅
| * | Exponentiation | ✅ | Raising to power | ✅
| ⍟ | Natural logarithm | ✅ | Logarithm | ✅
| ⌹ | Matrix inverse | ✅ | Matrix divide | ✅
| ○ | Pi Multiple | ✅ | Circular functions | ✅
| ! | Factorial | ✅ | Binomial | ✅
| ? | Roll | ✅ | Deal | ✅
| \| | Magnitude | ✅ | Residue | ✅
| ⌈ | Ceil | ✅ | Maximum | ✅
| ⌊ | Floor | ✅ | Minimum | ✅
| ⍳ | Generate index | ✅ | Index of | ✅
| ⍸ | Where | ✅ | Interval index | ✅
| / | - | - | Replicate | ✅
| / | - | - | Reduce | ✅
| \ | - | - | Expand | ✅
| \ | - | - | Scan | ✅
| , | Ravel | ✅ | Catenate | ✅
| ⍴ | Shape | ✅ | Reshape | ✅
| ⌽ | Reverse | ✅ | Rotate | ✅
| ⍉ | Transpose | ✅ | - | -
| = | - | - | Equality | ✅
| ≠ | - | - | Not Equal | ✅
| < | - | - | Less Than | ✅
| > | - | - | Greater Than | ✅
| ≤ | - | - | Less or Equal | ✅
| ≥ | - | - | Greater or Equal | ✅
| ∧ | - | - | And | ✅
| ∨ | - | - | Or | ✅
| ⍲ | - | - | Nand | ✅
| ⍱ | - | - | Nor | ✅
| ↑ | - | - | Take | ✅
| ↓ | - | - | Drop | ✅
| ⍋ | Grade Up | ✅ | - | -
| ⍒ | Grade Down | ✅ | - | -
| ¯ | High minus (negative literal) | ✅ | - | -
| ∘. | - | - | Outer Product | ✅
| f.g | - | - | Inner Product | ✅
| ← | - | - | Assignment | ✅
| {⍵} | - | - | Dfns (lambdas) | ✅
| ⍵ ⍺ | Right/Left arg | ✅ | - | -
| ∇ | Self-reference | ✅ | - | -
| ⋄ : | Guards / Statements | ✅ | - | -
| ⊃ | First | ✅ | - | -
| ∪ | Unique | ✅ | Union | ✅
| ∩ | - | - | Intersection | ✅
| ~ | Not | ✅ | Without | ✅
| ⊥ | - | - | Decode | ✅
| ⊤ | - | - | Encode | ✅
| ⌷ | - | - | Index | ✅
| ⊂ | Enclose | ✅ | - | -
| ⊃ | First / Disclose | ✅ | - | -
| ⊆ | - | - | Partition | ✅
| ¨ | Each (monadic) | ✅ | Each (dyadic) | ✅
| '' | String literals | ✅ | - | -

- \* - Not implemented for complex numbers

## Usage examples

```
>>> 5 25 125 ÷ 5
1 5 25
>>> 1 2 3 + 4 5 6
5 7 9
>>> - 1 2 3
¯1 ¯2 ¯3
>>> 1 2 3 * 2 4 6
1 16 729
>>> 10 ⍟ 100
2
>>> ⍳ 5
1 2 3 4 5
>>> +/ ⍳ 10
55
>>> 2 3 ⍴ ⍳ 6
1 2 3 4 5 6
>>> ⍴ 2 3 ⍴ ⍳ 6
2 3
>>> ⌽ 1 2 3 4 5
5 4 3 2 1
>>> 1 2 3 = 1 3 3
1 0 1
>>> 5 ⍴ 1 2
1 2 1 2 1
```

## Affiliation

This was implemented as my capstone project for the [rustcamp](https://github.com/rust-lang-ua/rustcamp), a Rust bootcamp organized by the Ukrainian Rust Community ([website](https://www.uarust.com), [linked in](https://www.linkedin.com/company/ukrainian-rust-community), [telegram](https://t.me/rustlang_ua), [github](https://github.com/rust-lang-ua), [youtube](https://www.youtube.com/channel/UCmkAFUu2MVOX8ly0LjB6TMA), [twitter](https://twitter.com/rustukraine)).
# apiel

Apiel is a subset of the [APL programming language](https://en.wikipedia.org/wiki/APL_(programming_language)) implemented in Rust.

The project exports a macro (`apl!`) for evaluating APL expressions from Rust code, and a CLI (`apiel-cli`) for interactive use.

## Array Languages

APL was the first language in an "Array programming" or "Iversonian" paradigm. These languages are closer to mathematical notation than to C-like programming languages. The concepts proposed by APL inspired many similar languages, influenced the development of the functional programming paradigm, and had a giant impact on programming as a whole.

## Approach

The project utilizes [Yacc](https://en.wikipedia.org/wiki/Yacc) **(Yet-Another-Compiler-Compiler)** implementation in Rust through [grmtools](https://github.com/softdevteam/grmtools) to build the lexer and parser.

`apiel.l` contains the tokens for the **lexer**, `apiel.y` describes the **Yacc grammar**. The `build.rs` generates Rust code for the lexer and parser. The evaluator in `parse/eval.rs` is a recursive match over the `Expr` AST.

Function trains are handled via token-level preprocessing: parenthesized groups of function references are detected by the lexer and rewritten to dfn expressions before parsing.

## Usage

### CLI

```
cargo run -p apiel-cli
```

or `RUST_LOG=debug cargo run -p apiel-cli` for debugging output.

### Library

```rust
use apiel::apl;

let result = apl!("+/ ⍳ 10").unwrap(); // [55.0]

// Pass data from Rust
let result = apl!("⍺ × ⍵", alpha: &[10.0], omega: &[1.0, 2.0, 3.0]).unwrap();

// Persistent environment
let mut env = apiel::Env::new();
apl!("data←⍳ 10", &mut env).unwrap();
apl!("+/ data", &mut env).unwrap(); // [55.0]
```

## Supported Glyphs and Operations

### Scalar Functions

| Glyph | Monadic operation | Impl. | Dyadic operation | Impl. |
| --- | --- | --- | --- | --- |
| + | Conjugate | ✅* | Addition | ✅ |
| - | Negate | ✅ | Subtraction | ✅ |
| × | Direction (signum) | ✅ | Multiplication | ✅ |
| ÷ | Reciprocal | ✅ | Division | ✅ |
| * | Exponential | ✅ | Power | ✅ |
| ⍟ | Natural logarithm | ✅ | Logarithm | ✅ |
| ○ | Pi multiple | ✅ | Circular functions | ✅ |
| ! | Factorial | ✅ | Binomial | ✅ |
| ? | Roll | ✅ | Deal | ✅ |
| \| | Magnitude | ✅ | Residue | ✅ |
| ⌈ | Ceiling | ✅ | Maximum | ✅ |
| ⌊ | Floor | ✅ | Minimum | ✅ |
| ⌹ | Matrix inverse | ✅ | Matrix divide | ✅ |

\* Not implemented for complex numbers

### Array Functions

| Glyph | Monadic operation | Impl. | Dyadic operation | Impl. |
| --- | --- | --- | --- | --- |
| ⍳ | Index generate | ✅ | Index of | ✅ |
| ⍸ | Where | ✅ | Interval index | ✅ |
| ⍴ | Shape | ✅ | Reshape | ✅ |
| , | Ravel | ✅ | Catenate | ✅ |
| ⌽ | Reverse | ✅ | Rotate | ✅ |
| ⍉ | Transpose | ✅ | Dyadic transpose | ✅ |
| ↑ | Mix | ✅ | Take | ✅ |
| ↓ | Split | ✅ | Drop | ✅ |
| ⍋ | Grade Up | ✅ | - | - |
| ⍒ | Grade Down | ✅ | - | - |
| ⊂ | Enclose | ✅ | Partitioned enclose | ✅ |
| ⊃ | First / Disclose | ✅ | - | - |
| ⊆ | - | - | Partition | ✅ |
| ⌷ | - | - | Index | ✅ |
| ⍷ | - | - | Find | ✅ |

### Selection and Set Functions

| Glyph | Monadic operation | Impl. | Dyadic operation | Impl. |
| --- | --- | --- | --- | --- |
| ∪ | Unique | ✅ | Union | ✅ |
| ∩ | - | - | Intersection | ✅ |
| ~ | Not | ✅ | Without | ✅ |
| ⊣ | Same (identity) | ✅ | Left | ✅ |
| ⊢ | Same (identity) | ✅ | Right | ✅ |
| ≡ | Depth | ✅ | Match | ✅ |
| ≢ | Tally | ✅ | Not Match | ✅ |

### Comparison and Logic

| Glyph | Monadic operation | Impl. | Dyadic operation | Impl. |
| --- | --- | --- | --- | --- |
| = | - | - | Equal | ✅ |
| ≠ | - | - | Not Equal | ✅ |
| < | - | - | Less Than | ✅ |
| > | - | - | Greater Than | ✅ |
| ≤ | - | - | Less or Equal | ✅ |
| ≥ | - | - | Greater or Equal | ✅ |
| ∧ | - | - | And | ✅ |
| ∨ | - | - | Or | ✅ |
| ⍲ | - | - | Nand | ✅ |
| ⍱ | - | - | Nor | ✅ |

### Encoding

| Glyph | Monadic operation | Impl. | Dyadic operation | Impl. |
| --- | --- | --- | --- | --- |
| ⊥ | - | - | Decode | ✅ |
| ⊤ | - | - | Encode | ✅ |

### Operators (Higher-Order)

| Glyph | Name | Impl. | Description |
| --- | --- | --- | --- |
| f/ | Reduce | ✅ | Right fold: `+/ 1 2 3` = 6 |
| f\ | Scan | ✅ | Cumulative fold: `+\ 1 2 3` = `1 3 6` |
| ∘.f | Outer Product | ✅ | All pairs: `1 2 ∘.× 3 4` |
| f.g | Inner Product | ✅ | Generalized matrix multiply |
| f¨ | Each | ✅ | Apply to each element |
| f⍨ | Commute / Selfie | ✅ | `A f⍨ B` = `B f A`; `f⍨ B` = `B f B` |
| f⍣n | Power | ✅ | Apply f n times |
| {f}∘{g} | Compose | ✅ | `f(g(⍵))` |
| {f}⍥{g} | Over | ✅ | Monadic: `f(g(⍵))`; Dyadic: `(g ⍺) f (g ⍵)` |
| {f}⍤k | Rank | ✅ | Apply f to each rank-k cell |
| {f}@i | At | ✅ | Apply f at specified indices |
| {f}⌸ | Key | ✅ | Group-by: apply f to each group |
| (f g h) | Fork (3-train) | ✅ | `(f ⍵) g (h ⍵)` -- e.g. `(+/ ÷ ≢)` for average |
| (f g) | Atop (2-train) | ✅ | `f (g ⍵)` |

Reduce, scan, outer product, inner product, and each work with all 20 primitive operators.

### Language Features

| Feature | Impl. | Description |
| --- | --- | --- |
| ← Assignment | ✅ | Variable binding |
| x+←1 Modified assignment | ✅ | `x←x+1` shorthand, works with all operators |
| x[i]←v Indexed assignment | ✅ | Modify elements at 1-based indices |
| {⍵} Dfns (lambdas) | ✅ | Anonymous functions with `⍵` (right) and `⍺` (left) args |
| f←{⍵} Named functions | ✅ | Store and call functions by name |
| ∇ Self-reference | ✅ | Recursive calls within dfns |
| ⋄ : Guards / Statements | ✅ | Multi-branch conditionals and sequential execution |
| ¯ High minus | ✅ | Negative number literals |
| '...' Strings | ✅ | Character vectors |
| Nested arrays | ✅ | Arrays containing arrays via `⊂` |
| N-dimensional arrays | ✅ | Any rank via `⍴` reshape |
| Scalar extension | ✅ | Auto-broadcast scalars to arrays |

### Examples

```
>>> (+/ ÷ ≢) 2 4 6 8 10
6
>>> (⌈/ - ⌊/) 3 1 4 1 5 9
8
>>> +⍨ 1 2 3
2 4 6
>>> {⍵+1}⍣3 ⍳ 5
4 5 6 7 8
>>> 2 3 ⍴ ⍳ 6
1 2 3 4 5 6
>>> ⍴ 2 3 ⍴ ⍳ 6
2 3
>>> ⌽ 1 2 3 4 5
5 4 3 2 1
>>> 1 2 3 = 1 3 3
1 0 1
>>> {⍵<2: ⍵ ⋄ (∇ ⍵-1)+∇ ⍵-2} 10
55
>>> ∧/ 1 1 1 0
0
>>> {≢⍵}⌸ 1 1 2 3 3 3
2 1 3
```

## Affiliation

This was implemented as my capstone project for the [rustcamp](https://github.com/rust-lang-ua/rustcamp), a Rust bootcamp organized by the Ukrainian Rust Community ([website](https://www.uarust.com), [linked in](https://www.linkedin.com/company/ukrainian-rust-community), [telegram](https://t.me/rustlang_ua), [github](https://github.com/rust-lang-ua), [youtube](https://www.youtube.com/channel/UCmkAFUu2MVOX8ly0LjB6TMA), [twitter](https://twitter.com/rustukraine)).
6 changes: 3 additions & 3 deletions apiel-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
[package]
name = "apiel-cli"
version = "0.2.0"
version = "0.3.0"
authors = ["Mark Firman <markfirmanwork@gmail.com>"]
edition = "2024"
description = "Interactive REPL for apiel, a subset of the APL programming language implemented in Rust."
keywords = ["apl", "array", "repl", "interpreter"]
categories = ["command-line-utilities", "mathematics"]
homepage = "https://github.com/NamesMark/apiel"
repository = "https://github.com/NamesMark/apiel/apiel-cli"
repository = "https://github.com/NamesMark/apiel/tree/main/apiel-cli"
license = "MIT"

[[bin]]
doc = false
name = "apiel-cli"

[dependencies]
apiel = { version = "0.2.0", path = "../apiel" }
apiel = { version = "0.3.0", path = "../apiel" }
tracing-subscriber.workspace = true

[dev-dependencies]
Expand Down
Loading
Loading