From f028c8d5a7a25745a8456c59ed12dc9e59b0e5db Mon Sep 17 00:00:00 2001 From: "Maurits van Riezen (mousetail)" Date: Thu, 11 Apr 2024 18:43:35 +0200 Subject: [PATCH] enable rational mode --- src/index.html | 365 +++++++++++++++------------ src/scripts/interpreter.ts | 13 +- src/scripts/main.ts | 71 ++++-- src/scripts/number_implementation.ts | 39 ++- src/style/style.scss | 19 ++ 5 files changed, 298 insertions(+), 209 deletions(-) diff --git a/src/index.html b/src/index.html index c0fedb6..6bb1526 100644 --- a/src/index.html +++ b/src/index.html @@ -11,235 +11,266 @@ -
-

><> (Fish) Interpreter by Mousetail

-
- -
-
- -
-
+
+
+

><> (Fish) Interpreter by Mousetail

- Cursor at [0,0], 0 bytes/0 chars. - +
+
- - +
+
+
+ Cursor at [0,0], 0 bytes/0 chars. + +
-
-
- -
-
- - -
+ + + +
+
+ +
+
+ + +
+
+
-
-
-
- -
-
- - -
+ +
+ + Numbers: + + +
+ + + + + +
+
+
+ +
+
+ + +
+
+
-
-
-
+
-
- - - -
+
+ + + +
-
+
- -
- -
-
+
+ Speed +
+ + + + -
-
-

Stack:

+ + -
+ + -

Cursor Position:

-
+ + +
+
-
-

Input Queue:

-
-

Register:

-
+ +
+
+

Stack:

+ +
+ +

Cursor Position:

+
+
+
+

Input Queue:

+
+ +

Register:

+
+ +
-
-

Output:

+

Output:

-
+
-
-
- -
-

Cheat Sheet

+
+ +
+

Cheat Sheet

-

Movement

+

Movement

-
-
-

<>^v Change Cursor Direction

-
-
-

/\|_# Reflect the cursor direction

+
+
+

<>^v Change Cursor Direction

+
+
+

/\|_# Reflect the cursor direction

+
-
-

x Change cursor directon randomly

+

x Change cursor directon randomly

-

! Skip the next instruction, moving 2 spaces instead of 1

+

! Skip the next instruction, moving 2 spaces instead of 1

-

? Pop 1 value of the stack, if it's 0 skip the next instruction and move - forward 2 - spaces. -

+

? Pop 1 value of the stack, if it's 0 skip the next instruction and move + forward 2 + spaces. +

-

. Pop 2 values of the stack then move to that position.

+

. Pop 2 values of the stack then move to that position.

-

Literals

+

Literals

-
-
-

0123456789 Push the number (0-9)

-
-
-

abcdef Push the number in hexadecimal (10-15)

+
+
+

0123456789 Push the number (0-9)

+
+
+

abcdef Push the number in hexadecimal (10-15)

+
-
-

+-*% Pop 2 values of the stack then perform the given operation.

+

+-*% Pop 2 values of the stack then perform the given operation.

-

, Divide the top 2 values of the stack.

+

, Divide the top 2 values of the stack.

-

= Pop 2 values of the stack, push 1 if they are equal and 0 otherwise -

+

= Pop 2 values of the stack, push 1 if they are equal and 0 otherwise +

-

)( Greater than, less than. Push 1 or 0 to the stack.

+

)( Greater than, less than. Push 1 or 0 to the stack.

-

'" Enable or disable string parsing mode. In string parsing mode - all characters are pushed to the stack instead of executed.

-

Stack

+

'" Enable or disable string parsing mode. In string parsing mode + all characters are pushed to the stack instead of executed.

+

Stack

-
-
-

: Duplicate the top item on the stack

-

$ Move the top element of the stack back 1

-
-
-

~ Delete the top element of the stack

-

@ Move the top element of the stack back 2

+
+
+

: Duplicate the top item on the stack

+

$ Move the top element of the stack back 1

+
+
+

~ Delete the top element of the stack

+

@ Move the top element of the stack back 2

+
-
-

{} Shifts the entire stack left or right

+

{} Shifts the entire stack left or right

-

r Reverse the stack

+

r Reverse the stack

-

l Push the length of the stack to the stack

+

l Push the length of the stack to the stack

-

[ Pop N from the stack, then create a new stack containing the top N - elements of - the old - stack.

+

[ Pop N from the stack, then create a new stack containing the top N + elements of + the old + stack.

-

] Go back to the previous stack, adding all elements of the top stack - back to - it.

+

] Go back to the previous stack, adding all elements of the top stack + back to + it.

-

IO

+

IO

-
-
-

o output a character

-
-
-

n output a number in decimal

+
+
+

o output a character

+
+
+

n output a number in decimal

+
-
-

i read a character

+

i read a character

-

Other

+

Other

-

& If the register is empty, pop the top element of the stack and move it - to the - register. - Else push the value of the register to the stack.

+

& If the register is empty, pop the top element of the stack and move it + to the + register. + Else push the value of the register to the stack.

-

g Pop the top 2 elements then push the code value at that position

+

g Pop the top 2 elements then push the code value at that position

-

p Pop a postion and a value from the stack, then write the character to - that - position in - the - code.

+

p Pop a postion and a value from the stack, then write the character to + that + position in + the + code.

-

; Halt

-
-
+

; Halt

+
+
+ +
+
+
+ -
- -
- - - + + + + \ No newline at end of file diff --git a/src/scripts/interpreter.ts b/src/scripts/interpreter.ts index 6a50a07..2fa377f 100644 --- a/src/scripts/interpreter.ts +++ b/src/scripts/interpreter.ts @@ -1,3 +1,5 @@ +import { NumberImplementation, Rational } from "./number_implementation"; + export interface Stack { contents: T[], register: T | undefined @@ -30,7 +32,8 @@ function push(o: ProgramState, n: T) { function wrap(f: (x: T, y: T, z: NumberImplementation) => T) { return (o: ProgramState) => { - push(o, f(pop(o), pop(o), o.number_implementation)) + let [x, y] = [pop(o), pop(o)]; + push(o, f(y, x, o.number_implementation)) } } @@ -91,7 +94,7 @@ const commands: {[k: string]: (o: ProgramState) => void} = { o.cursor[1] += o.cursor_direction[1]; }, '?': (o) => { - if (pop(o) === 0) { + if (!o.number_implementation.isTruthy(pop(o))) { o.cursor[0] += o.cursor_direction[0]; o.cursor[1] += o.cursor_direction[1]; } @@ -200,7 +203,7 @@ const commands: {[k: string]: (o: ProgramState) => void} = { o.output(pop(o)) }, 'n': (o) => { - let res: string = '' + pop(o); + let res: string = o.number_implementation.toString(pop(o)); for (let i = 0; i < res.length; i++) { o.output(o.number_implementation.fromInt(res.charCodeAt(i))) } @@ -256,7 +259,7 @@ const commands: {[k: string]: (o: ProgramState) => void} = { } } -type AnyTypeProgramState = {kind: 'float'} & ProgramState | {kind: 'rational'} & ProgramState; +export type AnyTypeProgramState = {kind: 'float'} & ProgramState | {kind: 'rational'} & ProgramState; function _step(o: ProgramState) { let token_code: T = o.cursor[0] < o.program[o.cursor[1]].length ? o.program[o.cursor[1]][o.cursor[0]] ?? o.number_implementation.default() : o.number_implementation.default(); @@ -268,7 +271,7 @@ function _step(o: ProgramState) { token = '�' } if (o.string_parsing_mode !== undefined) { - if (token_code === o.string_parsing_mode) { + if (o.number_implementation.isTruthy(o.number_implementation.eq(token_code, o.string_parsing_mode))) { o.string_parsing_mode = undefined } else { push(o, token_code); diff --git a/src/scripts/main.ts b/src/scripts/main.ts index 0de05f4..08891dd 100644 --- a/src/scripts/main.ts +++ b/src/scripts/main.ts @@ -5,6 +5,7 @@ import { PathDrawer } from './gen_svg'; import { show_copy_dialog } from './copy_dialog'; import { init_tabs } from './init_tabs'; import { load_explanation } from './explanation_manager'; +import { FloatingPointNumberImplementation, NumberImplementation, Rational, RationalNumberImplementation } from './number_implementation'; const play_button_label = "⏵ Start" const pause_button_label = "⏸︎ Pause" @@ -25,7 +26,7 @@ const reset_button = document.getElementById('reset') as HTMLButtonElement; const initial_stack = document.getElementById('initial-stack') as HTMLInputElement; const initial_input = document.getElementById('initial-input') as HTMLInputElement; -const fast_forward_checkbox = document.getElementById('fast-forward') as HTMLInputElement; +const main_form = document.getElementsByTagName('form')[0] as HTMLFormElement; let stack_format: 'numbers' | 'chars' = 'numbers'; let input_format: 'numbers' | 'chars' = 'chars'; @@ -107,6 +108,10 @@ function load_data_from_hash() { } } +const get_speed = () => { + return Number.parseInt(main_form.elements['speed'].value.replace(/x/, '')) +} + const format_char = (number_implementation: NumberImplementation): ((c:T) => string) => (value: T) => { const char = number_implementation.toIndex(value); if (char === 0 || char === 32) { @@ -139,7 +144,7 @@ function text_with_cursor(program: T[][], cursor: [number, number], number_im function update_ui_for_program(o: AnyTypeProgramState) { program_div.replaceChildren(...text_with_cursor(o.program, o.cursor, o.number_implementation)); - stack_div.innerText = o.stacks.map(i => i.contents.map(j => '' + j).join(' ')).join('\n'); + stack_div.innerText = o.stacks.map(i => i.contents.map(j => o.number_implementation.toString(j)).join(' ')).join('\n'); cursor_postion_box.textContent = JSON.stringify(o.cursor); register_box.textContent = o.stacks.map(i => i.register).join(', '); @@ -171,19 +176,48 @@ function enable_editor() { code_textarea.style.display = 'block'; } +function initializeProgramState( + number_implementation: NumberImplementation, + initial_stack_values: number[], + content: string, + raw_input_queue: number[], +): ProgramState { + let program: T[][] = content.split('\n').map(i => [...i].map(number_implementation.fromChar)); + let input_queue = raw_input_queue.map(number_implementation.fromInt); + + return { + stacks: [{ contents: initial_stack_values.map(number_implementation.fromInt), register: undefined }], + program: program, + cursor: [0, 0], + cursor_direction: [1, 0], + string_parsing_mode: undefined, + input: () => { + input_queue.reverse(); + let val = input_queue.pop() ?? number_implementation.fromInt(-1); + input_queue.reverse(); + input_queue_div.textContent = input_queue.join(' '); + return val; + }, + output: (o: T) => { + output_div.textContent += number_implementation.toChar(o); + }, + stopped: false, + number_implementation + } +} + function reset() { output_div.textContent = ""; - let program: number[][] = code_textarea.value.split('\n').map(i => [...i].map(j => j.codePointAt(0) as number)); program_div.style.display = 'block'; code_textarea.style.display = 'none'; - let longest_line = program.reduce((a, b) => Math.max(a, b.length), 0); + let longest_line = code_textarea.value.split('\n').reduce((a, b) => Math.max(a, b.length), 0); let size = program_div.getClientRects()[0]; let text_size = Math.max( Math.min( 0.8 * size.width / longest_line, - 0.8 * size.height / program.length, + 0.8 * size.height / 4, 80 ), 12 @@ -201,25 +235,10 @@ function reset() { const input_queue = input_format === 'chars' ? [...initial_input.value].map(i => i.charCodeAt(0)) : initial_input.value.split(' ').map(i => Number.parseFloat(i)); input_queue_div.textContent = input_queue.join(' '); - program_state = { - stacks: [{ contents: initial_stack_values, register: undefined }], - program: program, - cursor: [0, 0], - cursor_direction: [1, 0], - string_parsing_mode: undefined, - input: () => { - input_queue.reverse(); - let val = input_queue.pop() ?? -1; - input_queue.reverse(); - input_queue_div.textContent = input_queue.join(' '); - return val; - }, - output: (o: number) => { - output_div.textContent += String.fromCharCode(o); - }, - stopped: false, - number_implementation: FloatingPointNumberImplementation, - kind: 'float' + if (main_form.elements['number-implementation'] == 'float') { + program_state = {kind: 'float', ...initializeProgramState(FloatingPointNumberImplementation, initial_stack_values, code_textarea.value, input_queue)}; + } else { + program_state = {kind: 'rational', ...initializeProgramState(RationalNumberImplementation, initial_stack_values, code_textarea.value, input_queue)}; } has_started = true; @@ -261,7 +280,7 @@ start_button.addEventListener( } started_task_id = setInterval(() => { - let iteration = fast_forward_checkbox.checked ? 5 : 1; + let iteration = get_speed(); for (let i = 0; i < iteration; i++) { step_and_update(); if (program_state.stopped) { @@ -363,3 +382,5 @@ start_code_info_event_listeners(code_textarea); code_textarea.addEventListener('change', update_url_hash); initial_input.addEventListener('change', update_url_hash); initial_stack.addEventListener('change', update_url_hash); + +main_form.addEventListener('submit', (ev)=>ev.preventDefault()); \ No newline at end of file diff --git a/src/scripts/number_implementation.ts b/src/scripts/number_implementation.ts index ae0e486..036976b 100644 --- a/src/scripts/number_implementation.ts +++ b/src/scripts/number_implementation.ts @@ -1,4 +1,4 @@ -interface NumberImplementation { +export interface NumberImplementation { add(self: T, other: T): T; sub(self: T, other: T): T; mul(self: T, other: T): T; @@ -16,9 +16,12 @@ interface NumberImplementation { eq(self: T, other: T): T; lt(self: T, other: T): T; gt(self: T, other: T): T; + + toString(self: T): string; + isTruthy(self: T): boolean; } -type Rational = { +export type Rational = { numerator: number, denomonator: number } @@ -39,7 +42,7 @@ function reduce(rational: Rational): Rational { } } -const FloatingPointNumberImplementation: NumberImplementation = { +export const FloatingPointNumberImplementation: NumberImplementation = { add: function (self: number, other: number): number { return self + other; }, @@ -71,26 +74,32 @@ const FloatingPointNumberImplementation: NumberImplementation = { return d; }, eq: function (self: number, other: number): number { - throw (self == other) ? 1 : 0; + return (self == other) ? 1 : 0; }, lt: function (self: number, other: number): number { - throw (self < other) ? 1 : 0; + return (self < other) ? 1 : 0; }, gt: function (self: number, other: number): number { - throw (self > other) ? 1 : 0; + return (self > other) ? 1 : 0; + }, + toString(self: number): string { + return "" + self; + }, + isTruthy: function (self: number): boolean { + return self != 0; } } -const RationalNumberImplementation: NumberImplementation = { +export const RationalNumberImplementation: NumberImplementation = { add: function (self: Rational, other: Rational): Rational { return reduce({ - numerator: self.numerator * other.denomonator + other.denomonator * self.numerator, + numerator: self.numerator * other.denomonator + other.numerator * self.denomonator, denomonator: self.denomonator * other.denomonator }); }, sub: function (self: Rational, other: Rational): Rational { return reduce({ - numerator: self.numerator * other.denomonator - other.denomonator * self.numerator, + numerator: self.numerator * other.denomonator - other.numerator * self.denomonator, denomonator: self.denomonator * other.denomonator }); }, @@ -108,7 +117,7 @@ const RationalNumberImplementation: NumberImplementation = { }, mod: function (self: Rational, other: Rational): Rational { return reduce({ - numerator: self.numerator * other.denomonator % other.denomonator * self.numerator, + numerator: (self.numerator * other.denomonator) % (other.numerator * self.denomonator), denomonator: self.denomonator * other.denomonator }); }, @@ -116,10 +125,10 @@ const RationalNumberImplementation: NumberImplementation = { return String.fromCharCode(Math.floor(self.numerator / self.denomonator)); }, fromChar: function (char: string): Rational { - return { numerator: char.charCodeAt(0), denomonator: 0 }; + return { numerator: char.charCodeAt(0), denomonator: 1 }; }, toIndex: function (self: Rational): number { - throw new Error("Function not implemented."); + return Math.floor(self.numerator / self.denomonator); }, default: function (): Rational { return { @@ -147,5 +156,11 @@ const RationalNumberImplementation: NumberImplementation = { return RationalNumberImplementation.fromInt( self.numerator * other.denomonator > self.denomonator * other.numerator ? 1 : 0 ) + }, + toString(self: Rational): string { + return self.denomonator == 1 ? ""+self.numerator : self.numerator +'/'+self.denomonator; + }, + isTruthy(self: Rational): boolean { + return self.numerator != 0; } } \ No newline at end of file diff --git a/src/style/style.scss b/src/style/style.scss index 30d5393..d2ca2ec 100644 --- a/src/style/style.scss +++ b/src/style/style.scss @@ -42,6 +42,7 @@ body { .flex { display: flex; align-items: center; + gap: 0.25rem; } textarea:active, @@ -243,4 +244,22 @@ input[type="checkbox"]:checked { code { background-color: #880; } +} + +.hidden { + display: none; +} + +.radio-row { + border: 1px solid grey; + display: flex; + + label { + display: block; + padding: 0.25rem; + } + + input[type="radio"]:checked+label { + background-color: gray; + } } \ No newline at end of file