From 450745011a9f891cc7f1b0493e988e2ea4c0f26f Mon Sep 17 00:00:00 2001 From: Eric Rumsey Date: Thu, 29 May 2025 17:46:26 -0500 Subject: [PATCH] Prototype lifecycle --- src/lib/painter/canvas.ts | 80 ++++++++------------------- src/lib/painter/lifecycle.ts | 101 +++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 57 deletions(-) create mode 100644 src/lib/painter/lifecycle.ts diff --git a/src/lib/painter/canvas.ts b/src/lib/painter/canvas.ts index 6695985..9ca9fdb 100644 --- a/src/lib/painter/canvas.ts +++ b/src/lib/painter/canvas.ts @@ -1,34 +1,26 @@ import { type CursorPos } from 'node:readline' -import { type Either, fold, type Left, left, right } from '../util/basic/either' +import { type Either, left, right } from '../util/basic/either' +import { hasTag, type Tag } from '../util/basic/utility' import { PaintCursorError } from './errors' -// Cursor stuffs -interface CursorPosX { - readonly _tag: 'X' +export interface CursorPosAxis extends Tag<'X' | 'Y'> { readonly value: number } -interface CursorPosY { - readonly _tag: 'Y' - readonly value: number -} +type CursorPosVal = (tag: 'X' | 'Y') => (x: number) => CursorPosAxis +export const cursorPosVal: CursorPosVal = tag => x => ({_tag: tag, value: x}) -type CursorPosVal = (str: 'X' | 'Y') => (x: number) => CursorPosX | CursorPosY -const cursorPosVal: CursorPosVal = str => x => ({_tag: str, value: x}) +export const cursorPos = (x: number) => (y: number): CursorPos => ({ + rows: x, + cols: y, +}) -type IsCursorPos = (x: 'X' | 'Y') => (y: CursorPosX | CursorPosY) => boolean -const isCursorPos: IsCursorPos = x => y => y._tag === x - -const cursorPos = (x: number) => (y: number): CursorPos => ({rows: x, cols: y}) - -// Cursor Position type SafeCursorPos = - (x: CursorPosX | CursorPosY) => - (y: CursorPosX | CursorPosY) => Either -const safeCursorPos: SafeCursorPos = (x) => y => { - return isCursorPos('X')(x) && isCursorPos('Y')(y) + (x: CursorPosAxis) => (y: CursorPosAxis) => Either +export const safeCursorPos: SafeCursorPos = (x) => y => { + return hasTag('X')(x) && hasTag('Y')(y) ? right(cursorPos(x.value)(y.value)) - : isCursorPos('Y')(x) && isCursorPos('X')(y) + : hasTag('Y')(x) && hasTag('X')(y) ? right(cursorPos(y.value)(x.value)) : left( new PaintCursorError( @@ -38,56 +30,30 @@ const safeCursorPos: SafeCursorPos = (x) => y => { ) } -console.log( - safeCursorPos(cursorPosVal('X')(5))(cursorPosVal('X')(7)), -) - -// Tagged types -interface CanvasOrigin { - readonly _tag: 'Origin' +export interface CanvasOrigin extends Tag<'Origin'> { readonly value: CursorPos } -interface CanvasEnd { - readonly _tag: 'End' +export interface CanvasEnd extends Tag<'End'> { readonly value: CursorPos } -type CanvasOriginConstructor = (x: SafeCursorPos) => Either -const canvasOrigin = (x: CursorPos): CanvasOrigin => ({ - _tag: 'Origin', - value: x, -}) - -const canvasEnd = (x: CursorPos): CanvasEnd => ({ - _tag: 'End', - value: x, -}) - -// Canvas type using tagged positions -type Canvas = { - origin: CanvasOrigin, - end: CanvasEnd, -} - -// Constructor helpers -const origin = (pos: CursorPos): CanvasOrigin => ({ +export const origin = (pos: CursorPos): CanvasOrigin => ({ _tag: 'Origin', value: pos, }) -const end = (pos: CursorPos): CanvasEnd => ({ +export const end = (pos: CursorPos): CanvasEnd => ({ _tag: 'End', value: pos, }) -// Curried canvas creator with type safety -const canvas = (origin: CanvasOrigin) => (end: CanvasEnd): Canvas => ({ +export interface Canvas { + origin: CanvasOrigin + end: CanvasEnd +} + +export const canvas = (origin: CanvasOrigin) => (end: CanvasEnd): Canvas => ({ origin, end, }) - -// Usage with proper type safety -const myCanvas = canvas(origin({rows: 3, cols: 1}))( - end({rows: 8, cols: 1}), -) diff --git a/src/lib/painter/lifecycle.ts b/src/lib/painter/lifecycle.ts new file mode 100644 index 0000000..3315c7c --- /dev/null +++ b/src/lib/painter/lifecycle.ts @@ -0,0 +1,101 @@ +import { type CursorPos } from 'node:readline' +import type { Either } from '../util/basic/either' +import { + DoOption, + getOrElse, + none, + type Option, + some, +} from '../util/basic/option' +import { hasTag, pipe, type Tag } from '../util/basic/utility' +import { + type CanvasEnd, + type CanvasOrigin, + cursorPos, + end, + origin, +} from './canvas' + +interface PainterExit extends Tag<'PainterExit'> {} + +interface PainterTask extends Tag<'PainterTask'> { + task: { + origin: CanvasOrigin, + end: CanvasEnd, + effect: PainterEffect>, + state: Option>, + } +} + +type TaskConstructor = ( + x: CursorPos, + y: CursorPos, +) => (f: PainterEffect, state?: T & Tag<'PainterState'>) => PainterTask +const task: TaskConstructor = (x, y) => (f, state?) => { + return { + _tag: 'PainterTask', + task: { + origin: origin(x), + end: end(y), + effect: f, + state: !state ? none : some(state), + }, + } +} + +type PainterEffect< + T extends object = {}, +> = (task: PainterTask) => Either, PainterExit> + +type EffectState = (x: T) => T & Tag<'PainterState'> +const effectState: EffectState = x => ({_tag: 'PainterState', ...x}) + +export { type PainterEffect, task } + +// +// testing +// + +type TestEffect = (x: PainterTask<{message: Option}>) => PainterTask< + {message: Option} +> +const testEffect: PainterEffect<{message: string, counter: number}> = x => { + const state = DoOption.start() + .bind('state', x.task.state) + .doL(ctx => { + console.log('Message:', ctx.state.message) + return some(undefined) + }) + .doL(ctx => { + console.log('Counter:', ctx.state.counter) + return some(undefined) + }) + .return(ctx => + (ctx.state.counter < 100000) + ? {...ctx.state, counter: ctx.state.counter + 1} + : none + ) + if (pipe(state, getOrElse(none), hasTag('None'))) return {_tag: 'PainterExit'} + + return task(x.task.origin.value, x.task.end.value)( + x.task.effect, + getOrElse(none)(state), + ) +} + +const textTask = task<{message: string, counter: number}>( + cursorPos(2)(0), + cursorPos(4)(0), +)( + testEffect, + { + _tag: 'PainterState', + message: 'test message', + counter: 1, + }, +) +const testFn = (x: PainterTask | PainterExit) => { + return hasTag('PainterExit')(x) ? 'Exit' : testFn(x.task.effect(x)) +} + +testFn(textTask)