From bb234cc0dd8a56462a0e73b1449c09e8d332d6c9 Mon Sep 17 00:00:00 2001 From: Noratrieb <48135649+Noratrieb@users.noreply.github.com> Date: Sun, 7 Dec 2025 20:40:06 +0100 Subject: [PATCH] stuff --- package.json | 6 +- src/index.ts | 334 ++++++++++++++++++++++++++++++++++++++++++++++++++ tsconfig.json | 9 +- 3 files changed, 342 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 0ca68e9..fdea343 100644 --- a/package.json +++ b/package.json @@ -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": [], diff --git a/src/index.ts b/src/index.ts index e69de29..d9d84da 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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; + } + | { 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; +type PsObjectFontID = Extract; +type PsObjectInteger = Extract; +type PsObjectMark = Extract; +type PsObjectName = Extract; +type PsObjectNull = Extract; +type PsObjectOperator = Extract; +type PsObjectReal = Extract; +type PsObjectArray = Extract; +type PsObjectDictionary = Extract; +type PsObjectFile = Extract; +type PsObjectGState = Extract; +type PsObjectPackedArray = Extract; +type PsObjectSave = Extract; +type PsObjectString = Extract; + +// stuff + +let nextNameIdx = 0; +const nameIndicies: Map = new Map(); +const nameStrings: Map = 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 +): Generator { + const splitter = function* (file: Generator): Generator { + 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[]; + +type PSExecState = { + operandStack: Stack; + dictionaryStack: Stack; + executionStack: Stack; +}; + +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, + 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) => { + 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(); diff --git a/tsconfig.json b/tsconfig.json index 8199d03..c8e9b26 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -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,