Skip to content

Commit

Permalink
global memo with a trie, refactors
Browse files Browse the repository at this point in the history
  • Loading branch information
maxdeliso committed Feb 12, 2025
1 parent c5778e3 commit 8163a63
Show file tree
Hide file tree
Showing 32 changed files with 970 additions and 736 deletions.
32 changes: 9 additions & 23 deletions bin/ski.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ const { terminal } = tkexport;
import {
// SKI evaluator
stepOnceImmediate,
stepOnceSKI,
// SKI expressions
generateExpr,
prettyPrintSKI,
type SKIExpression,
// Parsers
Expand All @@ -37,8 +35,10 @@ import {
convertLambda,
// Types
prettyPrintTy,
inferType
inferType,
reduce
} from '../lib/index.js';
import { randExpression } from '../lib/ski/generator.js';

enum Mode {
SKI = 'SKI',
Expand All @@ -48,7 +48,7 @@ enum Mode {
}

let currentMode: Mode = Mode.SKI;
let currentSKI: SKIExpression = generateExpr(create(hrtime.bigint().toString()), 32);
let currentSKI: SKIExpression = randExpression(create(hrtime.bigint().toString()), 32);
let currentLambda: UntypedLambda | null = null;
let currentTypedLambda: TypedLambda | null = null;
let currentSystemF: SystemFTerm | null = null;
Expand Down Expand Up @@ -154,26 +154,13 @@ function skiStepOnce(): void {

function skiStepMany(): void {
const MAX_ITER = 100;
let iterations = 0;
while (iterations < MAX_ITER) {
const result = stepOnceSKI(currentSKI);
if (result.altered) {
currentSKI = result.expr;
printGreen(`step ${iterations + 1}: ${prettyPrintSKI(currentSKI)}`);
} else {
printYellow(`no further reduction after ${iterations} step(s).`);
break;
}
iterations++;
}
if (iterations === MAX_ITER) {
printRed('stopped after reaching maximum iterations.');
}
const result = reduce(currentSKI, MAX_ITER);
printGreen(`stepped many (with max of ${MAX_ITER}): ` + prettyPrintSKI(result));
}

function skiRegenerate(): void {
const rs = create(hrtime.bigint().toString());
currentSKI = generateExpr(rs, 32);
currentSKI = randExpression(rs, 32);
printGreen('generated new SKI expression: ' + prettyPrintSKI(currentSKI));
}

Expand Down Expand Up @@ -269,7 +256,6 @@ function processCommand(input: string): void {
} else if (cmd === 'p' || cmd === 'print') {
printCurrentTerm();
} else if (cmd === 'tc' || cmd === 'typecheck') {
// New typecheck command
switch (currentMode) {
case Mode.SKI:
printYellow('Type checking not available in SKI mode.');
Expand Down Expand Up @@ -303,7 +289,7 @@ function processCommand(input: string): void {
break;
}
}
else if (cmd === 'infer' || cmd === 'i') {
else if (cmd === 'i' || cmd === 'infer') {
// This command infers the type for the current untyped Lambda expression.
if (currentMode !== Mode.Lambda) {
printYellow('Type inference is only available in Lambda mode.');
Expand Down Expand Up @@ -341,7 +327,7 @@ Available commands:
:g or :generate -- generate a new SKI expression (SKI mode only)
:p or :print -- print the current term
:tc or :typecheck -- typecheck the current term (only available in TypedLambda and SystemF modes)
:infer or :i -- infer the type for the current untyped Lambda term and switch to TypedLambda mode
:i or :infer -- infer the type for the current untyped Lambda term and switch to TypedLambda mode
Any other input is interpreted as a new term for the current mode.
Press CTRL+C or type :quit to exit.`);
Expand Down
99 changes: 99 additions & 0 deletions lib/data/skiTrie.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { charToIndex, SKIExpression, SKIKey } from '../ski/expression.js';
import { SKITrieNode } from './skiTrieNode.js';

/**
* A simple immutable SKI trie.
*/
export interface SKITrie {
root: SKITrieNode<SKIExpression>;
}

/**
* Creates a new SKI trie.
* If no root is provided, a root node with an empty prefix is created.
*/
export function createSKITrie(root?: SKITrieNode<SKIExpression>): SKITrie {
return { root: root ?? makeTrieNode<SKIExpression>([]) };
}

/**
* Helper to create a new trie node.
* If no children array is provided, one with 5 slots is created.
*/
export function makeTrieNode<T>(
prefix: SKIKey,
value?: T,
children?: (SKITrieNode<T> | undefined)[]
): SKITrieNode<T> {
return {
prefix,
value,
children: children ? children.slice() : new Array<SKITrieNode<T> | undefined>(5).fill(undefined)
};
}

/**
* Returns a shallow copy of a node.
*/
function copyNode<T>(node: SKITrieNode<T>): SKITrieNode<T> {
return makeTrieNode(node.prefix.slice(), node.value, node.children);
}

/**
* Looks up the value associated with the given key.
*
* The key is an array of SKIChar.
*/
export function getSKITrie(trie: SKITrie, key: SKIKey): SKIExpression | undefined {
let node = trie.root;
// For each character in the key, descend one level.
for (const char of key) {
const idx = charToIndex(char);
const child = node.children[idx];
if (!child) {
return undefined;
}
node = child;
}
return node.value;
}

/**
* Returns a new trie that maps the given key to the given value.
* Makes use of structural sharing to avoid duplicating nodes.
*/
export function setSKITrie(trie: SKITrie, key: SKIKey, value: SKIExpression): SKITrie {
// Special-case: an empty key updates the root.
if (key.length === 0) {
const newRoot = copyNode(trie.root);
newRoot.value = value;
return createSKITrie(newRoot);
}

// Start by copying the root.
const newRoot = copyNode(trie.root);
let current = newRoot;
// For each character in the key, descend into the appropriate child.
for (const char of key) {
const idx = charToIndex(char);
let child = current.children[idx];
if (child) {
// Copy the child so as not to mutate the original structure.
child = copyNode(child);
} else {
// Create a new node for this character.
// (For non-root nodes the prefix is the one character of the key.)
child = makeTrieNode<SKIExpression>([char]);
}
// Replace the corresponding child pointer.
const newChildren = current.children.slice();
newChildren[idx] = child;
current.children = newChildren;
// Move down the trie.
current = child;
}

// At the end of the key, set the value.
current.value = value;
return createSKITrie(newRoot);
}
26 changes: 26 additions & 0 deletions lib/data/skiTrieNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { SKIKey } from '../ski/expression.js';

/**
* A plain record representing a node in the SKI trie.
*/
export interface SKITrieNode<T> {
prefix: SKIKey;
value?: T;
children: (SKITrieNode<T> | undefined)[];
}

/**
* Factory function to create a new SKI trie node.
* If children are not provided, a new 5-slot array filled with undefined is created.
*/
export function makeTrieNode<T>(
prefix: SKIKey,
value?: T,
children?: (SKITrieNode<T> | undefined)[]
): SKITrieNode<T> {
return {
prefix,
value,
children: children ?? new Array<SKITrieNode<T> | undefined>(5).fill(undefined)
};
}
Loading

0 comments on commit 8163a63

Please sign in to comment.