diff --git a/apps/ppp/messages/en.json b/apps/ppp/messages/en.json index 840e140e..ea8879be 100644 --- a/apps/ppp/messages/en.json +++ b/apps/ppp/messages/en.json @@ -12,5 +12,6 @@ "force_stop": "force stop", "execution_timeout": "execution timeout (ms)", "execution_timeout_description": "use zero to disable", - "untranslated_content": "this content has not yet been translated into your language." + "untranslated_content": "this content has not yet been translated into your language.", + "input_mode": "input mode" } diff --git a/apps/ppp/messages/ru.json b/apps/ppp/messages/ru.json index f726b19a..ae62fcfb 100644 --- a/apps/ppp/messages/ru.json +++ b/apps/ppp/messages/ru.json @@ -21,5 +21,6 @@ "forceStop": "Остановить принудительно", "executionTimeout": "Таймаут выполнения (мс)", "executionTimeoutDescription": "Используйте ноль для отключения", - "currently_untranslated_problem": "Данная проблема пока не переведена для вашего языка" + "currently_untranslated_problem": "Данная проблема пока не переведена для вашего языка", + "input_mode": "режим ввода" } \ No newline at end of file diff --git a/apps/ppp/src/lib/components/editor/controls/index.ts b/apps/ppp/src/lib/components/editor/controls/index.ts index 01db85c0..8eb88a54 100644 --- a/apps/ppp/src/lib/components/editor/controls/index.ts +++ b/apps/ppp/src/lib/components/editor/controls/index.ts @@ -1,2 +1,3 @@ export { default as CheckBox } from "./checkbox.svelte"; export { default as Number } from "./number.svelte"; +export { default as Select } from './select.svelte'; diff --git a/apps/ppp/src/lib/components/editor/controls/select.svelte b/apps/ppp/src/lib/components/editor/controls/select.svelte new file mode 100644 index 00000000..af3bbccf --- /dev/null +++ b/apps/ppp/src/lib/components/editor/controls/select.svelte @@ -0,0 +1,31 @@ + + +
+ {title} + + {#if alt} + {alt} + {/if} +
diff --git a/apps/ppp/src/lib/components/editor/terminal.ts b/apps/ppp/src/lib/components/editor/terminal.ts index 8264e915..383e4c68 100644 --- a/apps/ppp/src/lib/components/editor/terminal.ts +++ b/apps/ppp/src/lib/components/editor/terminal.ts @@ -1,93 +1,94 @@ -import { onDestroy } from "svelte"; -import { FitAddon } from "@xterm/addon-fit"; -import { Terminal, type ITheme } from "@xterm/xterm"; -import type { Streams, Writer } from "libs/io"; -import { BACKSPACE, makeErrorWriter } from "libs/logger"; +import { FitAddon } from '@xterm/addon-fit'; +import { Terminal, type IDisposable, type ITheme } from '@xterm/xterm'; export function makeTerminalTheme(): ITheme { - return { - background: "oklch(22.648% 0 0)", - }; + return { + background: 'oklch(22.648% 0 0)' + }; } export interface TerminalConfig { - theme?: ITheme; + theme?: ITheme; } -export function createTerminal({ - theme = makeTerminalTheme(), -}: TerminalConfig = {}) { - const terminal = new Terminal({ - theme, - fontFamily: "monospace", - convertEol: true, - rows: 1, - }); - const fitAddon = new FitAddon(); - terminal.loadAddon(fitAddon); - return { terminal, fitAddon }; +export enum InputMode { + Line = 'line', + Raw = 'raw' } -export function createStreams(terminal: Terminal): Streams { - const out: Writer = { - write(data) { - terminal.write(data); - }, - }; - const handlers = new Set<(data: Uint8Array) => void>(); - function handleData(data: Uint8Array) { - for (const handler of handlers) { - handler(data); - } - } - const encoder = new TextEncoder(); - let buffer = new Uint8Array(1024); - let offset = 0; - const emptyArray = new Uint8Array(); - const disposable = terminal.onData((data) => { - if (data === "\r") { - terminal.write("\r\n"); - handleData(emptyArray); - return; - } - // Backspace - if (data === "\x7f") { - terminal.write("\b \b"); - if (offset > 0) { - offset--; - } - handleData(BACKSPACE); - return; - } - terminal.write(data); - const input = encoder.encode(data); - if (offset + input.length > buffer.length) { - const next = new Uint8Array((offset + input.length) * 2); - next.set(buffer); - buffer = next; - } - buffer.set(input, offset); - offset += input.length; - handleData(input); - }); - onDestroy(() => disposable.dispose()); - return { - out, - err: makeErrorWriter(out), - in: { - read() { - const chunk = buffer.subarray(0, offset); - offset = 0; - return chunk; - }, - onData(handler) { - handlers.add(handler); - return { - [Symbol.dispose]() { - handlers.delete(handler); - }, - }; - }, - }, - }; -} \ No newline at end of file +export const INPUT_MODS = Object.values(InputMode) + +export function createTerminal({ theme = makeTerminalTheme() }: TerminalConfig = {}) { + const terminal = new Terminal({ + theme, + fontFamily: 'monospace', + convertEol: true, + rows: 1 + }); + const fitAddon = new FitAddon(); + terminal.loadAddon(fitAddon); + return { terminal, fitAddon }; +} + +export function createReadableStream(terminal: Terminal) { + let disposable: IDisposable; + return new ReadableStream({ + start(controller) { + disposable = terminal.onData((data) => { + controller.enqueue(data); + }); + }, + cancel() { + console.log("CANCEL") + disposable.dispose(); + } + }); +} + +const EMPTY_UINT8_ARRAY = new Uint8Array(); +const TEXT_ENCODER = new TextEncoder(); + +export function createRawInputMode(terminal: Terminal) { + return new TransformStream({ + transform(chunk, controller) { + if (chunk === '\x04') { + terminal.write(''); + controller.enqueue(EMPTY_UINT8_ARRAY); + return; + } + terminal.write(chunk); + controller.enqueue(TEXT_ENCODER.encode(chunk)); + } + }); +} + +export function createLineInputMode(terminal: Terminal) { + const buffer: string[] = []; + function flush(controller: TransformStreamDefaultController) { + if (buffer.length > 0) { + controller.enqueue(TEXT_ENCODER.encode(buffer.join(''))); + buffer.length = 0; + } + } + return new TransformStream({ + transform(chunk, controller) { + if (chunk === '\r') { + terminal.write('\r\n'); + flush(controller); + // EOF + controller.enqueue(EMPTY_UINT8_ARRAY); + return; + } + // Backspace + if (chunk === '\x7f') { + if (buffer.pop() !== undefined) { + terminal.write('\b \b'); + } + return; + } + terminal.write(chunk); + buffer.push(chunk); + }, + flush + }); +} diff --git a/apps/ppp/src/lib/problem/editor.svelte b/apps/ppp/src/lib/problem/editor.svelte index 2e7b6d5a..4c5e6094 100644 --- a/apps/ppp/src/lib/problem/editor.svelte +++ b/apps/ppp/src/lib/problem/editor.svelte @@ -6,6 +6,7 @@ import { createLogger } from 'libs/logger'; import { createContext, createRecoverableContext, withCancel, withTimeout } from 'libs/context'; import { runTests, type TestCase, type TestCompiler } from 'libs/testing'; + import type { ReadableStreamOfBytes } from 'libs/io'; import LucideInfo from '~icons/lucide/info'; import LucideCircleX from '~icons/lucide/circle-x'; import LucideCircleCheck from '~icons/lucide/circle-check'; @@ -32,7 +33,11 @@ EditorContext, setEditorContext, type ProcessStatus, - createStreams + createReadableStream, + createLineInputMode, + INPUT_MODS, + InputMode, + createRawInputMode, } from '$lib/components/editor'; import { Panel, @@ -42,7 +47,7 @@ TerminalTab, TabContent } from '$lib/components/editor/panel'; - import { CheckBox, Number } from '$lib/components/editor/controls'; + import { CheckBox, Number, Select } from '$lib/components/editor/controls'; import { m } from '$lib/paraglide/messages'; import EditorProvider from '$lib/editor-provider.svelte'; @@ -120,12 +125,6 @@ }; }); - const { terminal, fitAddon } = createTerminal(); - const streams = createStreams(terminal); - - const editorContext = new EditorContext(model, terminal, fitAddon); - setEditorContext(editorContext); - const editorWidthStorage = createSyncStorage( localStorage, 'test-editor-width', @@ -177,7 +176,23 @@ let executionTimeout = $state(executionTimeoutStorage.load()); debouncedSave(executionTimeoutStorage, () => executionTimeout, 100); - const terminalLogger = createLogger(streams.out); + const inputModeStorage = createSyncStorage(localStorage, "test-editor-input-mode", InputMode.Line) + let inputMode = $state(inputModeStorage.load()) + immediateSave(inputModeStorage, () => inputMode) + + const { terminal, fitAddon } = createTerminal(); + let lastInput: ReadableStreamOfBytes | undefined + const input = $derived.by(() => { + lastInput?.cancel() + return lastInput = createReadableStream(terminal).pipeThrough( + (inputMode === InputMode.Line ? createLineInputMode : createRawInputMode)(terminal) + ) + }) + const terminalLogger = createLogger(terminal); + + const editorContext = new EditorContext(model, terminal, fitAddon); + setEditorContext(editorContext); + let testCompilerFactory = $derived(runtime.factory); let status = $state('stopped'); let lastTestId = $state(-1); @@ -189,6 +204,7 @@ }); $effect(() => () => compilerCtx[Symbol.dispose]()); $effect(() => { + inputMode; testCompilerFactory; compilerCtx.cancel(); status = 'stopped'; @@ -206,7 +222,7 @@ terminal.reset(); try { if (testCompiler === null) { - testCompiler = await testCompilerFactory(compilerCtx.ref, streams); + testCompiler = await testCompilerFactory(compilerCtx.ref, { input, output: terminal }); } const testProgram = await testCompiler.compile(programCtxWithTimeout, [ { @@ -345,6 +361,7 @@ alt={m.execution_timeout_description()} bind:value={executionTimeout} /> + + + + + - -
- - - - - -
- - - {#snippet preLabel(lang)} - {@const Icon = LANGUAGE_ICONS[lang]} - - {/snippet} - {#snippet label(lang)} - {LANGUAGE_TITLE[lang]} - {/snippet} - {#snippet postLabel(lang)} - { - describedLanguage = lang; - e.stopPropagation(); - descriptionDialogElement.showModal(); - }} - class="invisible group-hover:visible" - /> - {/snippet} - {#snippet children()} -
  • - - - GitHub - -
  • - {/snippet} -
    - -
    -
    - - -
    - - -
    -
    -
    -
    diff --git a/apps/ppp/src/routes/(app)/editor/_runtimes.ts b/apps/ppp/src/routes/(app)/editor/_runtimes.ts deleted file mode 100644 index d73a7e36..00000000 --- a/apps/ppp/src/routes/(app)/editor/_runtimes.ts +++ /dev/null @@ -1,73 +0,0 @@ -import type { CompilerFactory, Program } from "libs/compiler"; -import { makeRemoteCompilerFactory } from "libs/compiler/actor"; - -import { Language } from "$lib/language"; -import PhpWorker from "$lib/runtime/php/worker?worker"; -import TsWorker from "$lib/runtime/ts/worker?worker"; -import PythonWorker from "$lib/runtime/python/worker?worker"; -import JsWorker from "$lib/runtime/js/worker?worker"; -import GoWorker from "$lib/runtime/go/worker?worker"; -import RustWorker from "$lib/runtime/rust/worker?worker"; -import GleamWorker from "$lib/runtime/gleam/worker?worker"; -import JavaWorker from "$lib/runtime/java/worker?worker"; -import RubyWorker from "$lib/runtime/ruby/worker?worker"; -import DotnetWorker from '$lib/runtime/dotnet/worker?worker'; - -import phpProgram from './_program.php?raw'; -import tsProgram from './_program.ts?raw'; -import pythonProgram from './_program.py?raw'; -import jsProgram from './_program.js?raw'; -import goProgram from './_program.go?raw'; -import rustProgram from './_program.rs?raw'; -import gleamProgram from './_program.gleam?raw'; -import javaProgram from './_program.java?raw'; -import csProgram from './_program.cs?raw'; -import rubyProgram from './_program.rb?raw'; - -interface Runtime { - initialValue: string; - compilerFactory: CompilerFactory; -} - -export const RUNTIMES: Record = { - [Language.JavaScript]: { - initialValue: jsProgram, - compilerFactory: makeRemoteCompilerFactory(JsWorker), - }, - [Language.TypeScript]: { - initialValue: tsProgram, - compilerFactory: makeRemoteCompilerFactory(TsWorker), - }, - [Language.Python]: { - initialValue: pythonProgram, - compilerFactory: makeRemoteCompilerFactory(PythonWorker), - }, - [Language.PHP]: { - initialValue: phpProgram, - compilerFactory: makeRemoteCompilerFactory(PhpWorker), - }, - [Language.Go]: { - initialValue: goProgram, - compilerFactory: makeRemoteCompilerFactory(GoWorker), - }, - [Language.Rust]: { - initialValue: rustProgram, - compilerFactory: makeRemoteCompilerFactory(RustWorker), - }, - [Language.Gleam]: { - initialValue: gleamProgram, - compilerFactory: makeRemoteCompilerFactory(GleamWorker), - }, - [Language.CSharp]: { - initialValue: csProgram, - compilerFactory: makeRemoteCompilerFactory(DotnetWorker), - }, - [Language.Java]: { - initialValue: javaProgram, - compilerFactory: makeRemoteCompilerFactory(JavaWorker), - }, - [Language.Ruby]: { - initialValue: rubyProgram, - compilerFactory: makeRemoteCompilerFactory(RubyWorker), - }, -}; diff --git a/apps/ppp/src/routes/(app)/editor/runtimes.ts b/apps/ppp/src/routes/(app)/editor/runtimes.ts new file mode 100644 index 00000000..50b6ff75 --- /dev/null +++ b/apps/ppp/src/routes/(app)/editor/runtimes.ts @@ -0,0 +1,73 @@ +import type { CompilerFactory, Program } from 'libs/compiler'; +import { makeRemoteCompilerFactory, type RemoteCompilerFactoryOptions } from 'libs/compiler/actor'; + +import { Language } from '$lib/language'; +import PhpWorker from '$lib/runtime/php/worker?worker'; +import TsWorker from '$lib/runtime/ts/worker?worker'; +import PythonWorker from '$lib/runtime/python/worker?worker'; +import JsWorker from '$lib/runtime/js/worker?worker'; +import GoWorker from '$lib/runtime/go/worker?worker'; +import RustWorker from '$lib/runtime/rust/worker?worker'; +import GleamWorker from '$lib/runtime/gleam/worker?worker'; +import JavaWorker from '$lib/runtime/java/worker?worker'; +import RubyWorker from '$lib/runtime/ruby/worker?worker'; +import DotnetWorker from '$lib/runtime/dotnet/worker?worker'; + +import phpProgram from './_program.php?raw'; +import tsProgram from './_program.ts?raw'; +import pythonProgram from './_program.py?raw'; +import jsProgram from './_program.js?raw'; +import goProgram from './_program.go?raw'; +import rustProgram from './_program.rs?raw'; +import gleamProgram from './_program.gleam?raw'; +import javaProgram from './_program.java?raw'; +import csProgram from './_program.cs?raw'; +import rubyProgram from './_program.rb?raw'; + +interface Runtime { + initialValue: string; + compilerFactory: CompilerFactory; +} + +export const RUNTIMES: Record = { + [Language.JavaScript]: { + initialValue: jsProgram, + compilerFactory: makeRemoteCompilerFactory(JsWorker) + }, + [Language.TypeScript]: { + initialValue: tsProgram, + compilerFactory: makeRemoteCompilerFactory(TsWorker) + }, + [Language.Python]: { + initialValue: pythonProgram, + compilerFactory: makeRemoteCompilerFactory(PythonWorker) + }, + [Language.PHP]: { + initialValue: phpProgram, + compilerFactory: makeRemoteCompilerFactory(PhpWorker) + }, + [Language.Go]: { + initialValue: goProgram, + compilerFactory: makeRemoteCompilerFactory(GoWorker) + }, + [Language.Rust]: { + initialValue: rustProgram, + compilerFactory: makeRemoteCompilerFactory(RustWorker) + }, + [Language.Gleam]: { + initialValue: gleamProgram, + compilerFactory: makeRemoteCompilerFactory(GleamWorker) + }, + [Language.CSharp]: { + initialValue: csProgram, + compilerFactory: makeRemoteCompilerFactory(DotnetWorker) + }, + [Language.Java]: { + initialValue: javaProgram, + compilerFactory: makeRemoteCompilerFactory(JavaWorker) + }, + [Language.Ruby]: { + initialValue: rubyProgram, + compilerFactory: makeRemoteCompilerFactory(RubyWorker) + } +}; diff --git a/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/csharp/factory.ts b/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/csharp/factory.ts index 4ba9ed1f..dc1ce2aa 100644 --- a/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/csharp/factory.ts +++ b/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/csharp/factory.ts @@ -1,23 +1,21 @@ -import { makeRemoteTestCompilerFactory } from "libs/testing/actor"; +import { makeRemoteTestCompilerFactory } from 'libs/testing/actor'; -import Worker from "$lib/runtime/dotnet/test-worker?worker"; +import Worker from '$lib/runtime/dotnet/test-worker?worker'; // Only type imports are allowed -import type { TestCompilerFactory } from "libs/testing"; +import type { RemoteCompilerFactoryOptions } from 'libs/compiler/actor'; +import type { TestCompilerFactory } from 'libs/testing'; -import type { DotnetTestWorkerConfig } from "$lib/runtime/dotnet/test-worker"; +import type { DotnetTestWorkerConfig } from '$lib/runtime/dotnet/test-worker'; -import type { Input, Output } from "../../tests-data"; +import type { Input, Output } from '../../tests-data'; -export const factory: TestCompilerFactory = - makeRemoteTestCompilerFactory( - Worker, - ( - ctx, - { dotnetTestCompilerFactory, makeExecutionCode }: DotnetTestWorkerConfig - ) => { - const definitions = `struct Args { +export const factory: TestCompilerFactory = + makeRemoteTestCompilerFactory( + Worker, + (ctx, { dotnetTestCompilerFactory, makeExecutionCode }: DotnetTestWorkerConfig) => { + const definitions = `struct Args { [JsonPropertyName("base")] public int Base { get; set; } [JsonPropertyName("amount")] @@ -26,7 +24,7 @@ export const factory: TestCompilerFactory = [JsonPropertyName("paymentSystem")] public string SystemType { get; set; } }`; - const executionCode = `var args = JsonSerializer.Deserialize(jsonArguments); + const executionCode = `var args = JsonSerializer.Deserialize(jsonArguments); var type = args.SystemType switch { "paypal" => SystemType.PayPal, "webmoney" => SystemType.WebMoney, @@ -36,11 +34,11 @@ var type = args.SystemType switch { var result = Solution.Payment(type, args.Base, args.Amount); `; - return dotnetTestCompilerFactory.create(ctx, { - executionCode: makeExecutionCode({ - additionalDefinitions: definitions, - executionCode, - }), - }); - } - ); + return dotnetTestCompilerFactory.create(ctx, { + executionCode: makeExecutionCode({ + additionalDefinitions: definitions, + executionCode + }) + }); + } + ); diff --git a/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/gleam/factory.ts b/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/gleam/factory.ts index 392a0905..06ec9e0d 100644 --- a/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/gleam/factory.ts +++ b/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/gleam/factory.ts @@ -1,38 +1,35 @@ -import { makeRemoteTestCompilerFactory } from "libs/testing/actor"; +import { makeRemoteTestCompilerFactory } from 'libs/testing/actor'; -import Worker from "$lib/runtime/gleam/test-worker?worker"; +import Worker from '$lib/runtime/gleam/test-worker?worker'; // Only type imports are allowed -import type { TestCompilerFactory } from "libs/testing"; -import type { CustomType } from "gleam-runtime/stdlib/gleam.mjs"; +import type { RemoteCompilerFactoryOptions } from 'libs/compiler/actor'; +import type { TestCompilerFactory } from 'libs/testing'; +import type { CustomType } from 'gleam-runtime/stdlib/gleam.mjs'; -import type { GleamTestWorkerConfig } from "$lib/runtime/gleam/test-worker"; +import type { GleamTestWorkerConfig } from '$lib/runtime/gleam/test-worker'; -import type { PaymentSystemType } from "../../reference"; -import type { Input, Output } from "../../tests-data"; +import type { PaymentSystemType } from '../../reference'; +import type { Input, Output } from '../../tests-data'; interface TestingModule { - PayPal: CustomType; - WebMoney: CustomType; - CatBank: CustomType; - payment(type: CustomType, base: number, amount: number): number; + PayPal: CustomType; + WebMoney: CustomType; + CatBank: CustomType; + payment(type: CustomType, base: number, amount: number): number; } -export const factory: TestCompilerFactory = - makeRemoteTestCompilerFactory( - Worker, - (ctx, { gleamTestCompilerFactory }: GleamTestWorkerConfig) => - gleamTestCompilerFactory.create(ctx, async (m: TestingModule, input) => { - const systems: Record = { - "cat-bank": m.CatBank, - paypal: m.PayPal, - webmoney: m.WebMoney, - }; - return m.payment( - systems[input.paymentSystem], - input.base, - input.amount - ); - }) - ); +export const factory: TestCompilerFactory = + makeRemoteTestCompilerFactory( + Worker, + (ctx, { gleamTestCompilerFactory }: GleamTestWorkerConfig) => + gleamTestCompilerFactory.create(ctx, async (m: TestingModule, input) => { + const systems: Record = { + 'cat-bank': m.CatBank, + paypal: m.PayPal, + webmoney: m.WebMoney + }; + return m.payment(systems[input.paymentSystem], input.base, input.amount); + }) + ); diff --git a/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/go/factory.ts b/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/go/factory.ts index 13c9f19d..94cd9440 100644 --- a/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/go/factory.ts +++ b/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/go/factory.ts @@ -1,29 +1,27 @@ -import { makeRemoteTestCompilerFactory } from "libs/testing/actor"; +import { makeRemoteTestCompilerFactory } from 'libs/testing/actor'; -import Worker from "$lib/runtime/go/test-worker?worker"; +import Worker from '$lib/runtime/go/test-worker?worker'; // Only type imports are allowed -import type { TestCompilerFactory } from "libs/testing"; +import type { RemoteCompilerFactoryOptions } from 'libs/compiler/actor'; +import type { TestCompilerFactory } from 'libs/testing'; -import type { GoTestWorkerConfig } from "$lib/runtime/go/test-worker"; +import type { GoTestWorkerConfig } from '$lib/runtime/go/test-worker'; -import type { Input, Output } from "../../tests-data"; -import type { PaymentSystemType } from "../../reference"; +import type { Input, Output } from '../../tests-data'; +import type { PaymentSystemType } from '../../reference'; -export const factory: TestCompilerFactory = - makeRemoteTestCompilerFactory( - Worker, - (ctx, { goTestCompilerFactory }: GoTestWorkerConfig) => { - const GO_PAYMENT_SYSTEM_TYPES: Record = { - paypal: 0, - webmoney: 1, - "cat-bank": 2, - }; - return goTestCompilerFactory.create( - ctx, - ({ paymentSystem, amount, base }) => - `solution.Payment(solution.SystemType(${GO_PAYMENT_SYSTEM_TYPES[paymentSystem]}), ${base}, ${amount})` - ); - } - ); +export const factory: TestCompilerFactory = + makeRemoteTestCompilerFactory(Worker, (ctx, { goTestCompilerFactory }: GoTestWorkerConfig) => { + const GO_PAYMENT_SYSTEM_TYPES: Record = { + paypal: 0, + webmoney: 1, + 'cat-bank': 2 + }; + return goTestCompilerFactory.create( + ctx, + ({ paymentSystem, amount, base }) => + `solution.Payment(solution.SystemType(${GO_PAYMENT_SYSTEM_TYPES[paymentSystem]}), ${base}, ${amount})` + ); + }); diff --git a/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/java/factory.ts b/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/java/factory.ts index 51626451..38aea4e1 100644 --- a/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/java/factory.ts +++ b/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/java/factory.ts @@ -1,46 +1,44 @@ -import { makeRemoteTestCompilerFactory } from "libs/testing/actor"; +import { makeRemoteTestCompilerFactory } from 'libs/testing/actor'; -import Worker from "$lib/runtime/java/test-worker?worker"; +import Worker from '$lib/runtime/java/test-worker?worker'; // Only type imports are allowed -import type { TestCompilerFactory } from "libs/testing"; +import type { RemoteCompilerFactoryOptions } from 'libs/compiler/actor'; +import type { TestCompilerFactory } from 'libs/testing'; -import type { JavaTestWorkerConfig } from "$lib/runtime/java/test-worker"; +import type { JavaTestWorkerConfig } from '$lib/runtime/java/test-worker'; -import type { Input, Output } from "../../tests-data"; -import type { PaymentSystemType } from "../../reference"; +import type { Input, Output } from '../../tests-data'; +import type { PaymentSystemType } from '../../reference'; -export const factory: TestCompilerFactory = - makeRemoteTestCompilerFactory( - Worker, - (ctx, { javaTestCompilerFactory, util }: JavaTestWorkerConfig) => { - const JAVA_PAYMENT_SYSTEM_TYPES: Record = { - paypal: "PAY_PAL", - webmoney: "WEB_MONEY", - "cat-bank": "CAT_BANK", - }; - return javaTestCompilerFactory.create(ctx, { - classDefinitions: `static native String getSystemType(); +export const factory: TestCompilerFactory = + makeRemoteTestCompilerFactory( + Worker, + (ctx, { javaTestCompilerFactory, util }: JavaTestWorkerConfig) => { + const JAVA_PAYMENT_SYSTEM_TYPES: Record = { + paypal: 'PAY_PAL', + webmoney: 'WEB_MONEY', + 'cat-bank': 'CAT_BANK' + }; + return javaTestCompilerFactory.create(ctx, { + classDefinitions: `static native String getSystemType(); static native int getBase(); static native int getAmount(); static native void saveResult(int result);`, - mainMethodBody: `saveResult(Solution.payment( + mainMethodBody: `saveResult(Solution.payment( SystemType.valueOf(getSystemType()), getBase(), getAmount() ));`, - nativesFactory: (input, save) => ({ - // @ts-expect-error TODO: import thread type - "getSystemType()Ljava/lang/String;": (t) => - util.initString( - t.getBsCl(), - JAVA_PAYMENT_SYSTEM_TYPES[input.paymentSystem] - ), - "getBase()I": () => input.base, - "getAmount()I": () => input.amount, - "saveResult(I)V": (_: unknown, result: number) => save(result), - }), - }); - } - ); + nativesFactory: (input, save) => ({ + // @ts-expect-error TODO: import thread type + 'getSystemType()Ljava/lang/String;': (t) => + util.initString(t.getBsCl(), JAVA_PAYMENT_SYSTEM_TYPES[input.paymentSystem]), + 'getBase()I': () => input.base, + 'getAmount()I': () => input.amount, + 'saveResult(I)V': (_: unknown, result: number) => save(result) + }) + }); + } + ); diff --git a/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/javascript/factory.ts b/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/javascript/factory.ts index 40be6703..ca7150a9 100644 --- a/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/javascript/factory.ts +++ b/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/javascript/factory.ts @@ -1,25 +1,24 @@ -import { makeRemoteTestCompilerFactory } from "libs/testing/actor"; +import { makeRemoteTestCompilerFactory } from 'libs/testing/actor'; -import Worker from "$lib/runtime/js/test-worker?worker"; +import Worker from '$lib/runtime/js/test-worker?worker'; // Only type imports are allowed -import type { TestCompilerFactory } from "libs/testing"; +import type { RemoteCompilerFactoryOptions } from 'libs/compiler/actor'; +import type { TestCompilerFactory } from 'libs/testing'; -import type { JsTestWorkerConfig } from "$lib/runtime/js/test-worker"; +import type { JsTestWorkerConfig } from '$lib/runtime/js/test-worker'; -import type { Input, Output } from "../../tests-data"; -import type { PaymentSystemType } from "../../reference"; +import type { Input, Output } from '../../tests-data'; +import type { PaymentSystemType } from '../../reference'; interface TestingModule { - payment(type: PaymentSystemType, base: number, amount: number): number; + payment(type: PaymentSystemType, base: number, amount: number): number; } -export const factory: TestCompilerFactory = - makeRemoteTestCompilerFactory( - Worker, - async (_, { jsTestCompilerFactory }: JsTestWorkerConfig) => - jsTestCompilerFactory.create(async (m: TestingModule, input) => - m.payment(input.paymentSystem, input.base, input.amount) - ) - ); +export const factory: TestCompilerFactory = + makeRemoteTestCompilerFactory(Worker, async (_, { jsTestCompilerFactory }: JsTestWorkerConfig) => + jsTestCompilerFactory.create(async (m: TestingModule, input) => + m.payment(input.paymentSystem, input.base, input.amount) + ) + ); diff --git a/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/php/factory.ts b/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/php/factory.ts index 711247dd..a1bef76e 100644 --- a/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/php/factory.ts +++ b/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/php/factory.ts @@ -1,29 +1,27 @@ -import { makeRemoteTestCompilerFactory } from "libs/testing/actor"; +import { makeRemoteTestCompilerFactory } from 'libs/testing/actor'; -import Worker from "$lib/runtime/php/test-worker?worker"; +import Worker from '$lib/runtime/php/test-worker?worker'; // Only type imports are allowed -import type { TestCompilerFactory } from "libs/testing"; +import type { RemoteCompilerFactoryOptions } from 'libs/compiler/actor'; +import type { TestCompilerFactory } from 'libs/testing'; -import type { PhpTestWorkerConfig } from "$lib/runtime/php/test-worker"; +import type { PhpTestWorkerConfig } from '$lib/runtime/php/test-worker'; -import type { Input, Output } from "../../tests-data"; -import type { PaymentSystemType } from "../../reference"; +import type { Input, Output } from '../../tests-data'; +import type { PaymentSystemType } from '../../reference'; -export const factory: TestCompilerFactory = - makeRemoteTestCompilerFactory( - Worker, - (ctx, { phpTestCompilerFactory }: PhpTestWorkerConfig) => { - const PHP_PAYMENT_SYSTEM_TYPES: Record = { - paypal: "PaymentSystemType::PAYPAL", - webmoney: "PaymentSystemType::WEBMONEY", - "cat-bank": "PaymentSystemType::CAT_BANK", - }; - return phpTestCompilerFactory.create( - ctx, - ({ paymentSystem, base, amount }) => - `payment(${PHP_PAYMENT_SYSTEM_TYPES[paymentSystem]}, ${base}, ${amount})` - ); - } - ); +export const factory: TestCompilerFactory = + makeRemoteTestCompilerFactory(Worker, (ctx, { phpTestCompilerFactory }: PhpTestWorkerConfig) => { + const PHP_PAYMENT_SYSTEM_TYPES: Record = { + paypal: 'PaymentSystemType::PAYPAL', + webmoney: 'PaymentSystemType::WEBMONEY', + 'cat-bank': 'PaymentSystemType::CAT_BANK' + }; + return phpTestCompilerFactory.create( + ctx, + ({ paymentSystem, base, amount }) => + `payment(${PHP_PAYMENT_SYSTEM_TYPES[paymentSystem]}, ${base}, ${amount})` + ); + }); diff --git a/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/python/factory.ts b/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/python/factory.ts index cfdead74..127a3fb1 100644 --- a/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/python/factory.ts +++ b/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/python/factory.ts @@ -1,22 +1,22 @@ -import { makeRemoteTestCompilerFactory } from "libs/testing/actor"; +import { makeRemoteTestCompilerFactory } from 'libs/testing/actor'; -import Worker from "$lib/runtime/python/test-worker?worker"; +import Worker from '$lib/runtime/python/test-worker?worker'; // Only type imports are allowed -import type { TestCompilerFactory } from "libs/testing"; +import type { RemoteCompilerFactoryOptions } from 'libs/compiler/actor'; +import type { TestCompilerFactory } from 'libs/testing'; -import type { PythonTestWorkerConfig } from "$lib/runtime/python/test-worker"; +import type { PythonTestWorkerConfig } from '$lib/runtime/python/test-worker'; -import type { Input, Output } from "../../tests-data"; +import type { Input, Output } from '../../tests-data'; -export const factory: TestCompilerFactory = - makeRemoteTestCompilerFactory( - Worker, - (ctx, { pythonTestCompilerFactory }: PythonTestWorkerConfig) => - pythonTestCompilerFactory.create( - ctx, - ({ paymentSystem, amount, base }) => - `payment("${paymentSystem}", ${base}, ${amount})` - ) - ); +export const factory: TestCompilerFactory = + makeRemoteTestCompilerFactory( + Worker, + (ctx, { pythonTestCompilerFactory }: PythonTestWorkerConfig) => + pythonTestCompilerFactory.create( + ctx, + ({ paymentSystem, amount, base }) => `payment("${paymentSystem}", ${base}, ${amount})` + ) + ); diff --git a/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/ruby/factory.ts b/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/ruby/factory.ts index bff57cf7..109a62d2 100644 --- a/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/ruby/factory.ts +++ b/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/ruby/factory.ts @@ -1,22 +1,20 @@ -import { makeRemoteTestCompilerFactory } from "libs/testing/actor"; +import { makeRemoteTestCompilerFactory } from 'libs/testing/actor'; -import Worker from "$lib/runtime/ruby/test-worker?worker"; +import Worker from '$lib/runtime/ruby/test-worker?worker'; // Only type imports are allowed -import type { TestCompilerFactory } from "libs/testing"; +import type { RemoteCompilerFactoryOptions } from 'libs/compiler/actor'; +import type { TestCompilerFactory } from 'libs/testing'; -import type { RubyTestWorkerConfig } from "$lib/runtime/ruby/test-worker"; +import type { RubyTestWorkerConfig } from '$lib/runtime/ruby/test-worker'; -import type { Input, Output } from "../../tests-data"; +import type { Input, Output } from '../../tests-data'; -export const factory: TestCompilerFactory = - makeRemoteTestCompilerFactory( - Worker, - (ctx, { rubyTestCompilerFactory }: RubyTestWorkerConfig) => - rubyTestCompilerFactory.create( - ctx, - ({ paymentSystem, base, amount }) => - `payment("${paymentSystem}", ${base}, ${amount})` - ) - ); +export const factory: TestCompilerFactory = + makeRemoteTestCompilerFactory(Worker, (ctx, { rubyTestCompilerFactory }: RubyTestWorkerConfig) => + rubyTestCompilerFactory.create( + ctx, + ({ paymentSystem, base, amount }) => `payment("${paymentSystem}", ${base}, ${amount})` + ) + ); diff --git a/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/rust/factory.ts b/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/rust/factory.ts index 4767246d..4479a03a 100644 --- a/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/rust/factory.ts +++ b/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/rust/factory.ts @@ -1,37 +1,38 @@ -import { makeRemoteTestCompilerFactory } from "libs/testing/actor"; +import { makeRemoteTestCompilerFactory } from 'libs/testing/actor'; -import Worker from "$lib/runtime/rust/test-worker?worker"; +import Worker from '$lib/runtime/rust/test-worker?worker'; // Only type imports are allowed -import type { TestCompilerFactory } from "libs/testing"; +import type { RemoteCompilerFactoryOptions } from 'libs/compiler/actor'; +import type { TestCompilerFactory } from 'libs/testing'; -import type { RustTestWorkerConfig } from "$lib/runtime/rust/test-worker"; +import type { RustTestWorkerConfig } from '$lib/runtime/rust/test-worker'; -import type { Input, Output } from "../../tests-data"; -import type { PaymentSystemType } from "../../reference"; +import type { Input, Output } from '../../tests-data'; +import type { PaymentSystemType } from '../../reference'; -export const factory: TestCompilerFactory = - makeRemoteTestCompilerFactory( - Worker, - (ctx, { rustTestCompilerFactory }: RustTestWorkerConfig) => { - const RUST_PAYMENT_SYSTEM_TYPES: Record = { - paypal: "PaymentSystemType::PayPal", - webmoney: "PaymentSystemType::WebMoney", - "cat-bank": "PaymentSystemType::CatBank", - }; - return rustTestCompilerFactory.create( - ctx, - ({ paymentSystem, amount, base }) => - `let str = payment(${RUST_PAYMENT_SYSTEM_TYPES[paymentSystem]}, ${base}, ${amount}).to_string(); +export const factory: TestCompilerFactory = + makeRemoteTestCompilerFactory( + Worker, + (ctx, { rustTestCompilerFactory }: RustTestWorkerConfig) => { + const RUST_PAYMENT_SYSTEM_TYPES: Record = { + paypal: 'PaymentSystemType::PayPal', + webmoney: 'PaymentSystemType::WebMoney', + 'cat-bank': 'PaymentSystemType::CatBank' + }; + return rustTestCompilerFactory.create( + ctx, + ({ paymentSystem, amount, base }) => + `let str = payment(${RUST_PAYMENT_SYSTEM_TYPES[paymentSystem]}, ${base}, ${amount}).to_string(); let output_content = str.as_bytes();`, - (result) => { - const r = parseInt(result, 10); - if (isNaN(r)) { - throw new Error(`Invalid result type: ${result}, expected number`); - } - return r; - } - ); - } - ); + (result) => { + const r = parseInt(result, 10); + if (isNaN(r)) { + throw new Error(`Invalid result type: ${result}, expected number`); + } + return r; + } + ); + } + ); diff --git a/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/typescript/factory.ts b/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/typescript/factory.ts index 70afffc3..36381379 100644 --- a/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/typescript/factory.ts +++ b/apps/ppp/src/routes/(app)/problems/design-patterns/(factory)/payment-system/runtimes/typescript/factory.ts @@ -1,24 +1,24 @@ -import { makeRemoteTestCompilerFactory } from "libs/testing/actor"; +import { makeRemoteTestCompilerFactory } from 'libs/testing/actor'; -import Worker from "$lib/runtime/ts/test-worker?worker"; +import Worker from '$lib/runtime/ts/test-worker?worker'; // Only type imports are allowed +import type { RemoteCompilerFactoryOptions } from 'libs/compiler/actor'; import type { TestCompilerFactory } from 'libs/testing'; -import type { TsTestWorkerConfig } from "$lib/runtime/ts/test-worker"; +import type { TsTestWorkerConfig } from '$lib/runtime/ts/test-worker'; -import type { PaymentSystemType } from "../../reference"; -import type { Input, Output } from "../../tests-data"; +import type { PaymentSystemType } from '../../reference'; +import type { Input, Output } from '../../tests-data'; interface TestingModule { - payment(type: PaymentSystemType, base: number, amount: number): number; + payment(type: PaymentSystemType, base: number, amount: number): number; } -export const factory: TestCompilerFactory = makeRemoteTestCompilerFactory( - Worker, - async (_, { tsTestCompilerFactory }: TsTestWorkerConfig) => - tsTestCompilerFactory.create(async (m: TestingModule, input) => - m.payment(input.paymentSystem, input.base, input.amount) - ) -); +export const factory: TestCompilerFactory = + makeRemoteTestCompilerFactory(Worker, async (_, { tsTestCompilerFactory }: TsTestWorkerConfig) => + tsTestCompilerFactory.create(async (m: TestingModule, input) => + m.payment(input.paymentSystem, input.base, input.amount) + ) + ); diff --git a/packages/libs/src/compiler/actor.ts b/packages/libs/src/compiler/actor.ts index fa70a56d..d9143d04 100644 --- a/packages/libs/src/compiler/actor.ts +++ b/packages/libs/src/compiler/actor.ts @@ -7,83 +7,80 @@ import { type EventMessage, type IncomingMessage, type OutgoingMessage, -} from "../actor/index.js"; -import { SharedQueue, StreamType, createSharedStreamsClient, createSharedStreamsServer } from '../sync/index.js'; +} from "libs/actor"; +import { SharedQueue, createStreamsClient, writeToQueue } from "libs/sync"; import { CanceledError, createContext, createRecoverableContext, withCancel, type Context, -} from "../context.js"; -import type { Streams } from "../io.js"; -import { stringifyError } from "../error.js"; -import { BACKSPACE, createLogger } from "../logger.js"; +} from "libs/context"; +import { stringifyError } from "libs/error"; +import { createLogger } from "libs/logger"; +import type { ReadableStreamOfBytes, Streams, Writer } from 'libs/io'; -import type { Compiler, CompilerFactory, File, Program } from "./compiler.js"; +import type { + Compiler, + CompilerFactory, + File, + Program, +} from "./compiler.js"; interface Handlers { [key: string]: any; - initialize(buffer: SharedArrayBuffer | ArrayBuffer): Promise; - destroy(): void; + initialize (buffer: SharedArrayBuffer | ArrayBuffer): Promise; + destroy (): void; - compile(files: File[]): Promise; - stopCompile(): void; + compile (files: File[]): Promise; + stopCompile (): void; - run(): Promise; - stopRun(): void; + run (): Promise; + stopRun (): void; } type Incoming = IncomingMessage; -interface ReadEventMessage extends EventMessage<"read", undefined> {} +interface WriteEventMessage extends EventMessage<"write", Uint8Array> { } -interface WriteEventMessage extends EventMessage<"write", { type: StreamType, data: Uint8Array }> {} - -type CompilerActorEvent = WriteEventMessage | ReadEventMessage; +type CompilerActorEvent = WriteEventMessage; type Outgoing = OutgoingMessage | CompilerActorEvent; class CompilerActor extends Actor implements Disposable { protected compiler: Compiler | null = null; protected compilerCtx = createRecoverableContext(() => { - this.compiler = null - return withCancel(createContext()) - }) - protected program: Program | null = null + this.compiler = null; + return withCancel(createContext()); + }); + protected program: Program | null = null; protected programCtx = createRecoverableContext(() => { - this.program = null - return withCancel(this.compilerCtx.ref) - }) - protected runCtx = createRecoverableContext(() => withCancel(this.programCtx.ref)) + this.program = null; + return withCancel(this.compilerCtx.ref); + }); + protected runCtx = createRecoverableContext(() => + withCancel(this.programCtx.ref) + ); constructor( connection: Connection, - compilerFactory: CompilerFactory + compilerFactory: CompilerFactory ) { const handlers: Handlers = { initialize: async (buffer: SharedArrayBuffer) => { - const sharedQueue = new SharedQueue(buffer) - const streams = createSharedStreamsClient( - sharedQueue, - () => connection.send({ - type: MessageType.Event, - event: "read", - payload: undefined - }), - (type, data) => connection.send({ - type: MessageType.Event, - event: "write", - payload: { - type, - data - } - }) - ) - this.compiler = await compilerFactory(this.compilerCtx.ref, streams); + this.compiler = await compilerFactory(this.compilerCtx.ref, createStreamsClient(new SharedQueue(buffer), + { + write (data) { + connection.send({ + type: MessageType.Event, + event: "write", + payload: data, + }); + }, + })); }, destroy: () => { - this.compilerCtx.cancel() + this.compilerCtx.cancel(); }, compile: async (files) => { if (this.compiler === null) { @@ -92,7 +89,7 @@ class CompilerActor extends Actor implements Disposable { this.program = await this.compiler.compile(this.programCtx.ref, files); }, stopCompile: () => { - this.programCtx.cancel() + this.programCtx.cancel(); }, run: async () => { if (this.program === null) { @@ -119,16 +116,16 @@ class CompilerActor extends Actor implements Disposable { [Symbol.dispose] (): void { this.compiler = null; - this.compilerCtx[Symbol.dispose]() + this.compilerCtx[Symbol.dispose](); this.program = null; - this.programCtx[Symbol.dispose]() - this.runCtx[Symbol.dispose]() + this.programCtx[Symbol.dispose](); + this.runCtx[Symbol.dispose](); } } -export function startCompilerActor( +export function startCompilerActor ( ctx: Context, - compilerFactory: CompilerFactory + compilerFactory: CompilerFactory ) { const connection = new WorkerConnection( self as unknown as Worker @@ -136,75 +133,62 @@ export function startCompilerActor( connection.start(ctx); const actor = new CompilerActor(connection, compilerFactory); ctx.onCancel(() => { - actor[Symbol.dispose]() - }) + actor[Symbol.dispose](); + }); actor.start(ctx); } interface WorkerConstructor { - new (): Worker; + new(): Worker; } -export function makeRemoteCompilerFactory(Worker: WorkerConstructor) { - return async (ctx: Context, streams: Streams): Promise> => { +export interface RemoteCompilerFactoryOptions { + input: ReadableStreamOfBytes + output: Writer +} + +export function makeRemoteCompilerFactory (Worker: WorkerConstructor) { + return async ( + ctx: Context, + { input, output }: RemoteCompilerFactoryOptions + ): Promise> => { const worker = new Worker(); - let read = -1 - const sub = streams.in.onData((data) => { - if (read === -1) { - return - } - if (data.length === 0) { - server.write(read) - // EOF - server.write(0) - read = -1 - } - if (data === BACKSPACE) { - // TODO: What to do with non-ASCII characters? - read -= 1; - } else { - read += data.length - } - }) ctx.onCancel(() => { - sub[Symbol.dispose]() - worker.terminate() - }) + worker.terminate(); + }); const connection = new WorkerConnection(worker); connection.start(ctx); - const log = createLogger(streams.out); - const Buffer = window.SharedArrayBuffer - ? SharedArrayBuffer - : ArrayBuffer; - const buffer = new Buffer(1024 * 1024) - const server = createSharedStreamsServer(new SharedQueue(buffer), streams) + const log = createLogger(output); + const Buffer = window.SharedArrayBuffer ? SharedArrayBuffer : ArrayBuffer; + const buffer = new Buffer(1024 * 1024); + const sharedWriter = writeToQueue(input, new SharedQueue(buffer)); + ctx.onCancel(() => { + sharedWriter[Symbol.dispose](); + }); const remote = startRemote( ctx, log, connection, { - read() { - read = 0; - }, - write({ type, data }) { - server.onClientWrite(type, data) + write (data) { + output.write(data); }, - error(err) { + error (err) { log.error(err instanceof CanceledError ? err.message : err); }, } ); - using _ = ctx.onCancel(() => remote.destroy()) + using _ = ctx.onCancel(() => remote.destroy()); await remote.initialize(buffer); return { - async compile(ctx, files) { - using _ = ctx.onCancel(() => remote.stopCompile()) + async compile (ctx, files) { + using _ = ctx.onCancel(() => remote.stopCompile()); await remote.compile(files); return { - async run(ctx) { - using _ = ctx.onCancel(() => remote.stopRun()) + async run (ctx) { + using _ = ctx.onCancel(() => remote.stopRun()); await remote.run(); - } + }, }; }, }; diff --git a/packages/libs/src/compiler/compiler.ts b/packages/libs/src/compiler/compiler.ts index a029bc39..83408948 100644 --- a/packages/libs/src/compiler/compiler.ts +++ b/packages/libs/src/compiler/compiler.ts @@ -1,5 +1,4 @@ -import type { Context } from 'libs/context'; -import type { Streams } from 'libs/io'; +import type { Context } from "libs/context"; export interface Program { run: (ctx: Context) => Promise; @@ -14,4 +13,7 @@ export interface Compiler

    { compile: (ctx: Context, files: File[]) => Promise

    ; } -export type CompilerFactory

    = (ctx: Context, streams: Streams) => Promise>; +export type CompilerFactory = ( + ctx: Context, + options: O +) => Promise>; diff --git a/packages/libs/src/io.ts b/packages/libs/src/io.ts index e9e85d4e..75b3f73a 100644 --- a/packages/libs/src/io.ts +++ b/packages/libs/src/io.ts @@ -1,14 +1,21 @@ export interface Writer { - write(data: Uint8Array): void + write(data: Uint8Array): void; } export interface Reader { read(): Uint8Array; - onData(handler: (data: Uint8Array) => void): Disposable } export interface Streams { - in: Reader - out: Writer - err: Writer + in: Reader; + out: Writer; + err: Writer; } + +export type ReadableStreamOfBytes = ReadableStream; + +export type WritableStreamOfBytes = WritableStream; + +export type BytesStreamWriter = WritableStreamDefaultWriter< + Uint8Array +>; diff --git a/packages/libs/src/sync/io.ts b/packages/libs/src/sync/io.ts index dee26b82..34ba12d3 100644 --- a/packages/libs/src/sync/io.ts +++ b/packages/libs/src/sync/io.ts @@ -1,60 +1,55 @@ -import type { Streams, Writer } from "../io.js"; +import type { ReadableStreamOfBytes, Streams, Writer } from "libs/io"; +import { makeErrorWriter } from "libs/logger"; +import { noop } from "libs/function"; import type { SharedQueue } from "./shared-queue.js"; -export enum StreamType { - Out = 1, - Err = 2, +export function readFromQueue(inputQueue: SharedQueue) { + const input = new ReadableStream({ + pull(controller) { + const r = inputQueue.blockingRead(); + const bytes = r.next().value.bytes; + // NOTE: The generator must be exhausted + r.next(); + controller.enqueue(bytes); + }, + }); + return input; +} + +export function writeToQueue(input: ReadableStreamOfBytes, queue: SharedQueue) { + const w = new WritableStream({ + write(bytes) { + queue.pushBytes(bytes); + queue.commit(); + }, + }); + const controller = new AbortController(); + input + .pipeTo(w, { signal: controller.signal, preventCancel: true }) + .catch(noop); + return { + [Symbol.dispose]() { + controller.abort(); + }, + }; } -export function createSharedStreamsClient( +export function createStreamsClient( inputQueue: SharedQueue, - beforeRead: () => void, - write: (stream: StreamType, data: Uint8Array) => void + writer: Writer ): Streams { return { in: { read() { - beforeRead() const r = inputQueue.blockingRead(); const bytes = r.next().value.bytes; // NOTE: The generator must be exhausted r.next(); return bytes; }, - onData() { - throw new Error("Not implemented"); - }, - }, - out: { - write(data) { - write(StreamType.Out, data); - }, - }, - err: { - write(data) { - write(StreamType.Err, data); - }, - }, - }; -} - -export function createSharedStreamsServer( - inputQueue: SharedQueue, - streams: Streams -) { - const writers: Record = { - [StreamType.Out]: streams.out, - [StreamType.Err]: streams.err, - }; - return { - onClientWrite(type: StreamType, data: Uint8Array) { - writers[type].write(data); - }, - write(size: number) { - const bytes = streams.in.read() - inputQueue.pushBytes(bytes.subarray(bytes.length - size)); - inputQueue.commit(); }, + out: writer, + err: makeErrorWriter(writer), }; } diff --git a/packages/libs/src/testing/actor.ts b/packages/libs/src/testing/actor.ts index b509d198..445ed381 100644 --- a/packages/libs/src/testing/actor.ts +++ b/packages/libs/src/testing/actor.ts @@ -4,11 +4,11 @@ import { createRecoverableContext, withCancel, type Context, -} from "../context.js"; -import { BACKSPACE, createLogger } from "../logger.js"; -import { stringifyError } from "../error.js"; -import { compileJsModule } from "../js.js"; -import type { Streams } from "../io.js"; +} from "libs/context"; +import { createLogger } from "libs/logger"; +import { stringifyError } from "libs/error"; +import { compileJsModule } from "libs/js"; +import type { Streams } from "libs/io"; import { Actor, MessageType, @@ -18,14 +18,10 @@ import { type EventMessage, type IncomingMessage, type OutgoingMessage, -} from "../actor/index.js"; -import { - createSharedStreamsClient, - createSharedStreamsServer, - SharedQueue, - StreamType -} from '../sync/index.js'; -import type { File } from "../compiler/index.js"; +} from "libs/actor"; +import { writeToQueue, SharedQueue, createStreamsClient } from "libs/sync"; +import type { File } from "libs/compiler"; +import type { RemoteCompilerFactoryOptions } from 'libs/compiler/actor'; import type { TestProgram, TestCompiler } from "./testing.js"; @@ -36,29 +32,27 @@ export interface InitConfig { interface Handlers { [key: string]: any; - initialize(config: InitConfig): Promise; - destroy(): void; + initialize (config: InitConfig): Promise; + destroy (): void; - compile(files: File[]): Promise; - stopCompile(): void; + compile (files: File[]): Promise; + stopCompile (): void; - test(data: I): Promise; - stopTest(): void; + test (data: I): Promise; + stopTest (): void; } type Incoming = IncomingMessage>; -interface ReadEventMessage extends EventMessage<"read", undefined> {} - -interface WriteEventMessage extends EventMessage<"write", { type: StreamType, data: Uint8Array }> {} +interface WriteEventMessage extends EventMessage<"write", Uint8Array> { } -type TestingActorEvent = WriteEventMessage | ReadEventMessage; +type TestingActorEvent = WriteEventMessage; type Outgoing = | OutgoingMessage, string> | TestingActorEvent; -async function evalEntity(functionStr: string) { +async function evalEntity (functionStr: string) { const moduleStr = `export default ${functionStr}`; const mod = await compileJsModule<{ default: T }>(moduleStr); return mod.default; @@ -75,65 +69,60 @@ export type TestCompilerSuperFactory = ( universalFactory: UniversalFactory ) => Promise>; -class TestCompilerActor extends Actor, string> implements Disposable { +class TestCompilerActor + extends Actor, string> + implements Disposable { protected compiler: TestCompiler | null = null; protected compilerCtx = createRecoverableContext(() => { - this.compiler = null - return withCancel(createContext()) - }) - protected program: TestProgram | null = null + this.compiler = null; + return withCancel(createContext()); + }); + protected program: TestProgram | null = null; protected programCtx = createRecoverableContext(() => { - this.program = null - return withCancel(this.compilerCtx.ref) - }) - protected runCtx = createRecoverableContext(() => withCancel(this.programCtx.ref)) + this.program = null; + return withCancel(this.compilerCtx.ref); + }); + protected runCtx = createRecoverableContext(() => + withCancel(this.programCtx.ref) + ); constructor( connection: Connection, Outgoing>, superFactory: TestCompilerSuperFactory ) { const handlers: Handlers = { - initialize: async({ universalFactoryFunction, buffer }) => { + initialize: async ({ universalFactoryFunction, buffer }) => { const universalFactory = await evalEntity>( universalFactoryFunction ); - const sharedQueue = new SharedQueue(buffer) - const client = createSharedStreamsClient( - sharedQueue, - () => connection.send({ - type: MessageType.Event, - event: "read", - payload: undefined, - }), - (type, data) => connection.send({ - type: MessageType.Event, - event: "write", - payload: { - type, - data - } - }) - ) this.compiler = await superFactory( this.compilerCtx.ref, - client, + createStreamsClient( + new SharedQueue(buffer), + { + write (data) { + connection.send({ + type: MessageType.Event, + event: "write", + payload: data, + }); + }, + } + ), universalFactory ); }, destroy: () => { - this.compilerCtx.cancel() + this.compilerCtx.cancel(); }, compile: async (files) => { if (this.compiler === null) { throw new Error("Test runner not initialized"); } - this.program = await this.compiler.compile( - this.programCtx.ref, - files - ); + this.program = await this.compiler.compile(this.programCtx.ref, files); }, stopCompile: () => { - this.programCtx.cancel() + this.programCtx.cancel(); }, test: async (input) => { if (this.program === null) { @@ -148,11 +137,11 @@ class TestCompilerActor extends Actor, string> implement try { return await this.program.run(this.runCtx.ref, input); } finally { - this.runCtx.cancel() + this.runCtx.cancel(); } }, stopTest: () => { - this.runCtx.cancel() + this.runCtx.cancel(); }, }; super(connection, handlers, stringifyError); @@ -160,14 +149,14 @@ class TestCompilerActor extends Actor, string> implement [Symbol.dispose] (): void { this.compiler = null; - this.compilerCtx[Symbol.dispose]() + this.compilerCtx[Symbol.dispose](); this.program = null; - this.programCtx[Symbol.dispose]() - this.runCtx[Symbol.dispose]() + this.programCtx[Symbol.dispose](); + this.runCtx[Symbol.dispose](); } } -export function startTestCompilerActor( +export function startTestCompilerActor ( ctx: Context, superFactory: TestCompilerSuperFactory ) { @@ -177,84 +166,66 @@ export function startTestCompilerActor( connection.start(ctx); const actor = new TestCompilerActor(connection, superFactory); ctx.onCancel(() => { - actor[Symbol.dispose]() - }) + actor[Symbol.dispose](); + }); actor.start(ctx); } interface WorkerConstructor { - new (): Worker; + new(): Worker; } -export function makeRemoteTestCompilerFactory( +export function makeRemoteTestCompilerFactory ( Worker: WorkerConstructor, universalFactory: UniversalFactory ) { - return async (ctx: Context, streams: Streams): Promise> => { + return async ( + ctx: Context, + { input, output }: RemoteCompilerFactoryOptions + ): Promise> => { const worker = new Worker(); - let read = -1 - const sub = streams.in.onData((data) => { - if (read === -1) { - return - } - if (data.length === 0) { - server.write(read) - // EOF - server.write(0) - read = -1 - } - if (data === BACKSPACE) { - // TODO: What to do with non-ASCII characters? - read -= 1; - } else { - read += data.length - } - }) ctx.onCancel(() => { - sub[Symbol.dispose]() - worker.terminate() - }) + worker.terminate(); + }); const connection = new WorkerConnection, Incoming>( worker ); connection.start(ctx); - const log = createLogger(streams.out); - const Buffer = window.SharedArrayBuffer - ? SharedArrayBuffer - : ArrayBuffer; - const buffer = new Buffer(1024 * 1024 * 10) - const server = createSharedStreamsServer(new SharedQueue(buffer), streams) + const log = createLogger(output); + const Buffer = window.SharedArrayBuffer ? SharedArrayBuffer : ArrayBuffer; + const buffer = new Buffer(1024 * 1024 * 10); + const sharedWriter = writeToQueue(input, new SharedQueue(buffer)); + ctx.onCancel(() => { + sharedWriter[Symbol.dispose](); + }); const remote = startRemote, string, TestingActorEvent>( ctx, log, connection, { - read() { - read = 0; + write (data) { + output.write(data); }, - write({ type, data }) { - server.onClientWrite(type, data) - }, - error(err) { + error (err) { log.error(err instanceof CanceledError ? err.message : err); }, } ); - using _ = ctx.onCancel(() => remote.destroy()) + using _ = ctx.onCancel(() => remote.destroy()); await remote.initialize({ universalFactoryFunction: universalFactory.toString(), - buffer + buffer, }); return { - async compile(ctx, files) { - using _ = ctx.onCancel(() => remote.stopCompile()) + async compile (ctx, files) { + using _ = ctx.onCancel(() => remote.stopCompile()); await remote.compile(files); return { - async run(ctx, input) { - using _ = ctx.onCancel(() => remote.stopTest()) + async run (ctx, input) { + using _ = ctx.onCancel(() => remote.stopTest()); return await remote.test(input); - } - } + }, + }; }, }; }; diff --git a/packages/libs/src/testing/testing.ts b/packages/libs/src/testing/testing.ts index 09d64b4d..e121a737 100644 --- a/packages/libs/src/testing/testing.ts +++ b/packages/libs/src/testing/testing.ts @@ -1,7 +1,7 @@ -import type { Logger } from "../logger.js"; -import type { Context } from "../context.js"; -import type { Compiler, CompilerFactory } from "../compiler/index.js"; -import { isDeepEqual } from '../deep-equal.js'; +import type { Logger } from "libs/logger"; +import type { Context } from "libs/context"; +import type { Compiler, CompilerFactory } from "libs/compiler"; +import { isDeepEqual } from "libs/deep-equal"; export interface TestCase { input: I; @@ -21,9 +21,12 @@ export interface TestProgram { run: (ctx: Context, input: I) => Promise; } -export type TestCompiler = Compiler> +export type TestCompiler = Compiler>; -export type TestCompilerFactory = CompilerFactory> +export type TestCompilerFactory = CompilerFactory< + Options, + TestProgram +>; export async function runTests( ctx: Context,