This commit is contained in:
nora 2025-12-07 20:40:06 +01:00
parent 464bdc2478
commit bb234cc0dd
3 changed files with 342 additions and 7 deletions

View file

@ -2,8 +2,12 @@
"name": "println",
"version": "1.0.0",
"description": "",
"main": "index.js",
"main": "dist/index.js",
"scripts": {
"watch": "pnpm '/watch:*/'",
"watch:build": "tsc --watch",
"watch:run": "node --watch .",
"dev": "node .",
"build": "tsc"
},
"keywords": [],

View file

@ -0,0 +1,334 @@
// simple
const TypeBoolean = Symbol("TypeBoolean");
const TypeFontID = Symbol("TypeFontID");
const TypeInteger = Symbol("TypeInteger");
const TypeMark = Symbol("TypeMark");
const TypeName = Symbol("TypeName");
const TypeNull = Symbol("TypeNull");
const TypeOperator = Symbol("TypeOperator");
const TypeReal = Symbol("TypeReal");
// composite
const TypeArray = Symbol("TypeArray");
const TypeDictionary = Symbol("TypeDictionary");
const TypeFile = Symbol("TypeFile");
const TypeGState = Symbol("TypeGState");
const TypePackedArray = Symbol("TypePackedArray");
const TypeSave = Symbol("TypeSave");
const TypeString = Symbol("TypeString");
type Name = number;
type PsObjectBase =
| { type: typeof TypeBoolean; value: boolean }
| { type: typeof TypeFontID; value: number }
| { type: typeof TypeInteger; value: number }
| { type: typeof TypeMark }
| { type: typeof TypeName; name: Name }
| { type: typeof TypeNull }
| { type: typeof TypeOperator; implementation: (state: PSExecState) => void }
| { type: typeof TypeReal; value: number }
| { type: typeof TypeArray }
| {
type: typeof TypeDictionary;
// TODO: store in memory
values: Map<Name, PsObject>;
}
| { type: typeof TypeFile }
| { type: typeof TypeGState }
| { type: typeof TypePackedArray }
| { type: typeof TypeSave }
| { type: typeof TypeString };
const AttrLiteral = Symbol("AttrLiteral");
const AttrExecutable = Symbol("AttrExecute");
const AccessUnlimited = Symbol("AccessUnlimited");
const AccessReadOnly = Symbol("AccessReadOnly");
const AccessExecuteOnly = Symbol("AccessExecuteOnly");
const AccessNone = Symbol("AccessNone");
type PsObject = PsObjectBase & {
litexec: typeof AttrLiteral | typeof AttrExecutable;
access:
| typeof AccessUnlimited
| typeof AccessReadOnly
| typeof AccessExecuteOnly
| typeof AccessNone;
};
type PsObjectBoolean = Extract<PsObject, { type: typeof TypeBoolean }>;
type PsObjectFontID = Extract<PsObject, { type: typeof TypeFontID }>;
type PsObjectInteger = Extract<PsObject, { type: typeof TypeInteger }>;
type PsObjectMark = Extract<PsObject, { type: typeof TypeMark }>;
type PsObjectName = Extract<PsObject, { type: typeof TypeName }>;
type PsObjectNull = Extract<PsObject, { type: typeof TypeNull }>;
type PsObjectOperator = Extract<PsObject, { type: typeof TypeOperator }>;
type PsObjectReal = Extract<PsObject, { type: typeof TypeReal }>;
type PsObjectArray = Extract<PsObject, { type: typeof TypeArray }>;
type PsObjectDictionary = Extract<PsObject, { type: typeof TypeDictionary }>;
type PsObjectFile = Extract<PsObject, { type: typeof TypeFile }>;
type PsObjectGState = Extract<PsObject, { type: typeof TypeGState }>;
type PsObjectPackedArray = Extract<PsObject, { type: typeof TypePackedArray }>;
type PsObjectSave = Extract<PsObject, { type: typeof TypeSave }>;
type PsObjectString = Extract<PsObject, { type: typeof TypeString }>;
// stuff
let nextNameIdx = 0;
const nameIndicies: Map<string, Name> = new Map();
const nameStrings: Map<Name, string> = new Map();
const newName = (value: string): Name => {
let nameIdx = nameIndicies.get(value);
if (nameIdx !== undefined) {
return nameIdx;
}
nameIdx = nextNameIdx++;
nameIndicies.set(value, nameIdx);
nameStrings.set(nameIdx, value);
return nameIdx;
};
const nameString = (name: Name): string => {
const string = nameStrings.get(name);
if (string === undefined) {
throw new Error(`invalid name: ${name} has no associated string`);
}
return string;
};
// logic
const file = function* (input: string) {
for (const char of input) {
yield char;
}
};
const scan = function* (
file: Generator<string>
): Generator<PsObject, void, void> {
const splitter = function* (file: Generator<string>): Generator<string> {
const isWhitespace = (value: string) =>
value === " " || value === "\t" || value === "\r" || value === "\n";
let accumulator = "";
let next;
while (((next = file.next()), !next.done)) {
if (next.value === "(") {
throw new Error("todo (");
}
if (isWhitespace(next.value)) {
if (accumulator.length > 0) {
yield accumulator;
}
accumulator = "";
continue;
} else if (next.value === "%") {
while (((next = file.next()), !next.done && next.value !== "\n")) {}
continue;
} else if (
["<", ">", "[", "]", "{", "}", "/", "%"].includes(next.value)
) {
if (accumulator.length > 0) {
yield accumulator;
}
accumulator = "";
}
accumulator += next.value;
}
if (accumulator.length > 0) {
yield accumulator;
}
};
const tokens = splitter(file);
let next;
while (((next = tokens.next()), !next.done)) {
console.log("token", next.value);
switch (true) {
case /^\d+$/.test(next.value): {
const numericValue = parseInt(next.value, 10);
yield {
type: TypeInteger,
value: numericValue,
litexec: AttrLiteral,
access: AccessUnlimited,
};
break;
}
default: {
const isLiteral = next.value.startsWith("/");
const nameValue = newName(isLiteral ? next.value.slice(1) : next.value);
yield {
type: TypeName,
name: nameValue,
litexec: isLiteral ? AttrLiteral : AttrExecutable,
access: AccessUnlimited,
};
}
}
}
};
type Stack<T> = T[];
type PSExecState = {
operandStack: Stack<PsObject>;
dictionaryStack: Stack<PsObjectDictionary>;
executionStack: Stack<PsObject>;
};
const initExecState = (): PSExecState => {
const systemdict: PsObjectDictionary = {
type: TypeDictionary,
values: new Map(),
litexec: AttrLiteral,
access: AccessReadOnly,
};
const defineOperator = (
name: string,
implementation: (state: PSExecState) => void
) => {
systemdict.values.set(newName(name), {
type: TypeOperator,
litexec: AttrExecutable,
access: AccessReadOnly,
implementation,
});
};
// helpers
const popOperand = (state: PSExecState): PsObject => {
const value = state.operandStack.pop();
if (!value) {
throw new Error("error: stackunderflow");
}
return value;
};
defineOperator("add", (state) => {
const b = popOperand(state);
const a = popOperand(state);
if (a.type === TypeInteger && b.type === TypeInteger) {
state.operandStack.push({
type: TypeInteger,
litexec: AttrLiteral,
access: AccessUnlimited,
value: a.value + b.value,
});
}
});
defineOperator("def", (state) => {
const value = popOperand(state);
const key = popOperand(state);
const topDict = state.dictionaryStack.at(-1)!;
let name: Name;
if (key.type === TypeName) {
name = key.name;
} else {
throw new Error("todo: other def entries");
}
topDict.values.set(name, value);
});
const globaldict: PsObjectDictionary = {
type: TypeDictionary,
values: new Map(),
litexec: AttrLiteral,
access: AccessUnlimited,
};
const userdict: PsObjectDictionary = {
type: TypeDictionary,
values: new Map(),
litexec: AttrLiteral,
access: AccessUnlimited,
};
return {
operandStack: [],
dictionaryStack: [systemdict, globaldict, userdict],
executionStack: [],
};
};
const lookupDictionaryStack = (
stack: Stack<PsObjectDictionary>,
name: Name
): PsObject | undefined => {
for (let i = stack.length - 1; i >= 0; i--) {
const dict = stack[i];
const value = dict?.values.get(name);
if (value) {
return value;
}
}
return undefined;
};
const executeObject = (state: PSExecState, object: PsObject) => {
console.log(object);
switch (object.type) {
case TypeInteger: {
state.operandStack.push(object);
break;
}
case TypeName: {
if (object.litexec === AttrLiteral) {
state.operandStack.push(object);
} else {
const value = lookupDictionaryStack(state.dictionaryStack, object.name);
if (!value) {
throw new Error(`error: 'undefined' '${nameString(object.name)}'`);
}
executeObject(state, value);
}
break;
}
case TypeOperator: {
if (object.litexec === AttrLiteral) {
state.operandStack.push(object);
} else {
object.implementation(state);
}
break;
}
case TypeNull: {
break;
}
default: {
throw new Error(
`cannot execute ${object.type.description}: ${JSON.stringify(object)}`
);
}
}
};
const execute = (state: PSExecState, input: Generator<PsObject>) => {
for (const object of input) {
executeObject(state, object);
}
};
// yeet
const main = () => {
const program = `
% meow
/fourtytwo 42 def
1 fourtytwo add
`;
const f = file(program);
const scanner = scan(f);
const state = initExecState();
execute(state, scanner);
console.log(state.operandStack);
};
main();

View file

@ -25,12 +25,9 @@
"exactOptionalPropertyTypes": true,
// Style Options
// "noImplicitReturns": true,
// "noImplicitOverride": true,
// "noUnusedLocals": true,
// "noUnusedParameters": true,
// "noFallthroughCasesInSwitch": true,
// "noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noPropertyAccessFromIndexSignature": true,
"noFallthroughCasesInSwitch": true,
// Recommended Options
"strict": true,