Skip to content

Instantly share code, notes, and snippets.

@HugeLetters
Last active November 10, 2025 01:36
Show Gist options
  • Select an option

  • Save HugeLetters/b7cfd60b0afc5e61f1fc2441d340326c to your computer and use it in GitHub Desktop.

Select an option

Save HugeLetters/b7cfd60b0afc5e61f1fc2441d340326c to your computer and use it in GitHub Desktop.
AppleScript codegen + pretty-print with Effect
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