import * as Arr from "effect/Array"; import * as Data from "effect/Data"; import * as Effect from "effect/Effect"; import * as Fn from "effect/Function"; import * as Match from "effect/Match"; import * as Option from "effect/Option"; export function render(node: Node) { return resolveNode(node).pipe(Effect.provide(RenderService.Default)); } function resolveNode( node: Node, ): Effect.Effect { return Match.value(node).pipe( Match.when(Match.string, (s) => { return RenderService.use((r) => { if (r.lineStart) { return `${"\t".repeat(r.indenation)}${s}`; } return s; }); }), Match.tag("Indent", (s) => resolveNode(s.node).pipe(RenderService.indent)), Match.tag("InlineSequence", (s) => Fn.pipe( s.nodes, Arr.map((node, i) => { if (i === 0) { return resolveNode(node); } return resolveNode(node).pipe( RenderService.update(() => ({ lineStart: false })), ); }), Effect.all, Effect.map((s) => s.join("")), ), ), Match.tag("Sequence", (s) => Fn.pipe( s.nodes, Arr.filterMap((node) => Option.fromNullable(node).pipe( Option.map(resolveNode), Option.map(RenderService.update(() => ({ lineStart: true }))), ), ), Effect.all, Effect.map((s) => s.join("\n")), ), ), Match.when(Effect.isEffect, (s) => s.pipe(Effect.flatMap(resolveNode))), Match.exhaustive, ); } class Indent extends Data.TaggedClass("Indent")<{ readonly node: Node; }> {} export function indent(node: Node) { return new Indent({ node }); } class InlineSequence extends Data.TaggedClass("InlineSequence")<{ readonly nodes: ReadonlyArray>; }> {} export function inline( ...nodes: InlineSequence["nodes"] ) { return new InlineSequence({ nodes }); } class Sequence extends Data.TaggedClass("Sequence")<{ readonly nodes: ReadonlyArray | null | undefined>; }> {} export function seq(...nodes: Sequence["nodes"]) { return new Sequence({ nodes }); } type Node = | string | Indent | InlineSequence | Sequence | Effect.Effect, E, C | RenderService>; class RenderService extends Effect.Service()("RenderService", { sync() { return { indenation: 0, lineStart: true, }; }, }) { static update(fn: (service: RenderService) => Partial) { return Effect.updateService(RenderService, (r) => { const updated = fn(r); return RenderService.make({ lineStart: updated.lineStart ?? r.lineStart, indenation: updated.indenation ?? r.indenation, }); }); } static indent = RenderService.update((r) => ({ indenation: r.indenation + 1, })); } export function condition( predicate: Node, ifTrue: Node, ifFalse?: Node, ) { return seq( inline("if ", predicate, " then"), indent(ifTrue), ifFalse ? seq("else", indent(ifFalse)) : null, "end if", ); } export function tell(who: Node, what: Node) { return seq(inline("tell ", who), indent(what), "end tell"); } export function isRunning(appName: string) { return `application "${appName}" is running`; } export function exists(what: Node) { return inline("exists ", what); } export function repeatUntil( predicate: Node, body: Node, ) { return seq( inline("repeat until (", predicate, ")"), indent(body), "end repeat", ); }