Last active
November 10, 2025 01:36
-
-
Save HugeLetters/b7cfd60b0afc5e61f1fc2441d340326c to your computer and use it in GitHub Desktop.
AppleScript codegen + pretty-print with Effect
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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<E = never, C = never>(node: Node<E, C>) { | |
| return resolveNode(node).pipe(Effect.provide(RenderService.Default)); | |
| } | |
| function resolveNode<E = never, C = never>( | |
| node: Node<E, C>, | |
| ): Effect.Effect<string, E, C | RenderService> { | |
| 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<E, C> extends Data.TaggedClass("Indent")<{ | |
| readonly node: Node<E, C>; | |
| }> {} | |
| export function indent<E = never, C = never>(node: Node<E, C>) { | |
| return new Indent({ node }); | |
| } | |
| class InlineSequence<E, C> extends Data.TaggedClass("InlineSequence")<{ | |
| readonly nodes: ReadonlyArray<Node<E, C>>; | |
| }> {} | |
| export function inline<E = never, C = never>( | |
| ...nodes: InlineSequence<E, C>["nodes"] | |
| ) { | |
| return new InlineSequence({ nodes }); | |
| } | |
| class Sequence<E, C> extends Data.TaggedClass("Sequence")<{ | |
| readonly nodes: ReadonlyArray<Node<E, C> | null | undefined>; | |
| }> {} | |
| export function seq<E = never, C = never>(...nodes: Sequence<E, C>["nodes"]) { | |
| return new Sequence({ nodes }); | |
| } | |
| type Node<E, C> = | |
| | string | |
| | Indent<E, C> | |
| | InlineSequence<E, C> | |
| | Sequence<E, C> | |
| | Effect.Effect<Node<E, C>, E, C | RenderService>; | |
| class RenderService extends Effect.Service<RenderService>()("RenderService", { | |
| sync() { | |
| return { | |
| indenation: 0, | |
| lineStart: true, | |
| }; | |
| }, | |
| }) { | |
| static update(fn: (service: RenderService) => Partial<RenderService>) { | |
| 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<E = never, C = never>( | |
| predicate: Node<E, C>, | |
| ifTrue: Node<E, C>, | |
| ifFalse?: Node<E, C>, | |
| ) { | |
| return seq( | |
| inline("if ", predicate, " then"), | |
| indent(ifTrue), | |
| ifFalse ? seq("else", indent(ifFalse)) : null, | |
| "end if", | |
| ); | |
| } | |
| export function tell<E = never, C = never>(who: Node<E, C>, what: Node<E, C>) { | |
| return seq(inline("tell ", who), indent(what), "end tell"); | |
| } | |
| export function isRunning(appName: string) { | |
| return `application "${appName}" is running`; | |
| } | |
| export function exists<E = never, C = never>(what: Node<E, C>) { | |
| return inline("exists ", what); | |
| } | |
| export function repeatUntil<E = never, C = never>( | |
| predicate: Node<E, C>, | |
| body: Node<E, C>, | |
| ) { | |
| return seq( | |
| inline("repeat until (", predicate, ")"), | |
| indent(body), | |
| "end repeat", | |
| ); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment