From 7567dacd9194d645c4e3c42e490e8d33364ada1c Mon Sep 17 00:00:00 2001 From: Roman Krasilnikov Date: Tue, 18 Nov 2025 00:45:18 +0300 Subject: [PATCH 1/6] WIP: Use web streams --- .../ppp/src/lib/components/editor/terminal.ts | 2 +- packages/libs/src/compiler/actor.ts | 45 +++++-------------- packages/libs/src/io.ts | 17 ++++--- packages/libs/src/logger.ts | 2 +- packages/libs/src/sync/io.ts | 36 ++++++++------- 5 files changed, 46 insertions(+), 56 deletions(-) diff --git a/apps/ppp/src/lib/components/editor/terminal.ts b/apps/ppp/src/lib/components/editor/terminal.ts index 8264e915..1c61b671 100644 --- a/apps/ppp/src/lib/components/editor/terminal.ts +++ b/apps/ppp/src/lib/components/editor/terminal.ts @@ -74,7 +74,7 @@ export function createStreams(terminal: Terminal): Streams { return { out, err: makeErrorWriter(out), - in: { + BytesReaderStream: { read() { const chunk = buffer.subarray(0, offset); offset = 0; diff --git a/packages/libs/src/compiler/actor.ts b/packages/libs/src/compiler/actor.ts index fa70a56d..226ead9c 100644 --- a/packages/libs/src/compiler/actor.ts +++ b/packages/libs/src/compiler/actor.ts @@ -16,7 +16,7 @@ import { withCancel, type Context, } from "../context.js"; -import type { Streams } from "../io.js"; +import type { ReadableStreamOfBytes, Streams, WebStreams, WritableStreamOfBytes, Writer } from "../io.js"; import { stringifyError } from "../error.js"; import { BACKSPACE, createLogger } from "../logger.js"; @@ -36,11 +36,9 @@ interface Handlers { type Incoming = IncomingMessage; -interface ReadEventMessage extends EventMessage<"read", undefined> {} - interface WriteEventMessage extends EventMessage<"write", { type: StreamType, data: Uint8Array }> {} -type CompilerActorEvent = WriteEventMessage | ReadEventMessage; +type CompilerActorEvent = WriteEventMessage; type Outgoing = OutgoingMessage | CompilerActorEvent; @@ -66,11 +64,6 @@ class CompilerActor extends Actor implements Disposable { 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", @@ -145,47 +138,31 @@ interface WorkerConstructor { new (): Worker; } +export interface CompilerFactoryOptions { + input: ReadableStreamOfBytes + output: Writer +} + export function makeRemoteCompilerFactory(Worker: WorkerConstructor) { - return async (ctx: Context, streams: Streams): Promise> => { + return async (ctx: Context, { input, output }: CompilerFactoryOptions): 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]() + server[Symbol.dispose]() worker.terminate() }) const connection = new WorkerConnection(worker); connection.start(ctx); - const log = createLogger(streams.out); + const log = createLogger(output); const Buffer = window.SharedArrayBuffer ? SharedArrayBuffer : ArrayBuffer; const buffer = new Buffer(1024 * 1024) - const server = createSharedStreamsServer(new SharedQueue(buffer), streams) + const server = createSharedStreamsServer(new SharedQueue(buffer), input, output) const remote = startRemote( ctx, log, connection, { - read() { - read = 0; - }, write({ type, data }) { server.onClientWrite(type, data) }, 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/logger.ts b/packages/libs/src/logger.ts index fdfe4cba..a0875d1a 100644 --- a/packages/libs/src/logger.ts +++ b/packages/libs/src/logger.ts @@ -1,5 +1,5 @@ import { stringify } from "./json.js"; -import type { Writer } from './io.js'; +import type { WritableStreamOfBytes, BytesStreamWriter, Writer } from './io.js'; export interface Logger { debug(text: string): void; diff --git a/packages/libs/src/sync/io.ts b/packages/libs/src/sync/io.ts index dee26b82..0073c47b 100644 --- a/packages/libs/src/sync/io.ts +++ b/packages/libs/src/sync/io.ts @@ -1,4 +1,6 @@ -import type { Streams, Writer } from "../io.js"; +import { noop } from "../function.js"; +import type { Streams, ReadableStreamOfBytes, BytesStreamWriter, Writer } from "../io.js"; +import { makeErrorWriter } from "../logger.js"; import type { SharedQueue } from "./shared-queue.js"; @@ -7,24 +9,21 @@ export enum StreamType { Err = 2, } +export const STREAM_TYPES = Object.values(StreamType) as StreamType[]; + export function createSharedStreamsClient( inputQueue: SharedQueue, - beforeRead: () => void, write: (stream: StreamType, data: Uint8Array) => void ): 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) { @@ -41,20 +40,27 @@ export function createSharedStreamsClient( export function createSharedStreamsServer( inputQueue: SharedQueue, - streams: Streams + input: ReadableStreamOfBytes, + output: Writer ) { - const writers: Record = { - [StreamType.Out]: streams.out, - [StreamType.Err]: streams.err, - }; + const w = new WritableStream({ + write(bytes) { + inputQueue.pushBytes(bytes); + inputQueue.commit(); + }, + }); + const c = new AbortController(); + input.pipeTo(w, { signal: c.signal }).catch(noop); + const writers = { + [StreamType.Out]: output, + [StreamType.Err]: makeErrorWriter(output), + } as const; 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(); + [Symbol.dispose]() { + c.abort(); }, }; } From cb1c9a7d1e8a480c7e9dfee3cf509b2c7560f0f1 Mon Sep 17 00:00:00 2001 From: Roman Krasilnikov Date: Tue, 18 Nov 2025 04:55:11 +0300 Subject: [PATCH 2/6] Refactor actors IO logic --- packages/libs/src/compiler/actor.ts | 107 +++++++++-------- packages/libs/src/compiler/compiler.ts | 9 +- packages/libs/src/logger.ts | 2 +- packages/libs/src/sync/io.ts | 47 ++------ packages/libs/src/testing/actor.ts | 157 +++++++++++-------------- packages/libs/src/testing/testing.ts | 6 +- 6 files changed, 143 insertions(+), 185 deletions(-) diff --git a/packages/libs/src/compiler/actor.ts b/packages/libs/src/compiler/actor.ts index 226ead9c..7c19f670 100644 --- a/packages/libs/src/compiler/actor.ts +++ b/packages/libs/src/compiler/actor.ts @@ -8,7 +8,11 @@ import { type IncomingMessage, type OutgoingMessage, } from "../actor/index.js"; -import { SharedQueue, StreamType, createSharedStreamsClient, createSharedStreamsServer } from '../sync/index.js'; +import { + SharedQueue, + createSharedStreamsClient, + pipeToQueue, +} from "../sync/index.js"; import { CanceledError, createContext, @@ -16,9 +20,9 @@ import { withCancel, type Context, } from "../context.js"; -import type { ReadableStreamOfBytes, Streams, WebStreams, WritableStreamOfBytes, Writer } from "../io.js"; +import type { ReadableStreamOfBytes, Writer } from "../io.js"; import { stringifyError } from "../error.js"; -import { BACKSPACE, createLogger } from "../logger.js"; +import { createLogger } from "../logger.js"; import type { Compiler, CompilerFactory, File, Program } from "./compiler.js"; @@ -36,7 +40,7 @@ interface Handlers { type Incoming = IncomingMessage; -interface WriteEventMessage extends EventMessage<"write", { type: StreamType, data: Uint8Array }> {} +interface WriteEventMessage extends EventMessage<"write", Uint8Array> {} type CompilerActorEvent = WriteEventMessage; @@ -45,15 +49,17 @@ 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, @@ -61,22 +67,20 @@ class CompilerActor extends Actor implements Disposable { ) { const handlers: Handlers = { initialize: async (buffer: SharedArrayBuffer) => { - const sharedQueue = new SharedQueue(buffer) - const streams = createSharedStreamsClient( - sharedQueue, - (type, data) => connection.send({ - type: MessageType.Event, - event: "write", - payload: { - type, - data - } - }) - ) + const sharedQueue = new SharedQueue(buffer); + const streams = createSharedStreamsClient(sharedQueue, { + write(data) { + connection.send({ + type: MessageType.Event, + event: "write", + payload: data, + }); + }, + }); this.compiler = await compilerFactory(this.compilerCtx.ref, streams); }, destroy: () => { - this.compilerCtx.cancel() + this.compilerCtx.cancel(); }, compile: async (files) => { if (this.compiler === null) { @@ -85,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) { @@ -110,12 +114,12 @@ class CompilerActor extends Actor implements Disposable { super(connection, handlers, stringifyError); } - [Symbol.dispose] (): void { + [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](); } } @@ -129,8 +133,8 @@ export function startCompilerActor( connection.start(ctx); const actor = new CompilerActor(connection, compilerFactory); ctx.onCancel(() => { - actor[Symbol.dispose]() - }) + actor[Symbol.dispose](); + }); actor.start(ctx); } @@ -138,50 +142,53 @@ interface WorkerConstructor { new (): Worker; } -export interface CompilerFactoryOptions { - input: ReadableStreamOfBytes - output: Writer +export interface RemoteCompilerFactoryOptions { + input: ReadableStreamOfBytes; + output: Writer; } export function makeRemoteCompilerFactory(Worker: WorkerConstructor) { - return async (ctx: Context, { input, output }: CompilerFactoryOptions): Promise> => { + return async ( + ctx: Context, + { input, output }: RemoteCompilerFactoryOptions + ): Promise> => { const worker = new Worker(); ctx.onCancel(() => { - server[Symbol.dispose]() - worker.terminate() - }) + worker.terminate(); + }); const connection = new WorkerConnection(worker); connection.start(ctx); const log = createLogger(output); - const Buffer = window.SharedArrayBuffer - ? SharedArrayBuffer - : ArrayBuffer; - const buffer = new Buffer(1024 * 1024) - const server = createSharedStreamsServer(new SharedQueue(buffer), input, output) + const Buffer = window.SharedArrayBuffer ? SharedArrayBuffer : ArrayBuffer; + const buffer = new Buffer(1024 * 1024); + const sharedWriter = pipeToQueue(input, new SharedQueue(buffer)); + ctx.onCancel(() => { + sharedWriter[Symbol.dispose](); + }) const remote = startRemote( ctx, log, connection, { - write({ type, data }) { - server.onClientWrite(type, data) + write(data) { + output.write(data); }, 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()) + using _ = ctx.onCancel(() => remote.stopCompile()); await remote.compile(files); return { async run(ctx) { - using _ = ctx.onCancel(() => remote.stopRun()) + 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..dc55556e 100644 --- a/packages/libs/src/compiler/compiler.ts +++ b/packages/libs/src/compiler/compiler.ts @@ -1,5 +1,5 @@ -import type { Context } from 'libs/context'; -import type { Streams } from 'libs/io'; +import type { Context } from "libs/context"; +import type { Streams } from "libs/io"; export interface Program { run: (ctx: Context) => Promise; @@ -14,4 +14,7 @@ export interface Compiler

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

; } -export type CompilerFactory

= (ctx: Context, streams: Streams) => Promise>; +export type CompilerFactory

= ( + ctx: Context, + streams: Streams, +) => Promise>; diff --git a/packages/libs/src/logger.ts b/packages/libs/src/logger.ts index a0875d1a..fdfe4cba 100644 --- a/packages/libs/src/logger.ts +++ b/packages/libs/src/logger.ts @@ -1,5 +1,5 @@ import { stringify } from "./json.js"; -import type { WritableStreamOfBytes, BytesStreamWriter, Writer } from './io.js'; +import type { Writer } from './io.js'; export interface Logger { debug(text: string): void; diff --git a/packages/libs/src/sync/io.ts b/packages/libs/src/sync/io.ts index 0073c47b..1c8d0b3c 100644 --- a/packages/libs/src/sync/io.ts +++ b/packages/libs/src/sync/io.ts @@ -1,19 +1,11 @@ -import { noop } from "../function.js"; -import type { Streams, ReadableStreamOfBytes, BytesStreamWriter, Writer } from "../io.js"; +import type { Streams, ReadableStreamOfBytes, Writer } from "../io.js"; import { makeErrorWriter } from "../logger.js"; import type { SharedQueue } from "./shared-queue.js"; -export enum StreamType { - Out = 1, - Err = 2, -} - -export const STREAM_TYPES = Object.values(StreamType) as StreamType[]; - export function createSharedStreamsClient( inputQueue: SharedQueue, - write: (stream: StreamType, data: Uint8Array) => void + writer: Writer ): Streams { return { in: { @@ -25,42 +17,23 @@ export function createSharedStreamsClient( return bytes; }, }, - out: { - write(data) { - write(StreamType.Out, data); - }, - }, - err: { - write(data) { - write(StreamType.Err, data); - }, - }, + out: writer, + err: makeErrorWriter(writer), }; } -export function createSharedStreamsServer( - inputQueue: SharedQueue, - input: ReadableStreamOfBytes, - output: Writer -) { +export function pipeToQueue(input: ReadableStreamOfBytes, queue: SharedQueue) { const w = new WritableStream({ write(bytes) { - inputQueue.pushBytes(bytes); - inputQueue.commit(); + queue.pushBytes(bytes); + queue.commit(); }, }); - const c = new AbortController(); - input.pipeTo(w, { signal: c.signal }).catch(noop); - const writers = { - [StreamType.Out]: output, - [StreamType.Err]: makeErrorWriter(output), - } as const; + const controller = new AbortController(); + input.pipeTo(w); return { - onClientWrite(type: StreamType, data: Uint8Array) { - writers[type].write(data); - }, [Symbol.dispose]() { - c.abort(); + controller.abort(); }, }; } diff --git a/packages/libs/src/testing/actor.ts b/packages/libs/src/testing/actor.ts index b509d198..d78e854e 100644 --- a/packages/libs/src/testing/actor.ts +++ b/packages/libs/src/testing/actor.ts @@ -8,7 +8,7 @@ import { import { BACKSPACE, createLogger } from "../logger.js"; import { stringifyError } from "../error.js"; import { compileJsModule } from "../js.js"; -import type { Streams } from "../io.js"; +import type { ReadableStreamOfBytes, Streams, Writer } from "../io.js"; import { Actor, MessageType, @@ -19,12 +19,7 @@ import { type IncomingMessage, type OutgoingMessage, } from "../actor/index.js"; -import { - createSharedStreamsClient, - createSharedStreamsServer, - SharedQueue, - StreamType -} from '../sync/index.js'; +import { createSharedStreamsClient, pipeToQueue, SharedQueue } from "../sync/index.js"; import type { File } from "../compiler/index.js"; import type { TestProgram, TestCompiler } from "./testing.js"; @@ -48,11 +43,9 @@ interface Handlers { 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> @@ -75,45 +68,43 @@ 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 - } - }) - ) + const sharedQueue = new SharedQueue(buffer); + const client = createSharedStreamsClient(sharedQueue, { + write(data) { + connection.send({ + type: MessageType.Event, + event: "write", + payload: data, + }); + }, + }); this.compiler = await superFactory( this.compilerCtx.ref, client, @@ -121,19 +112,16 @@ class TestCompilerActor extends Actor, string> implement ); }, 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,22 +136,22 @@ 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); } - [Symbol.dispose] (): void { + [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](); } } @@ -177,8 +165,8 @@ export function startTestCompilerActor( connection.start(ctx); const actor = new TestCompilerActor(connection, superFactory); ctx.onCancel(() => { - actor[Symbol.dispose]() - }) + actor[Symbol.dispose](); + }); actor.start(ctx); } @@ -186,75 +174,62 @@ interface WorkerConstructor { new (): Worker; } +export interface RemoteTestCompilerFactoryOptions { + input: ReadableStreamOfBytes; + output: Writer; +} + export function makeRemoteTestCompilerFactory( Worker: WorkerConstructor, universalFactory: UniversalFactory ) { - return async (ctx: Context, streams: Streams): Promise> => { + return async ( + ctx: Context, + { input, output }: RemoteTestCompilerFactoryOptions + ): 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 = pipeToQueue(input, new SharedQueue(buffer)); + ctx.onCancel(() => { + sharedWriter[Symbol.dispose](); + }); const remote = startRemote, string, TestingActorEvent>( ctx, log, connection, { - read() { - read = 0; - }, - write({ type, data }) { - server.onClientWrite(type, data) + write(data) { + output.write(data) }, 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()) + using _ = ctx.onCancel(() => remote.stopCompile()); await remote.compile(files); return { async run(ctx, input) { - using _ = ctx.onCancel(() => remote.stopTest()) + 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..f444396c 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 { isDeepEqual } from "../deep-equal.js"; export interface TestCase { input: I; @@ -21,9 +21,9 @@ 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>; export async function runTests( ctx: Context, From 37d750a226ea4571ff369f11fefe6324a0d3e305 Mon Sep 17 00:00:00 2001 From: Roman Krasilnikov Date: Tue, 18 Nov 2025 05:29:49 +0300 Subject: [PATCH 3/6] WIP: Refactor compiler lib --- packages/libs/src/compiler/actor.ts | 53 ++++++++++++-------------- packages/libs/src/compiler/compiler.ts | 5 +-- packages/libs/src/sync/io.ts | 31 +++++++-------- packages/libs/src/testing/actor.ts | 29 ++++++-------- packages/libs/src/testing/testing.ts | 8 ++-- 5 files changed, 56 insertions(+), 70 deletions(-) diff --git a/packages/libs/src/compiler/actor.ts b/packages/libs/src/compiler/actor.ts index 7c19f670..90a26b75 100644 --- a/packages/libs/src/compiler/actor.ts +++ b/packages/libs/src/compiler/actor.ts @@ -7,24 +7,25 @@ import { type EventMessage, type IncomingMessage, type OutgoingMessage, -} from "../actor/index.js"; -import { - SharedQueue, - createSharedStreamsClient, - pipeToQueue, -} from "../sync/index.js"; +} from "libs/actor"; +import { SharedQueue, readFromQueue, writeToQueue } from "libs/sync"; import { CanceledError, createContext, createRecoverableContext, withCancel, type Context, -} from "../context.js"; -import type { ReadableStreamOfBytes, Writer } from "../io.js"; -import { stringifyError } from "../error.js"; -import { createLogger } from "../logger.js"; +} from "libs/context"; +import { stringifyError } from "libs/error"; +import { createLogger } from "libs/logger"; -import type { Compiler, CompilerFactory, File, Program } from "./compiler.js"; +import type { + Compiler, + CompilerFactory, + CompilerFactoryOptions, + File, + Program, +} from "./compiler.js"; interface Handlers { [key: string]: any; @@ -67,17 +68,18 @@ class CompilerActor extends Actor implements Disposable { ) { const handlers: Handlers = { initialize: async (buffer: SharedArrayBuffer) => { - const sharedQueue = new SharedQueue(buffer); - const streams = createSharedStreamsClient(sharedQueue, { - write(data) { - connection.send({ - type: MessageType.Event, - event: "write", - payload: data, - }); + this.compiler = await compilerFactory(this.compilerCtx.ref, { + input: readFromQueue(new SharedQueue(buffer)), + output: { + write(data) { + connection.send({ + type: MessageType.Event, + event: "write", + payload: data, + }); + }, }, }); - this.compiler = await compilerFactory(this.compilerCtx.ref, streams); }, destroy: () => { this.compilerCtx.cancel(); @@ -142,15 +144,10 @@ interface WorkerConstructor { new (): Worker; } -export interface RemoteCompilerFactoryOptions { - input: ReadableStreamOfBytes; - output: Writer; -} - export function makeRemoteCompilerFactory(Worker: WorkerConstructor) { return async ( ctx: Context, - { input, output }: RemoteCompilerFactoryOptions + { input, output }: CompilerFactoryOptions ): Promise> => { const worker = new Worker(); ctx.onCancel(() => { @@ -161,10 +158,10 @@ export function makeRemoteCompilerFactory(Worker: WorkerConstructor) { const log = createLogger(output); const Buffer = window.SharedArrayBuffer ? SharedArrayBuffer : ArrayBuffer; const buffer = new Buffer(1024 * 1024); - const sharedWriter = pipeToQueue(input, new SharedQueue(buffer)); + const sharedWriter = writeToQueue(input, new SharedQueue(buffer)); ctx.onCancel(() => { sharedWriter[Symbol.dispose](); - }) + }); const remote = startRemote( ctx, log, diff --git a/packages/libs/src/compiler/compiler.ts b/packages/libs/src/compiler/compiler.ts index dc55556e..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"; export interface Program { run: (ctx: Context) => Promise; @@ -14,7 +13,7 @@ export interface Compiler

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

; } -export type CompilerFactory

= ( +export type CompilerFactory = ( ctx: Context, - streams: Streams, + options: O ) => Promise>; diff --git a/packages/libs/src/sync/io.ts b/packages/libs/src/sync/io.ts index 1c8d0b3c..af1f06cd 100644 --- a/packages/libs/src/sync/io.ts +++ b/packages/libs/src/sync/io.ts @@ -1,28 +1,23 @@ -import type { Streams, ReadableStreamOfBytes, Writer } from "../io.js"; -import { makeErrorWriter } from "../logger.js"; +import type { ReadableStreamOfBytes } from "libs/io"; import type { SharedQueue } from "./shared-queue.js"; -export function createSharedStreamsClient( +export function readFromQueue( inputQueue: SharedQueue, - writer: Writer -): Streams { - return { - in: { - read() { - const r = inputQueue.blockingRead(); - const bytes = r.next().value.bytes; - // NOTE: The generator must be exhausted - r.next(); - return bytes; - }, +) { + 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) }, - out: writer, - err: makeErrorWriter(writer), - }; + }) + return input } -export function pipeToQueue(input: ReadableStreamOfBytes, queue: SharedQueue) { +export function writeToQueue(input: ReadableStreamOfBytes, queue: SharedQueue) { const w = new WritableStream({ write(bytes) { queue.pushBytes(bytes); diff --git a/packages/libs/src/testing/actor.ts b/packages/libs/src/testing/actor.ts index d78e854e..6697eca9 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 { ReadableStreamOfBytes, Streams, Writer } 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,9 +18,9 @@ import { type EventMessage, type IncomingMessage, type OutgoingMessage, -} from "../actor/index.js"; -import { createSharedStreamsClient, pipeToQueue, SharedQueue } from "../sync/index.js"; -import type { File } from "../compiler/index.js"; +} from "libs/actor"; +import { readFromQueue, writeToQueue, SharedQueue } from "libs/sync"; +import type { CompilerFactoryOptions, File } from "libs/compiler"; import type { TestProgram, TestCompiler } from "./testing.js"; @@ -96,7 +96,7 @@ class TestCompilerActor universalFactoryFunction ); const sharedQueue = new SharedQueue(buffer); - const client = createSharedStreamsClient(sharedQueue, { + const client = readFromQueue(sharedQueue, { write(data) { connection.send({ type: MessageType.Event, @@ -174,18 +174,13 @@ interface WorkerConstructor { new (): Worker; } -export interface RemoteTestCompilerFactoryOptions { - input: ReadableStreamOfBytes; - output: Writer; -} - export function makeRemoteTestCompilerFactory( Worker: WorkerConstructor, universalFactory: UniversalFactory ) { return async ( ctx: Context, - { input, output }: RemoteTestCompilerFactoryOptions + { input, output }: CompilerFactoryOptions ): Promise> => { const worker = new Worker(); ctx.onCancel(() => { @@ -198,7 +193,7 @@ export function makeRemoteTestCompilerFactory( const log = createLogger(output); const Buffer = window.SharedArrayBuffer ? SharedArrayBuffer : ArrayBuffer; const buffer = new Buffer(1024 * 1024 * 10); - const sharedWriter = pipeToQueue(input, new SharedQueue(buffer)); + const sharedWriter = writeToQueue(input, new SharedQueue(buffer)); ctx.onCancel(() => { sharedWriter[Symbol.dispose](); }); @@ -208,7 +203,7 @@ export function makeRemoteTestCompilerFactory( connection, { write(data) { - output.write(data) + output.write(data); }, error(err) { log.error(err instanceof CanceledError ? err.message : err); diff --git a/packages/libs/src/testing/testing.ts b/packages/libs/src/testing/testing.ts index f444396c..584c7ec6 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; From 9f449413ce7ccc43a8a8d6eec979084fdd85d84d Mon Sep 17 00:00:00 2001 From: Roman Krasilnikov Date: Tue, 18 Nov 2025 12:59:36 +0300 Subject: [PATCH 4/6] Refactor compiler lib --- packages/libs/src/compiler/actor.ts | 55 ++++++++++++----------- packages/libs/src/sync/io.ts | 36 +++++++++++---- packages/libs/src/testing/actor.ts | 65 ++++++++++++++-------------- packages/libs/src/testing/testing.ts | 6 ++- 4 files changed, 94 insertions(+), 68 deletions(-) diff --git a/packages/libs/src/compiler/actor.ts b/packages/libs/src/compiler/actor.ts index 90a26b75..d9143d04 100644 --- a/packages/libs/src/compiler/actor.ts +++ b/packages/libs/src/compiler/actor.ts @@ -8,7 +8,7 @@ import { type IncomingMessage, type OutgoingMessage, } from "libs/actor"; -import { SharedQueue, readFromQueue, writeToQueue } from "libs/sync"; +import { SharedQueue, createStreamsClient, writeToQueue } from "libs/sync"; import { CanceledError, createContext, @@ -18,30 +18,30 @@ import { } 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, - CompilerFactoryOptions, 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 WriteEventMessage extends EventMessage<"write", Uint8Array> {} +interface WriteEventMessage extends EventMessage<"write", Uint8Array> { } type CompilerActorEvent = WriteEventMessage; @@ -64,22 +64,20 @@ class CompilerActor extends Actor implements Disposable { constructor( connection: Connection, - compilerFactory: CompilerFactory + compilerFactory: CompilerFactory ) { const handlers: Handlers = { initialize: async (buffer: SharedArrayBuffer) => { - this.compiler = await compilerFactory(this.compilerCtx.ref, { - input: readFromQueue(new SharedQueue(buffer)), - output: { - write(data) { + 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(); @@ -116,7 +114,7 @@ class CompilerActor extends Actor implements Disposable { super(connection, handlers, stringifyError); } - [Symbol.dispose](): void { + [Symbol.dispose] (): void { this.compiler = null; this.compilerCtx[Symbol.dispose](); this.program = null; @@ -125,9 +123,9 @@ class CompilerActor extends Actor implements Disposable { } } -export function startCompilerActor( +export function startCompilerActor ( ctx: Context, - compilerFactory: CompilerFactory + compilerFactory: CompilerFactory ) { const connection = new WorkerConnection( self as unknown as Worker @@ -141,13 +139,18 @@ export function startCompilerActor( } interface WorkerConstructor { - new (): Worker; + new(): Worker; +} + +export interface RemoteCompilerFactoryOptions { + input: ReadableStreamOfBytes + output: Writer } -export function makeRemoteCompilerFactory(Worker: WorkerConstructor) { +export function makeRemoteCompilerFactory (Worker: WorkerConstructor) { return async ( ctx: Context, - { input, output }: CompilerFactoryOptions + { input, output }: RemoteCompilerFactoryOptions ): Promise> => { const worker = new Worker(); ctx.onCancel(() => { @@ -167,10 +170,10 @@ export function makeRemoteCompilerFactory(Worker: WorkerConstructor) { log, connection, { - write(data) { + write (data) { output.write(data); }, - error(err) { + error (err) { log.error(err instanceof CanceledError ? err.message : err); }, } @@ -178,11 +181,11 @@ export function makeRemoteCompilerFactory(Worker: WorkerConstructor) { using _ = ctx.onCancel(() => remote.destroy()); await remote.initialize(buffer); return { - async compile(ctx, files) { + async compile (ctx, files) { using _ = ctx.onCancel(() => remote.stopCompile()); await remote.compile(files); return { - async run(ctx) { + async run (ctx) { using _ = ctx.onCancel(() => remote.stopRun()); await remote.run(); }, diff --git a/packages/libs/src/sync/io.ts b/packages/libs/src/sync/io.ts index af1f06cd..c655b0f7 100644 --- a/packages/libs/src/sync/io.ts +++ b/packages/libs/src/sync/io.ts @@ -1,20 +1,19 @@ -import type { ReadableStreamOfBytes } from "libs/io"; +import type { ReadableStreamOfBytes, Streams, Writer } from "libs/io"; import type { SharedQueue } from "./shared-queue.js"; +import { makeErrorWriter } from "../logger.js"; -export function readFromQueue( - inputQueue: SharedQueue, -) { +export function readFromQueue(inputQueue: SharedQueue) { const input = new ReadableStream({ pull(controller) { - const r = inputQueue.blockingRead() + const r = inputQueue.blockingRead(); const bytes = r.next().value.bytes; // NOTE: The generator must be exhausted - r.next() - controller.enqueue(bytes) + r.next(); + controller.enqueue(bytes); }, - }) - return input + }); + return input; } export function writeToQueue(input: ReadableStreamOfBytes, queue: SharedQueue) { @@ -32,3 +31,22 @@ export function writeToQueue(input: ReadableStreamOfBytes, queue: SharedQueue) { }, }; } + +export function createStreamsClient( + inputQueue: SharedQueue, + writer: Writer +): Streams { + return { + in: { + read() { + const r = inputQueue.blockingRead(); + const bytes = r.next().value.bytes; + // NOTE: The generator must be exhausted + r.next(); + return bytes; + }, + }, + out: writer, + err: makeErrorWriter(writer), + }; +} diff --git a/packages/libs/src/testing/actor.ts b/packages/libs/src/testing/actor.ts index 6697eca9..445ed381 100644 --- a/packages/libs/src/testing/actor.ts +++ b/packages/libs/src/testing/actor.ts @@ -19,8 +19,9 @@ import { type IncomingMessage, type OutgoingMessage, } from "libs/actor"; -import { readFromQueue, writeToQueue, SharedQueue } from "libs/sync"; -import type { CompilerFactoryOptions, File } from "libs/compiler"; +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"; @@ -31,19 +32,19 @@ 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 WriteEventMessage extends EventMessage<"write", Uint8Array> {} +interface WriteEventMessage extends EventMessage<"write", Uint8Array> { } type TestingActorEvent = WriteEventMessage; @@ -51,7 +52,7 @@ 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; @@ -70,8 +71,7 @@ export type TestCompilerSuperFactory = ( class TestCompilerActor extends Actor, string> - implements Disposable -{ + implements Disposable { protected compiler: TestCompiler | null = null; protected compilerCtx = createRecoverableContext(() => { this.compiler = null; @@ -95,19 +95,20 @@ class TestCompilerActor const universalFactory = await evalEntity>( universalFactoryFunction ); - const sharedQueue = new SharedQueue(buffer); - const client = readFromQueue(sharedQueue, { - write(data) { - connection.send({ - type: MessageType.Event, - event: "write", - payload: 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 ); }, @@ -146,7 +147,7 @@ class TestCompilerActor super(connection, handlers, stringifyError); } - [Symbol.dispose](): void { + [Symbol.dispose] (): void { this.compiler = null; this.compilerCtx[Symbol.dispose](); this.program = null; @@ -155,7 +156,7 @@ class TestCompilerActor } } -export function startTestCompilerActor( +export function startTestCompilerActor ( ctx: Context, superFactory: TestCompilerSuperFactory ) { @@ -171,16 +172,16 @@ export function startTestCompilerActor( } interface WorkerConstructor { - new (): Worker; + new(): Worker; } -export function makeRemoteTestCompilerFactory( +export function makeRemoteTestCompilerFactory ( Worker: WorkerConstructor, universalFactory: UniversalFactory ) { return async ( ctx: Context, - { input, output }: CompilerFactoryOptions + { input, output }: RemoteCompilerFactoryOptions ): Promise> => { const worker = new Worker(); ctx.onCancel(() => { @@ -202,10 +203,10 @@ export function makeRemoteTestCompilerFactory( log, connection, { - write(data) { + write (data) { output.write(data); }, - error(err) { + error (err) { log.error(err instanceof CanceledError ? err.message : err); }, } @@ -216,11 +217,11 @@ export function makeRemoteTestCompilerFactory( buffer, }); return { - async compile(ctx, files) { + async compile (ctx, files) { using _ = ctx.onCancel(() => remote.stopCompile()); await remote.compile(files); return { - async run(ctx, input) { + 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 584c7ec6..3cae8325 100644 --- a/packages/libs/src/testing/testing.ts +++ b/packages/libs/src/testing/testing.ts @@ -2,6 +2,7 @@ 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"; +import type { Streams } from "libs/io"; export interface TestCase { input: I; @@ -23,7 +24,10 @@ export interface TestProgram { export type TestCompiler = Compiler>; -export type TestCompilerFactory = CompilerFactory>; +export type TestCompilerFactory = CompilerFactory< + Streams, + TestProgram +>; export async function runTests( ctx: Context, From 2dd3ed83b7943b3496137c19aa6302508e621827 Mon Sep 17 00:00:00 2001 From: Roman Krasilnikov Date: Tue, 18 Nov 2025 17:30:34 +0300 Subject: [PATCH 5/6] Refactor terminal IO --- .../ppp/src/lib/components/editor/terminal.ts | 161 +++---- apps/ppp/src/lib/problem/editor.svelte | 10 +- apps/ppp/src/lib/problem/model.ts | 3 +- .../lib/runtime/dotnet/compiler-factory.ts | 450 +++++++++--------- .../src/lib/runtime/gleam/compiler-factory.ts | 67 ++- .../src/lib/runtime/go/compiler-factory.ts | 43 +- .../src/lib/runtime/java/compiler-factory.ts | 5 +- .../src/lib/runtime/js/compiler-factory.ts | 3 +- .../src/lib/runtime/php/compiler-factory.ts | 3 +- .../lib/runtime/python/compiler-factory.ts | 44 +- .../src/lib/runtime/ruby/compiler-factory.ts | 33 +- .../src/lib/runtime/rust/compiler-factory.ts | 70 ++- .../src/lib/runtime/ts/compiler-factory.ts | 32 +- .../lib/runtime/ts/test-compiler-factory.ts | 2 +- .../ppp/src/routes/(app)/editor/+page@.svelte | 118 ++--- apps/ppp/src/routes/(app)/editor/_runtimes.ts | 73 --- apps/ppp/src/routes/(app)/editor/runtimes.ts | 73 +++ .../payment-system/runtimes/csharp/factory.ts | 42 +- .../payment-system/runtimes/gleam/factory.ts | 53 +-- .../payment-system/runtimes/go/factory.ts | 42 +- .../payment-system/runtimes/java/factory.ts | 62 ++- .../runtimes/javascript/factory.ts | 29 +- .../payment-system/runtimes/php/factory.ts | 42 +- .../payment-system/runtimes/python/factory.ts | 30 +- .../payment-system/runtimes/ruby/factory.ts | 28 +- .../payment-system/runtimes/rust/factory.ts | 59 +-- .../runtimes/typescript/factory.ts | 26 +- packages/libs/src/sync/io.ts | 7 +- packages/libs/src/testing/testing.ts | 5 +- 29 files changed, 795 insertions(+), 820 deletions(-) delete mode 100644 apps/ppp/src/routes/(app)/editor/_runtimes.ts create mode 100644 apps/ppp/src/routes/(app)/editor/runtimes.ts diff --git a/apps/ppp/src/lib/components/editor/terminal.ts b/apps/ppp/src/lib/components/editor/terminal.ts index 1c61b671..856a4f94 100644 --- a/apps/ppp/src/lib/components/editor/terminal.ts +++ b/apps/ppp/src/lib/components/editor/terminal.ts @@ -1,93 +1,86 @@ -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 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 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), - BytesReaderStream: { - 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 function createReadableStream(terminal: Terminal) { + let disposable: IDisposable; + return new ReadableStream({ + start(controller) { + disposable = terminal.onData((data) => { + controller.enqueue(data); + }); + }, + 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..71932c53 100644 --- a/apps/ppp/src/lib/problem/editor.svelte +++ b/apps/ppp/src/lib/problem/editor.svelte @@ -32,7 +32,8 @@ EditorContext, setEditorContext, type ProcessStatus, - createStreams + createReadableStream, + createLineInputMode, } from '$lib/components/editor'; import { Panel, @@ -121,7 +122,8 @@ }); const { terminal, fitAddon } = createTerminal(); - const streams = createStreams(terminal); + const input = createReadableStream(terminal) + .pipeThrough(createLineInputMode(terminal)); const editorContext = new EditorContext(model, terminal, fitAddon); setEditorContext(editorContext); @@ -177,7 +179,7 @@ let executionTimeout = $state(executionTimeoutStorage.load()); debouncedSave(executionTimeoutStorage, () => executionTimeout, 100); - const terminalLogger = createLogger(streams.out); + const terminalLogger = createLogger(terminal); let testCompilerFactory = $derived(runtime.factory); let status = $state('stopped'); let lastTestId = $state(-1); @@ -206,7 +208,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, [ { diff --git a/apps/ppp/src/lib/problem/model.ts b/apps/ppp/src/lib/problem/model.ts index ee9591a8..52eba608 100644 --- a/apps/ppp/src/lib/problem/model.ts +++ b/apps/ppp/src/lib/problem/model.ts @@ -2,6 +2,7 @@ import type { TestCompilerFactory } from 'libs/testing'; import { m } from '$lib/paraglide/messages'; import type { Locale } from '$lib/paraglide/runtime'; +import type { RemoteCompilerFactoryOptions } from 'libs/compiler/actor'; export interface Problem { titles: Record @@ -19,7 +20,7 @@ export const PROBLEM_CATEGORY_TO_LABEL: Record string> = export interface Runtime { code: string; - factory: TestCompilerFactory; + factory: TestCompilerFactory; } export const EDITOR_MIN_WIDTH = 5; diff --git a/apps/ppp/src/lib/runtime/dotnet/compiler-factory.ts b/apps/ppp/src/lib/runtime/dotnet/compiler-factory.ts index 63f83839..fb1c9c32 100644 --- a/apps/ppp/src/lib/runtime/dotnet/compiler-factory.ts +++ b/apps/ppp/src/lib/runtime/dotnet/compiler-factory.ts @@ -1,242 +1,238 @@ +import type { CompilerFactory, Program } from 'libs/compiler'; +import { inContext } from 'libs/context'; +import { createLogger, redirect } from 'libs/logger'; +import { patch } from 'libs/patcher'; +import type { Streams } from 'libs/io'; import { - type CompilerModuleExports, - type CompilerModuleImports, - type DotnetModule, - DotnetProgram, - DotnetCompilerFactory, - DotnetRuntimeFactory, -} from "dotnet-runtime"; -import type { CompilerFactory, Program } from "libs/compiler"; -import { inContext } from "libs/context"; -import { createLogger, redirect } from "libs/logger"; -import { patch } from "libs/patcher"; + type CompilerModuleExports, + type CompilerModuleImports, + type DotnetModule, + DotnetProgram, + DotnetCompilerFactory, + DotnetRuntimeFactory +} from 'dotnet-runtime'; import { base } from '$app/paths'; const dotnetUrl = new URL( - `${base}/assets/dotnet/compiler/dotnet.js`, - globalThis.location.origin + `${base}/assets/dotnet/compiler/dotnet.js`, + globalThis.location.origin ).toString(); const precompiledLibsIndexUrl = new URL( - `${base}/assets/dotnet/lib`, - globalThis.location.origin + `${base}/assets/dotnet/lib`, + globalThis.location.origin ).toString(); export const LIBS = [ - "Humanizer.dll", - "Microsoft.Bcl.AsyncInterfaces.dll", - "Microsoft.CSharp.dll", - // "Microsoft.CodeAnalysis.CSharp.Workspaces.dll", - // "Microsoft.CodeAnalysis.CSharp.dll", - // "Microsoft.CodeAnalysis.VisualBasic.Workspaces.dll", - // "Microsoft.CodeAnalysis.VisualBasic.dll", - // "Microsoft.CodeAnalysis.Workspaces.dll", - // "Microsoft.CodeAnalysis.dll", - // "Microsoft.JSInterop.WebAssembly.dll", - // "Microsoft.JSInterop.dll", - // "Microsoft.VisualBasic.Core.dll", - // "Microsoft.VisualBasic.dll", - // "Microsoft.Win32.Primitives.dll", - // "Microsoft.Win32.Registry.dll", - "System.AppContext.dll", - "System.Buffers.dll", - "System.Collections.Concurrent.dll", - "System.Collections.Immutable.dll", - "System.Collections.NonGeneric.dll", - "System.Collections.Specialized.dll", - "System.Collections.dll", - // "System.ComponentModel.Annotations.dll", - // "System.ComponentModel.DataAnnotations.dll", - // "System.ComponentModel.EventBasedAsync.dll", - // "System.ComponentModel.Primitives.dll", - // "System.ComponentModel.TypeConverter.dll", - // "System.ComponentModel.dll", - // "System.Composition.AttributedModel.dll", - // "System.Composition.Convention.dll", - // "System.Composition.Hosting.dll", - // "System.Composition.Runtime.dll", - // "System.Composition.TypedParts.dll", - // "System.Configuration.dll", - "System.Console.dll", - "System.Core.dll", - // "System.Data.Common.dll", - // "System.Data.DataSetExtensions.dll", - // "System.Data.dll", - // "System.Diagnostics.Contracts.dll", - "System.Diagnostics.Debug.dll", - // "System.Diagnostics.DiagnosticSource.dll", - // "System.Diagnostics.FileVersionInfo.dll", - // "System.Diagnostics.Process.dll", - // "System.Diagnostics.StackTrace.dll", - // "System.Diagnostics.TextWriterTraceListener.dll", - // "System.Diagnostics.Tools.dll", - // "System.Diagnostics.TraceSource.dll", - // "System.Diagnostics.Tracing.dll", - // "System.Drawing.Primitives.dll", - // "System.Drawing.dll", - // "System.Dynamic.Runtime.dll", - // "System.Formats.Asn1.dll", - // "System.Formats.Tar.dll", - "System.Globalization.Calendars.dll", - "System.Globalization.Extensions.dll", - "System.Globalization.dll", - // "System.IO.Compression.Brotli.dll", - // "System.IO.Compression.FileSystem.dll", - // "System.IO.Compression.ZipFile.dll", - // "System.IO.Compression.dll", - // "System.IO.FileSystem.AccessControl.dll", - // "System.IO.FileSystem.DriveInfo.dll", - // "System.IO.FileSystem.Primitives.dll", - // "System.IO.FileSystem.Watcher.dll", - "System.IO.FileSystem.dll", - // "System.IO.IsolatedStorage.dll", - // "System.IO.MemoryMappedFiles.dll", - // "System.IO.Pipelines.dll", - // "System.IO.Pipes.AccessControl.dll", - // "System.IO.Pipes.dll", - "System.IO.UnmanagedMemoryStream.dll", - "System.IO.dll", - "System.Linq.Expressions.dll", - "System.Linq.Parallel.dll", - "System.Linq.Queryable.dll", - "System.Linq.dll", - "System.Memory.dll", - "System.Net.Http.Json.dll", - "System.Net.Http.dll", - "System.Net.HttpListener.dll", - // "System.Net.Mail.dll", - "System.Net.NameResolution.dll", - "System.Net.NetworkInformation.dll", - // "System.Net.Ping.dll", - "System.Net.Primitives.dll", - // "System.Net.Quic.dll", - "System.Net.Requests.dll", - // "System.Net.Security.dll", - // "System.Net.ServicePoint.dll", - "System.Net.Sockets.dll", - "System.Net.WebClient.dll", - "System.Net.WebHeaderCollection.dll", - // "System.Net.WebProxy.dll", - // "System.Net.WebSockets.Client.dll", - // "System.Net.WebSockets.dll", - "System.Net.dll", - "System.Numerics.Vectors.dll", - "System.Numerics.dll", - "System.ObjectModel.dll", - "System.Private.CoreLib.dll", - // "System.Private.DataContractSerialization.dll", - "System.Private.Uri.dll", - // "System.Private.Xml.Linq.dll", - // "System.Private.Xml.dll", - // "System.Reflection.DispatchProxy.dll", - // "System.Reflection.Emit.ILGeneration.dll", - // "System.Reflection.Emit.Lightweight.dll", - // "System.Reflection.Emit.dll", - // "System.Reflection.Extensions.dll", - // "System.Reflection.Metadata.dll", - // "System.Reflection.Primitives.dll", - // "System.Reflection.TypeExtensions.dll", - "System.Reflection.dll", - "System.Resources.Reader.dll", - "System.Resources.ResourceManager.dll", - "System.Resources.Writer.dll", - // "System.Runtime.CompilerServices.Unsafe.dll", - // "System.Runtime.CompilerServices.VisualC.dll", - "System.Runtime.Extensions.dll", - "System.Runtime.Handles.dll", - // "System.Runtime.InteropServices.JavaScript.dll", - "System.Runtime.InteropServices.RuntimeInformation.dll", - "System.Runtime.InteropServices.dll", - // "System.Runtime.Intrinsics.dll", - // "System.Runtime.Loader.dll", - // "System.Runtime.Numerics.dll", - // "System.Runtime.Serialization.Formatters.dll", - "System.Runtime.Serialization.Json.dll", - // "System.Runtime.Serialization.Primitives.dll", - // "System.Runtime.Serialization.Xml.dll", - // "System.Runtime.Serialization.dll", - "System.Runtime.dll", - // "System.Security.AccessControl.dll", - // "System.Security.Claims.dll", - // "System.Security.Cryptography.Algorithms.dll", - // "System.Security.Cryptography.Cng.dll", - // "System.Security.Cryptography.Csp.dll", - // "System.Security.Cryptography.Encoding.dll", - // "System.Security.Cryptography.OpenSsl.dll", - // "System.Security.Cryptography.Primitives.dll", - // "System.Security.Cryptography.X509Certificates.dll", - // "System.Security.Cryptography.dll", - // "System.Security.Principal.Windows.dll", - "System.Security.Principal.dll", - "System.Security.SecureString.dll", - "System.Security.dll", - // "System.ServiceModel.Web.dll", - // "System.ServiceProcess.dll", - // "System.Text.Encoding.CodePages.dll", - "System.Text.Encoding.Extensions.dll", - "System.Text.Encoding.dll", - "System.Text.Encodings.Web.dll", - "System.Text.Json.dll", - "System.Text.RegularExpressions.dll", - // "System.Threading.Channels.dll", - // "System.Threading.Overlapped.dll", - // "System.Threading.Tasks.Dataflow.dll", - "System.Threading.Tasks.Extensions.dll", - "System.Threading.Tasks.Parallel.dll", - "System.Threading.Tasks.dll", - "System.Threading.Thread.dll", - "System.Threading.ThreadPool.dll", - "System.Threading.Timer.dll", - "System.Threading.dll", - // "System.Transactions.Local.dll", - "System.Transactions.dll", - "System.ValueTuple.dll", - "System.Web.HttpUtility.dll", - // "System.Web.dll", - // "System.Windows.dll", - // "System.Xml.Linq.dll", - // "System.Xml.ReaderWriter.dll", - // "System.Xml.Serialization.dll", - // "System.Xml.XDocument.dll", - // "System.Xml.XPath.XDocument.dll", - // "System.Xml.XPath.dll", - // "System.Xml.XmlDocument.dll", - // "System.Xml.XmlSerializer.dll", - // "System.Xml.dll", - "System.dll", - "WebAssembly.dll", - // "WindowsBase.dll", - // "compiler.dll", - "mscorlib.dll", - "netstandard.dll", + 'Humanizer.dll', + 'Microsoft.Bcl.AsyncInterfaces.dll', + 'Microsoft.CSharp.dll', + // "Microsoft.CodeAnalysis.CSharp.Workspaces.dll", + // "Microsoft.CodeAnalysis.CSharp.dll", + // "Microsoft.CodeAnalysis.VisualBasic.Workspaces.dll", + // "Microsoft.CodeAnalysis.VisualBasic.dll", + // "Microsoft.CodeAnalysis.Workspaces.dll", + // "Microsoft.CodeAnalysis.dll", + // "Microsoft.JSInterop.WebAssembly.dll", + // "Microsoft.JSInterop.dll", + // "Microsoft.VisualBasic.Core.dll", + // "Microsoft.VisualBasic.dll", + // "Microsoft.Win32.Primitives.dll", + // "Microsoft.Win32.Registry.dll", + 'System.AppContext.dll', + 'System.Buffers.dll', + 'System.Collections.Concurrent.dll', + 'System.Collections.Immutable.dll', + 'System.Collections.NonGeneric.dll', + 'System.Collections.Specialized.dll', + 'System.Collections.dll', + // "System.ComponentModel.Annotations.dll", + // "System.ComponentModel.DataAnnotations.dll", + // "System.ComponentModel.EventBasedAsync.dll", + // "System.ComponentModel.Primitives.dll", + // "System.ComponentModel.TypeConverter.dll", + // "System.ComponentModel.dll", + // "System.Composition.AttributedModel.dll", + // "System.Composition.Convention.dll", + // "System.Composition.Hosting.dll", + // "System.Composition.Runtime.dll", + // "System.Composition.TypedParts.dll", + // "System.Configuration.dll", + 'System.Console.dll', + 'System.Core.dll', + // "System.Data.Common.dll", + // "System.Data.DataSetExtensions.dll", + // "System.Data.dll", + // "System.Diagnostics.Contracts.dll", + 'System.Diagnostics.Debug.dll', + // "System.Diagnostics.DiagnosticSource.dll", + // "System.Diagnostics.FileVersionInfo.dll", + // "System.Diagnostics.Process.dll", + // "System.Diagnostics.StackTrace.dll", + // "System.Diagnostics.TextWriterTraceListener.dll", + // "System.Diagnostics.Tools.dll", + // "System.Diagnostics.TraceSource.dll", + // "System.Diagnostics.Tracing.dll", + // "System.Drawing.Primitives.dll", + // "System.Drawing.dll", + // "System.Dynamic.Runtime.dll", + // "System.Formats.Asn1.dll", + // "System.Formats.Tar.dll", + 'System.Globalization.Calendars.dll', + 'System.Globalization.Extensions.dll', + 'System.Globalization.dll', + // "System.IO.Compression.Brotli.dll", + // "System.IO.Compression.FileSystem.dll", + // "System.IO.Compression.ZipFile.dll", + // "System.IO.Compression.dll", + // "System.IO.FileSystem.AccessControl.dll", + // "System.IO.FileSystem.DriveInfo.dll", + // "System.IO.FileSystem.Primitives.dll", + // "System.IO.FileSystem.Watcher.dll", + 'System.IO.FileSystem.dll', + // "System.IO.IsolatedStorage.dll", + // "System.IO.MemoryMappedFiles.dll", + // "System.IO.Pipelines.dll", + // "System.IO.Pipes.AccessControl.dll", + // "System.IO.Pipes.dll", + 'System.IO.UnmanagedMemoryStream.dll', + 'System.IO.dll', + 'System.Linq.Expressions.dll', + 'System.Linq.Parallel.dll', + 'System.Linq.Queryable.dll', + 'System.Linq.dll', + 'System.Memory.dll', + 'System.Net.Http.Json.dll', + 'System.Net.Http.dll', + 'System.Net.HttpListener.dll', + // "System.Net.Mail.dll", + 'System.Net.NameResolution.dll', + 'System.Net.NetworkInformation.dll', + // "System.Net.Ping.dll", + 'System.Net.Primitives.dll', + // "System.Net.Quic.dll", + 'System.Net.Requests.dll', + // "System.Net.Security.dll", + // "System.Net.ServicePoint.dll", + 'System.Net.Sockets.dll', + 'System.Net.WebClient.dll', + 'System.Net.WebHeaderCollection.dll', + // "System.Net.WebProxy.dll", + // "System.Net.WebSockets.Client.dll", + // "System.Net.WebSockets.dll", + 'System.Net.dll', + 'System.Numerics.Vectors.dll', + 'System.Numerics.dll', + 'System.ObjectModel.dll', + 'System.Private.CoreLib.dll', + // "System.Private.DataContractSerialization.dll", + 'System.Private.Uri.dll', + // "System.Private.Xml.Linq.dll", + // "System.Private.Xml.dll", + // "System.Reflection.DispatchProxy.dll", + // "System.Reflection.Emit.ILGeneration.dll", + // "System.Reflection.Emit.Lightweight.dll", + // "System.Reflection.Emit.dll", + // "System.Reflection.Extensions.dll", + // "System.Reflection.Metadata.dll", + // "System.Reflection.Primitives.dll", + // "System.Reflection.TypeExtensions.dll", + 'System.Reflection.dll', + 'System.Resources.Reader.dll', + 'System.Resources.ResourceManager.dll', + 'System.Resources.Writer.dll', + // "System.Runtime.CompilerServices.Unsafe.dll", + // "System.Runtime.CompilerServices.VisualC.dll", + 'System.Runtime.Extensions.dll', + 'System.Runtime.Handles.dll', + // "System.Runtime.InteropServices.JavaScript.dll", + 'System.Runtime.InteropServices.RuntimeInformation.dll', + 'System.Runtime.InteropServices.dll', + // "System.Runtime.Intrinsics.dll", + // "System.Runtime.Loader.dll", + // "System.Runtime.Numerics.dll", + // "System.Runtime.Serialization.Formatters.dll", + 'System.Runtime.Serialization.Json.dll', + // "System.Runtime.Serialization.Primitives.dll", + // "System.Runtime.Serialization.Xml.dll", + // "System.Runtime.Serialization.dll", + 'System.Runtime.dll', + // "System.Security.AccessControl.dll", + // "System.Security.Claims.dll", + // "System.Security.Cryptography.Algorithms.dll", + // "System.Security.Cryptography.Cng.dll", + // "System.Security.Cryptography.Csp.dll", + // "System.Security.Cryptography.Encoding.dll", + // "System.Security.Cryptography.OpenSsl.dll", + // "System.Security.Cryptography.Primitives.dll", + // "System.Security.Cryptography.X509Certificates.dll", + // "System.Security.Cryptography.dll", + // "System.Security.Principal.Windows.dll", + 'System.Security.Principal.dll', + 'System.Security.SecureString.dll', + 'System.Security.dll', + // "System.ServiceModel.Web.dll", + // "System.ServiceProcess.dll", + // "System.Text.Encoding.CodePages.dll", + 'System.Text.Encoding.Extensions.dll', + 'System.Text.Encoding.dll', + 'System.Text.Encodings.Web.dll', + 'System.Text.Json.dll', + 'System.Text.RegularExpressions.dll', + // "System.Threading.Channels.dll", + // "System.Threading.Overlapped.dll", + // "System.Threading.Tasks.Dataflow.dll", + 'System.Threading.Tasks.Extensions.dll', + 'System.Threading.Tasks.Parallel.dll', + 'System.Threading.Tasks.dll', + 'System.Threading.Thread.dll', + 'System.Threading.ThreadPool.dll', + 'System.Threading.Timer.dll', + 'System.Threading.dll', + // "System.Transactions.Local.dll", + 'System.Transactions.dll', + 'System.ValueTuple.dll', + 'System.Web.HttpUtility.dll', + // "System.Web.dll", + // "System.Windows.dll", + // "System.Xml.Linq.dll", + // "System.Xml.ReaderWriter.dll", + // "System.Xml.Serialization.dll", + // "System.Xml.XDocument.dll", + // "System.Xml.XPath.XDocument.dll", + // "System.Xml.XPath.dll", + // "System.Xml.XmlDocument.dll", + // "System.Xml.XmlSerializer.dll", + // "System.Xml.dll", + 'System.dll', + 'WebAssembly.dll', + // "WindowsBase.dll", + // "compiler.dll", + 'mscorlib.dll', + 'netstandard.dll' ]; -export const makeDotnetCompiler: CompilerFactory = async (ctx, streams) => { - const log = createLogger(streams.out); - const patchedConsole = redirect(globalThis.console, log); +export const makeDotnetCompiler: CompilerFactory = async (ctx, streams) => { + const log = createLogger(streams.out); + const patchedConsole = redirect(globalThis.console, log); - const { dotnet } = await inContext(ctx, import(/* @vite-ignore */ dotnetUrl)); - using _ = patch(globalThis, "console", patchedConsole); - const compilerModule: DotnetModule< - CompilerModuleImports, - CompilerModuleExports - > = await inContext(ctx, dotnet.create()); - const compiler = await inContext( - ctx, - new DotnetCompilerFactory(log, compilerModule).create( - precompiledLibsIndexUrl, - LIBS - ) - ); - const runtimeFactory = new DotnetRuntimeFactory(compiler); - return { - async compile(_, files) { - if (files.length !== 1) { - throw new Error("Compilation of multiple files is not implemented"); - } - const runtime = runtimeFactory.create(ctx, files[0].content); - return new DotnetProgram(runtime); - }, - }; + const { dotnet } = await inContext(ctx, import(/* @vite-ignore */ dotnetUrl)); + using _ = patch(globalThis, 'console', patchedConsole); + const compilerModule: DotnetModule = + await inContext(ctx, dotnet.create()); + const compiler = await inContext( + ctx, + new DotnetCompilerFactory(log, compilerModule).create(precompiledLibsIndexUrl, LIBS) + ); + const runtimeFactory = new DotnetRuntimeFactory(compiler); + return { + async compile(_, files) { + if (files.length !== 1) { + throw new Error('Compilation of multiple files is not implemented'); + } + const runtime = runtimeFactory.create(ctx, files[0].content); + return new DotnetProgram(runtime); + } + }; }; diff --git a/apps/ppp/src/lib/runtime/gleam/compiler-factory.ts b/apps/ppp/src/lib/runtime/gleam/compiler-factory.ts index 5bb52bf5..70b08a89 100644 --- a/apps/ppp/src/lib/runtime/gleam/compiler-factory.ts +++ b/apps/ppp/src/lib/runtime/gleam/compiler-factory.ts @@ -1,44 +1,39 @@ -import { redirect, createLogger } from "libs/logger"; -import type { CompilerFactory, Program } from "libs/compiler"; -import { - GleamModuleCompiler, - type GleamModule, - GleamProgram, -} from "gleam-runtime"; +import { redirect, createLogger } from 'libs/logger'; +import type { CompilerFactory, Program } from 'libs/compiler'; +import type { Streams } from 'libs/io'; +import { compileJsModule } from 'libs/js'; +import { GleamModuleCompiler, type GleamModule, GleamProgram } from 'gleam-runtime'; -import compilerWasmUrl from "gleam-runtime/compiler.wasm?url"; -import { compileJsModule } from "libs/js"; +import compilerWasmUrl from 'gleam-runtime/compiler.wasm?url'; import { base } from '$app/paths'; const precompiledGleamStdlibIndexUrl = new URL( - `${base}/assets/gleam`, - globalThis.location.origin + `${base}/assets/gleam`, + globalThis.location.origin ).toString(); -export const makeGleamCompiler: CompilerFactory = async (ctx, streams) => { - const patchedConsole = redirect(globalThis.console, createLogger(streams.out)); - const compiler = new GleamModuleCompiler( - streams.out, - precompiledGleamStdlibIndexUrl, - await WebAssembly.compileStreaming( - fetch(compilerWasmUrl, { signal: ctx.signal }) - ) - ); - return { - async compile(_, files) { - if (files.length !== 1) { - throw new Error("Compilation of multiple files is not implemented"); - } - const jsCode = compiler.compile(files[0].content); - const jsModule = await compileJsModule(jsCode); - if (!jsModule || typeof jsModule !== "object") { - throw new Error("Compilation failed"); - } - if (!("main" in jsModule) || typeof jsModule.main !== "function") { - throw new Error("Main function is missing"); - } - return new GleamProgram(jsModule as GleamModule, patchedConsole); - }, - }; +export const makeGleamCompiler: CompilerFactory = async (ctx, streams) => { + const patchedConsole = redirect(globalThis.console, createLogger(streams.out)); + const compiler = new GleamModuleCompiler( + streams.out, + precompiledGleamStdlibIndexUrl, + await WebAssembly.compileStreaming(fetch(compilerWasmUrl, { signal: ctx.signal })) + ); + return { + async compile(_, files) { + if (files.length !== 1) { + throw new Error('Compilation of multiple files is not implemented'); + } + const jsCode = compiler.compile(files[0].content); + const jsModule = await compileJsModule(jsCode); + if (!jsModule || typeof jsModule !== 'object') { + throw new Error('Compilation failed'); + } + if (!('main' in jsModule) || typeof jsModule.main !== 'function') { + throw new Error('Main function is missing'); + } + return new GleamProgram(jsModule as GleamModule, patchedConsole); + } + }; }; diff --git a/apps/ppp/src/lib/runtime/go/compiler-factory.ts b/apps/ppp/src/lib/runtime/go/compiler-factory.ts index de5250a6..dc866417 100644 --- a/apps/ppp/src/lib/runtime/go/compiler-factory.ts +++ b/apps/ppp/src/lib/runtime/go/compiler-factory.ts @@ -1,26 +1,25 @@ +import type { CompilerFactory, Program } from 'libs/compiler'; +import type { Streams } from 'libs/io'; +import { inContext } from 'libs/context'; import { - GoProgram, - makeCompilerFactory, - makeGoCompilerFactory, - makeGoExecutorFactory, -} from "go-runtime"; -import type { CompilerFactory, Program } from "libs/compiler"; -import { inContext } from "libs/context"; + GoProgram, + makeCompilerFactory, + makeGoCompilerFactory, + makeGoExecutorFactory +} from 'go-runtime'; -import wasmInit from "go-runtime/compiler.wasm?init"; +import wasmInit from 'go-runtime/compiler.wasm?init'; -export const makeGoCompiler: CompilerFactory = async (ctx, streams) => { - const goExecutorFactory = makeGoExecutorFactory( - makeGoCompilerFactory( - await makeCompilerFactory((imports) => inContext(ctx, wasmInit(imports))) - ) - ); - return { - async compile(ctx, files) { - if (files.length !== 1) { - throw new Error("Compilation of multiple files is not implemented"); - } - return new GoProgram(await goExecutorFactory(ctx, streams, files[0].content)); - }, - }; +export const makeGoCompiler: CompilerFactory = async (ctx, streams) => { + const goExecutorFactory = makeGoExecutorFactory( + makeGoCompilerFactory(await makeCompilerFactory((imports) => inContext(ctx, wasmInit(imports)))) + ); + return { + async compile(ctx, files) { + if (files.length !== 1) { + throw new Error('Compilation of multiple files is not implemented'); + } + return new GoProgram(await goExecutorFactory(ctx, streams, files[0].content)); + } + }; }; diff --git a/apps/ppp/src/lib/runtime/java/compiler-factory.ts b/apps/ppp/src/lib/runtime/java/compiler-factory.ts index 1472660c..2e31d88e 100644 --- a/apps/ppp/src/lib/runtime/java/compiler-factory.ts +++ b/apps/ppp/src/lib/runtime/java/compiler-factory.ts @@ -1,16 +1,17 @@ +import type { CompilerFactory, Program } from "libs/compiler"; +import type { Streams } from 'libs/io'; import { initFs, JavaCompiler, JavaProgram, makeJVMFactory, } from "java-runtime"; -import type { CompilerFactory, Program } from "libs/compiler"; import libZipUrl from "java-runtime/doppio.zip?url"; const CLASSNAME = "Program"; -export const makeJavaCompiler: CompilerFactory = async (ctx, streams) => { +export const makeJavaCompiler: CompilerFactory = async (ctx, streams) => { const jvmFactory = makeJVMFactory(streams); const libZipData = await fetch(libZipUrl, { signal: ctx.signal, diff --git a/apps/ppp/src/lib/runtime/js/compiler-factory.ts b/apps/ppp/src/lib/runtime/js/compiler-factory.ts index c124f015..c60cee48 100644 --- a/apps/ppp/src/lib/runtime/js/compiler-factory.ts +++ b/apps/ppp/src/lib/runtime/js/compiler-factory.ts @@ -1,8 +1,9 @@ import { redirect, createLogger } from "libs/logger"; import type { CompilerFactory, Program } from "libs/compiler"; +import type { Streams } from 'libs/io'; import { JsProgram } from "javascript-runtime"; -export const makeJsCompiler: CompilerFactory = async (_, streams) => { +export const makeJsCompiler: CompilerFactory = async (_, streams) => { const patchedConsole = redirect(globalThis.console, createLogger(streams.out)); return { async compile(_, files) { diff --git a/apps/ppp/src/lib/runtime/php/compiler-factory.ts b/apps/ppp/src/lib/runtime/php/compiler-factory.ts index e4a53a48..091e22be 100644 --- a/apps/ppp/src/lib/runtime/php/compiler-factory.ts +++ b/apps/ppp/src/lib/runtime/php/compiler-factory.ts @@ -1,9 +1,10 @@ import type { CompilerFactory, Program } from 'libs/compiler'; +import type { Streams } from 'libs/io'; import { phpCompilerFactory, PHPProgram } from 'php-runtime'; import phpWasmUrl from 'php-runtime/php.wasm?url'; -export const makePhpCompiler: CompilerFactory = async (ctx, streams) => { +export const makePhpCompiler: CompilerFactory = async (ctx, streams) => { const php = await phpCompilerFactory(ctx, async (info, resolve) => { const { instance, module } = await WebAssembly.instantiateStreaming( fetch(phpWasmUrl, { signal: ctx.signal, cache: 'force-cache' }), diff --git a/apps/ppp/src/lib/runtime/python/compiler-factory.ts b/apps/ppp/src/lib/runtime/python/compiler-factory.ts index d04dc6d7..2abd40c4 100644 --- a/apps/ppp/src/lib/runtime/python/compiler-factory.ts +++ b/apps/ppp/src/lib/runtime/python/compiler-factory.ts @@ -1,26 +1,24 @@ -import type { CompilerFactory, Program } from "libs/compiler"; -import { PyProgram, pyRuntimeFactory } from "python-runtime"; +import type { CompilerFactory, Program } from 'libs/compiler'; +import type { Streams } from 'libs/io'; +import { PyProgram, pyRuntimeFactory } from 'python-runtime'; -import wasmUrl from "python-runtime/pyodide.wasm?url"; -import stdlibUrl from "python-runtime/python-stdlib.zip?url"; +import wasmUrl from 'python-runtime/pyodide.wasm?url'; +import stdlibUrl from 'python-runtime/python-stdlib.zip?url'; -export const makePythonCompiler: CompilerFactory = async (ctx, streams) => { - const pyRuntime = await pyRuntimeFactory( - ctx, - streams, - (ctx, imports) => - WebAssembly.instantiateStreaming( - fetch(wasmUrl, { signal: ctx.signal }), - imports - ), - stdlibUrl - ); - return { - async compile(_, files) { - if (files.length !== 1) { - throw new Error("Compilation of multiple files is not implemented"); - } - return new PyProgram(files[0].content, pyRuntime); - }, - }; +export const makePythonCompiler: CompilerFactory = async (ctx, streams) => { + const pyRuntime = await pyRuntimeFactory( + ctx, + streams, + (ctx, imports) => + WebAssembly.instantiateStreaming(fetch(wasmUrl, { signal: ctx.signal }), imports), + stdlibUrl + ); + return { + async compile(_, files) { + if (files.length !== 1) { + throw new Error('Compilation of multiple files is not implemented'); + } + return new PyProgram(files[0].content, pyRuntime); + } + }; }; diff --git a/apps/ppp/src/lib/runtime/ruby/compiler-factory.ts b/apps/ppp/src/lib/runtime/ruby/compiler-factory.ts index df9ab2d5..a78c5a0d 100644 --- a/apps/ppp/src/lib/runtime/ruby/compiler-factory.ts +++ b/apps/ppp/src/lib/runtime/ruby/compiler-factory.ts @@ -1,19 +1,20 @@ -import type { CompilerFactory, Program } from "libs/compiler"; -import { RubyProgram, createRubyVM } from "ruby-runtime"; +import type { CompilerFactory, Program } from 'libs/compiler'; +import type { Streams } from 'libs/io'; +import { RubyProgram, createRubyVM } from 'ruby-runtime'; -import rubyWasmUrl from "ruby-runtime/ruby.wasm?url"; +import rubyWasmUrl from 'ruby-runtime/ruby.wasm?url'; -export const makeRubyCompiler: CompilerFactory = async (ctx, streams) => { - const rubyWasmModule = await WebAssembly.compileStreaming( - fetch(rubyWasmUrl, { signal: ctx.signal, cache: "force-cache" }) - ); - return { - async compile(_, files) { - if (files.length !== 1) { - throw new Error("Compilation of multiple files is not implemented"); - } - const vm = await createRubyVM(ctx, streams, rubyWasmModule); - return new RubyProgram(files[0].content, vm); - }, - }; +export const makeRubyCompiler: CompilerFactory = async (ctx, streams) => { + const rubyWasmModule = await WebAssembly.compileStreaming( + fetch(rubyWasmUrl, { signal: ctx.signal, cache: 'force-cache' }) + ); + return { + async compile(_, files) { + if (files.length !== 1) { + throw new Error('Compilation of multiple files is not implemented'); + } + const vm = await createRubyVM(ctx, streams, rubyWasmModule); + return new RubyProgram(files[0].content, vm); + } + }; }; diff --git a/apps/ppp/src/lib/runtime/rust/compiler-factory.ts b/apps/ppp/src/lib/runtime/rust/compiler-factory.ts index 0861cace..762c8c4b 100644 --- a/apps/ppp/src/lib/runtime/rust/compiler-factory.ts +++ b/apps/ppp/src/lib/runtime/rust/compiler-factory.ts @@ -1,45 +1,43 @@ -import type { Context } from "libs/context"; -import type { CompilerFactory, Program } from "libs/compiler"; -import { RustProgram, createWASI } from "rust-runtime"; +import type { Context } from 'libs/context'; +import type { CompilerFactory, Program } from 'libs/compiler'; +import type { Streams } from 'libs/io'; +import { RustProgram, createWASI } from 'rust-runtime'; -import miriWasmUrl from "rust-runtime/miri.wasm?url"; +import miriWasmUrl from 'rust-runtime/miri.wasm?url'; -const libsUrls = import.meta.glob("/node_modules/rust-runtime/dist/lib/*", { - eager: true, - import: "default", +const libsUrls = import.meta.glob('/node_modules/rust-runtime/dist/lib/*', { + eager: true, + import: 'default' }) as Record; // TODO: manual cache for large assets function loadLibs(ctx: Context) { - return Promise.all( - Object.entries(libsUrls).map(async ([lib, url]) => { - const response = await fetch(url, { - signal: ctx.signal, - cache: "force-cache", - }); - const buffer = await response.arrayBuffer(); - return [lib.slice(36), buffer] as [string, ArrayBuffer]; - }) - ); + return Promise.all( + Object.entries(libsUrls).map(async ([lib, url]) => { + const response = await fetch(url, { + signal: ctx.signal, + cache: 'force-cache' + }); + const buffer = await response.arrayBuffer(); + return [lib.slice(36), buffer] as [string, ArrayBuffer]; + }) + ); } -export const makeRustCompiler: CompilerFactory = async ( - ctx, - streams -) => { - const [miri, libs] = await Promise.all([ - await WebAssembly.compileStreaming( - fetch(miriWasmUrl, { signal: ctx.signal, cache: "force-cache" }) - ), - loadLibs(ctx), - ]); - const wasi = createWASI(streams, libs); - return { - async compile(_, files) { - if (files.length !== 1) { - throw new Error("Compilation of multiple files is not implemented"); - } - return new RustProgram(files[0].content, wasi, miri); - }, - }; +export const makeRustCompiler: CompilerFactory = async (ctx, streams) => { + const [miri, libs] = await Promise.all([ + await WebAssembly.compileStreaming( + fetch(miriWasmUrl, { signal: ctx.signal, cache: 'force-cache' }) + ), + loadLibs(ctx) + ]); + const wasi = createWASI(streams, libs); + return { + async compile(_, files) { + if (files.length !== 1) { + throw new Error('Compilation of multiple files is not implemented'); + } + return new RustProgram(files[0].content, wasi, miri); + } + }; }; diff --git a/apps/ppp/src/lib/runtime/ts/compiler-factory.ts b/apps/ppp/src/lib/runtime/ts/compiler-factory.ts index e3e4b7c5..1f122514 100644 --- a/apps/ppp/src/lib/runtime/ts/compiler-factory.ts +++ b/apps/ppp/src/lib/runtime/ts/compiler-factory.ts @@ -1,19 +1,17 @@ -import { redirect, createLogger } from "libs/logger"; -import type { CompilerFactory, Program } from "libs/compiler"; -import { JsProgram } from "javascript-runtime"; -import { compileTsModule } from "typescript-runtime"; +import { redirect, createLogger } from 'libs/logger'; +import type { CompilerFactory, Program } from 'libs/compiler'; +import type { Streams } from 'libs/io'; +import { JsProgram } from 'javascript-runtime'; +import { compileTsModule } from 'typescript-runtime'; -export const makeTsCompiler: CompilerFactory = async (_, streams) => { - const patchedConsole = redirect(globalThis.console, createLogger(streams.out)); - return { - async compile (_, files) { - if (files.length !== 1) { - throw new Error("Compilation of multiple files is not implemented"); - } - return new JsProgram( - compileTsModule(files[0].content), - patchedConsole - ); - }, - }; +export const makeTsCompiler: CompilerFactory = async (_, streams) => { + const patchedConsole = redirect(globalThis.console, createLogger(streams.out)); + return { + async compile(_, files) { + if (files.length !== 1) { + throw new Error('Compilation of multiple files is not implemented'); + } + return new JsProgram(compileTsModule(files[0].content), patchedConsole); + } + }; }; diff --git a/apps/ppp/src/lib/runtime/ts/test-compiler-factory.ts b/apps/ppp/src/lib/runtime/ts/test-compiler-factory.ts index ea482fe1..9e66acd6 100644 --- a/apps/ppp/src/lib/runtime/ts/test-compiler-factory.ts +++ b/apps/ppp/src/lib/runtime/ts/test-compiler-factory.ts @@ -1,4 +1,4 @@ -import type { Streams, Writer } from "libs/io"; +import type { Streams } from "libs/io"; import { compileJsModule } from "libs/js"; import { createLogger, redirect } from "libs/logger"; import type { TestCompiler } from "libs/testing"; diff --git a/apps/ppp/src/routes/(app)/editor/+page@.svelte b/apps/ppp/src/routes/(app)/editor/+page@.svelte index d324b08f..9bf4b5c5 100644 --- a/apps/ppp/src/routes/(app)/editor/+page@.svelte +++ b/apps/ppp/src/routes/(app)/editor/+page@.svelte @@ -24,7 +24,8 @@ createTerminal, RunButton, type ProcessStatus, - createStreams + createReadableStream, + createLineInputMode } from '$lib/components/editor'; import { Panel, @@ -36,10 +37,10 @@ } from '$lib/components/editor/panel'; import { CheckBox, Number } from '$lib/components/editor/controls'; import { m } from '$lib/paraglide/messages'; - - import { RUNTIMES } from './_runtimes'; import EditorProvider from '$lib/editor-provider.svelte'; + import { RUNTIMES } from './runtimes'; + const languages = Object.keys(RUNTIMES).sort() as Language[]; if (languages.length === 0) { throw new Error('No test runner factories provided'); @@ -77,7 +78,8 @@ }); const { terminal, fitAddon } = createTerminal(); - const streams = createStreams(terminal); + const input = createReadableStream(terminal) + .pipeThrough(createLineInputMode(terminal)) setEditorContext(new EditorContext(model, terminal, fitAddon)); @@ -101,7 +103,7 @@ let executionTimeout = $state(executionTimeoutStorage.load()); debouncedSave(executionTimeoutStorage, () => executionTimeout, 100); - const terminalLogger = createLogger(streams.out); + const terminalLogger = createLogger(terminal); let compilerFactory = $derived(runtime.compilerFactory); let status = $state('stopped'); let compiler: Compiler | null = null; @@ -131,7 +133,7 @@ terminal.reset(); try { if (compiler === null) { - compiler = await compilerFactory(compilerCtx.ref, streams); + compiler = await compilerFactory(compilerCtx.ref, { input, output: terminal }); } const program = await compiler.compile(programCtxWithTimeout, [ { @@ -157,59 +159,59 @@

+ +
+ + + + + +
+ + + {#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} +
    + +
    +
    + + +
    + + +
    +
    +
    +
    - -
    - - - - - -
    - - - {#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/sync/io.ts b/packages/libs/src/sync/io.ts index c655b0f7..34ba12d3 100644 --- a/packages/libs/src/sync/io.ts +++ b/packages/libs/src/sync/io.ts @@ -1,7 +1,8 @@ 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"; -import { makeErrorWriter } from "../logger.js"; export function readFromQueue(inputQueue: SharedQueue) { const input = new ReadableStream({ @@ -24,7 +25,9 @@ export function writeToQueue(input: ReadableStreamOfBytes, queue: SharedQueue) { }, }); const controller = new AbortController(); - input.pipeTo(w); + input + .pipeTo(w, { signal: controller.signal, preventCancel: true }) + .catch(noop); return { [Symbol.dispose]() { controller.abort(); diff --git a/packages/libs/src/testing/testing.ts b/packages/libs/src/testing/testing.ts index 3cae8325..e121a737 100644 --- a/packages/libs/src/testing/testing.ts +++ b/packages/libs/src/testing/testing.ts @@ -2,7 +2,6 @@ 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"; -import type { Streams } from "libs/io"; export interface TestCase { input: I; @@ -24,8 +23,8 @@ export interface TestProgram { export type TestCompiler = Compiler>; -export type TestCompilerFactory = CompilerFactory< - Streams, +export type TestCompilerFactory = CompilerFactory< + Options, TestProgram >; From 9eab6d346281a19870970e515aca9a35e7a634fa Mon Sep 17 00:00:00 2001 From: Roman Krasilnikov Date: Wed, 19 Nov 2025 01:15:47 +0300 Subject: [PATCH 6/6] Add input mode selector --- apps/ppp/messages/en.json | 3 +- apps/ppp/messages/ru.json | 3 +- .../lib/components/editor/controls/index.ts | 1 + .../components/editor/controls/select.svelte | 31 +++++++++++++++++ .../ppp/src/lib/components/editor/terminal.ts | 22 +++++++++---- apps/ppp/src/lib/problem/editor.svelte | 31 ++++++++++++----- .../ppp/src/routes/(app)/editor/+page@.svelte | 33 ++++++++++++++----- 7 files changed, 98 insertions(+), 26 deletions(-) create mode 100644 apps/ppp/src/lib/components/editor/controls/select.svelte 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 856a4f94..383e4c68 100644 --- a/apps/ppp/src/lib/components/editor/terminal.ts +++ b/apps/ppp/src/lib/components/editor/terminal.ts @@ -11,6 +11,13 @@ export interface TerminalConfig { theme?: ITheme; } +export enum InputMode { + Line = 'line', + Raw = 'raw' +} + +export const INPUT_MODS = Object.values(InputMode) + export function createTerminal({ theme = makeTerminalTheme() }: TerminalConfig = {}) { const terminal = new Terminal({ theme, @@ -32,13 +39,14 @@ export function createReadableStream(terminal: Terminal) { }); }, cancel() { + console.log("CANCEL") disposable.dispose(); } }); } -const EMPTY_UINT8_ARRAY = new Uint8Array() -const TEXT_ENCODER = new TextEncoder() +const EMPTY_UINT8_ARRAY = new Uint8Array(); +const TEXT_ENCODER = new TextEncoder(); export function createRawInputMode(terminal: Terminal) { return new TransformStream({ @@ -58,7 +66,7 @@ export function createLineInputMode(terminal: Terminal) { const buffer: string[] = []; function flush(controller: TransformStreamDefaultController) { if (buffer.length > 0) { - controller.enqueue(TEXT_ENCODER.encode(buffer.join(""))); + controller.enqueue(TEXT_ENCODER.encode(buffer.join(''))); buffer.length = 0; } } @@ -67,7 +75,7 @@ export function createLineInputMode(terminal: Terminal) { if (chunk === '\r') { terminal.write('\r\n'); flush(controller); - // EOF + // EOF controller.enqueue(EMPTY_UINT8_ARRAY); return; } @@ -76,10 +84,10 @@ export function createLineInputMode(terminal: Terminal) { if (buffer.pop() !== undefined) { terminal.write('\b \b'); } - return + return; } - terminal.write(chunk) - buffer.push(chunk) + 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 71932c53..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'; @@ -34,6 +35,9 @@ type ProcessStatus, createReadableStream, createLineInputMode, + INPUT_MODS, + InputMode, + createRawInputMode, } from '$lib/components/editor'; import { Panel, @@ -43,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'; @@ -121,13 +125,6 @@ }; }); - const { terminal, fitAddon } = createTerminal(); - const input = createReadableStream(terminal) - .pipeThrough(createLineInputMode(terminal)); - - const editorContext = new EditorContext(model, terminal, fitAddon); - setEditorContext(editorContext); - const editorWidthStorage = createSyncStorage( localStorage, 'test-editor-width', @@ -179,7 +176,23 @@ let executionTimeout = $state(executionTimeoutStorage.load()); debouncedSave(executionTimeoutStorage, () => executionTimeout, 100); + 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); @@ -191,6 +204,7 @@ }); $effect(() => () => compilerCtx[Symbol.dispose]()); $effect(() => { + inputMode; testCompilerFactory; compilerCtx.cancel(); status = 'stopped'; @@ -347,6 +361,7 @@ alt={m.execution_timeout_description()} bind:value={executionTimeout} /> +