diff --git a/src/components/Machine.tsx b/src/components/Machine.tsx index de65819..32fe524 100644 --- a/src/components/Machine.tsx +++ b/src/components/Machine.tsx @@ -1,6 +1,5 @@ import { useState } from "react"; import { stg_machine } from "@/stgmachine/machine"; -import { sum_prg } from "@/stglang/test"; import ProgramView from "@/components/ProgramView"; import StackView from "@/components/StackView"; import HeapView from "@/components/HeapView"; @@ -13,6 +12,8 @@ import { import { Toaster } from "@/components/ui/toaster"; import { useMediaQuery } from "@/hooks/use-media-query"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { build_ast } from "@/stglang/ASTBuilder"; +import type { InferEntrySchema } from "astro:content"; export type STGSettings = { garbage_collection: boolean, @@ -22,11 +23,9 @@ export type STGSettings = { run_limit: number } -export const default_program = sum_prg; - -export default function Machine() { +export default function Machine({ examples, default_program }: { examples: InferEntrySchema<"examples">[], default_program: string }) { // set machine is called *only* when a new program is loaded, but will be mutated while stepping - const [machine, setMachine] = useState(() => new stg_machine(default_program, false, true)); + const [machine, setMachine] = useState(() => new stg_machine(build_ast(examples.find(x => x.name === default_program)?.code as string), false, true)); const [loaded, setLoaded] = useState(false); const [step, setStepOriginal] = useState(Number(new URLSearchParams(location.search).get('step')) || 0); const [breakpoints, setBreakpoints] = useState>(new Map()); @@ -100,7 +99,8 @@ export default function Machine() { settings={settings} setSettings={setSettings} breakpoints={breakpoints} setBreakpoints={setBreakpoints} enteredThunks={enteredThunks} setEnteredThunks={setEnteredThunks} - isDesktop={isDesktop} className="w-full h-0 grow" /> + isDesktop={isDesktop} className="w-full h-0 grow" + examples={examples} default_program={default_program} /> @@ -124,7 +124,8 @@ export default function Machine() { settings={settings} setSettings={setSettings} breakpoints={breakpoints} setBreakpoints={setBreakpoints} enteredThunks={enteredThunks} setEnteredThunks={setEnteredThunks} - isDesktop={isDesktop} className="h-full" /> + isDesktop={isDesktop} className="h-full" + examples={examples} default_program={default_program} /> {loaded && diff --git a/src/components/ProgramView.tsx b/src/components/ProgramView.tsx index 9971e51..a144aa8 100644 --- a/src/components/ProgramView.tsx +++ b/src/components/ProgramView.tsx @@ -12,14 +12,13 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; -import examples from "@/stglang/examples"; import { binding, case_eval, case_expr, FUN, identifier, let_expr, letrec_expr, literal, THUNK, type expression } from "@/stglang/types"; import { Separator } from "@/components/ui/separator"; import HelpPopover from "@/components/HelpPopover"; import type { STGSettings } from "@/components/Machine"; import SettingsMenu from "@/components/SettingsMenu"; -import { default_program } from "@/components/Machine"; import { inflate, deflate } from 'pako'; +import type { InferEntrySchema } from "astro:content"; function compress(txt: string) { return btoa(Array.from(deflate(txt), (byte) => String.fromCodePoint(byte)).join("")); @@ -55,7 +54,7 @@ function findExpression(lineStart: number, nextLineStart: number, expr: expressi return undefined; } -export default function ProgramView({ className, machine, setMachine, step, setStep, loaded, setLoaded, settings, setSettings, breakpoints, setBreakpoints, isDesktop, enteredThunks, setEnteredThunks }: +export default function ProgramView({ className, machine, setMachine, step, setStep, loaded, setLoaded, settings, setSettings, breakpoints, setBreakpoints, isDesktop, enteredThunks, setEnteredThunks, examples, default_program }: { className?: string, machine: stg_machine, @@ -71,8 +70,10 @@ export default function ProgramView({ className, machine, setMachine, step, setS isDesktop: boolean, enteredThunks: [number, number][], setEnteredThunks: React.Dispatch, + examples: InferEntrySchema<"examples">[], + default_program: string, }) { - const [selected, setSelected] = useState('Sum foldl'); + const [selected, setSelected] = useState(default_program); const [programText, setProgramText] = useState(() => { const searchParams = new URLSearchParams(location.search); const programParam = searchParams.get('program'); @@ -80,7 +81,7 @@ export default function ProgramView({ className, machine, setMachine, step, setS setSelected(''); return decompress(programParam); } else { - return String(default_program); + return examples.find(x => x.name === default_program)?.code as string; } }); const [error, setError] = useState<{ from: number, to: number, step: number } | undefined>(undefined); diff --git a/src/content/config.ts b/src/content/config.ts index 8c97f08..b09aa43 100644 --- a/src/content/config.ts +++ b/src/content/config.ts @@ -9,4 +9,11 @@ export const collections = { draft: z.boolean(), }), }), + 'examples': defineCollection({ + type: 'data', + schema: z.object({ + name: z.string(), + code: z.string(), + }), + }), }; \ No newline at end of file diff --git a/src/content/docs/mdtest.mdx b/src/content/docs/mdtest.mdx index 4515006..da7f588 100644 --- a/src/content/docs/mdtest.mdx +++ b/src/content/docs/mdtest.mdx @@ -3,9 +3,9 @@ title: "Example with STG machine components" description: jsx components? In my markdown? draft: true --- -import examples from "@/stglang/examples"; +import { getEntry } from "astro:content"; -export const code = examples.find(x => x.name === "Partial application").code; +export const code = (await getEntry("examples", "partial-application")).data.code; # Hello world This is the first test of markdown based examples! diff --git a/src/content/examples/fib-zipWith.yaml b/src/content/examples/fib-zipWith.yaml new file mode 100644 index 0000000..8981182 --- /dev/null +++ b/src/content/examples/fib-zipWith.yaml @@ -0,0 +1,52 @@ + +name: Fibonacci zipWith +code: |2 + data Number a = Num a + data Boolean = True | False + data List a = Nil | Cons a (List a) + nil = CON Nil + plusInt = FUN(x y -> + case x of { + Num i -> case y of { + Num j -> case i +# j of { + x -> let result = CON(Num x) + in result; + }; + }; + } + ) + zipWith = FUN(f x y -> + case x of { + Nil -> nil; + Cons hx tx -> case y of { + Nil -> nil; + Cons hy ty -> let fxy = THUNK(f hx hy) + fxys = THUNK(zipWith f tx ty) + zippedList = CON(Cons fxy fxys) + in zippedList; + }; + } + ) + forcen = FUN(n list -> + case n ># 0 of { + False -> nil; + True -> case list of { + Nil -> nil; + Cons h t -> case h of { + x -> case n -# 1 of { + n -> case forcen n t of { + y -> let result = CON(Cons x y) + in result; + }; + }; + }; + }; + } + ) + zero = CON(Num 0) + one = CON(Num 1) + fib = THUNK(letrec fib0 = CON(Cons zero fib1) + fib1 = CON(Cons one fib2) + fib2 = THUNK(zipWith plusInt fib0 fib1) + in fib2) + main = THUNK(forcen 10 fib) diff --git a/src/content/examples/map-laziness.yaml b/src/content/examples/map-laziness.yaml new file mode 100644 index 0000000..8323a44 --- /dev/null +++ b/src/content/examples/map-laziness.yaml @@ -0,0 +1,36 @@ +name: Not so simple map +code: |2 + data Number a = Num a + data List a = Nil | Cons a (List a) + nil = CON Nil + one = CON(Num 1) + two = CON(Num 2) + three = CON(Num 3) + plusOne = FUN(x -> + case x of { + Num i -> case i +# 1 of { + y -> let result = CON(Num y) + in result; + }; + } + ) + list1 = CON(Cons one nil) + list2 = CON(Cons two list1) + list3 = CON(Cons three list2) + map = FUN(f xs -> + case xs of { + Nil -> nil; + Cons h t -> let fh = THUNK(f h) + ft = THUNK(map f t) + result = CON(Cons fh ft) + in result; + }) + last = FUN(xs -> + case xs of { + Cons h t -> case t of { + Nil -> h; + Cons h2 t2 -> last t; + }; + }) + mapped = THUNK(map plusOne list3) + main = THUNK(last mapped) diff --git a/src/content/examples/partial-application.yaml b/src/content/examples/partial-application.yaml new file mode 100644 index 0000000..3aef154 --- /dev/null +++ b/src/content/examples/partial-application.yaml @@ -0,0 +1,43 @@ +name: Partial application +code: |2 + data Number a = Num a + data List a = Nil | Cons a (List a) + nil = CON Nil + map = FUN(f xs -> + case xs of { + Nil -> nil; + Cons y ys -> let h = THUNK(f y) + t = THUNK(map f ys) + r = CON(Cons h t) + in r; + } + ) + times2 = FUN(x -> + case x of { + Num x -> case x *# 2 of { + x -> let result = CON(Num x) + in result; + }; + } + ) + times2list = THUNK(map times2) + forcelist = FUN(list -> + case list of { + Nil -> nil; + Cons h t -> case h of { + x -> case forcelist t of { + y -> let result = CON(Cons x y) + in result; + }; + }; + } + ) + main = THUNK(let one = CON(Num 1) + two = CON(Num 2) + three = CON(Num 3) + list1 = CON(Cons one nil) + list2 = CON(Cons two list1) + list3 = CON(Cons three list2) + list_x2 = THUNK(times2list list3) + list_x4 = THUNK(times2list list_x2) + in forcelist list_x4) diff --git a/src/content/examples/primop.yaml b/src/content/examples/primop.yaml new file mode 100644 index 0000000..302b025 --- /dev/null +++ b/src/content/examples/primop.yaml @@ -0,0 +1,7 @@ +name: Primitive operator +code: |2 + data Number a = Num a + main = THUNK(case 2 +# 3 of { + x -> let result = CON(Num x) + in result; + }) diff --git a/src/content/examples/sum-foldl.yaml b/src/content/examples/sum-foldl.yaml new file mode 100644 index 0000000..90c5783 --- /dev/null +++ b/src/content/examples/sum-foldl.yaml @@ -0,0 +1,31 @@ +name: Sum foldl +code: |2 + data Number a = Num a + data List a = Nil | Cons a (List a) + nil = CON Nil + zero = CON(Num 0) + one = CON(Num 1) + two = CON(Num 2) + three = CON(Num 3) + plusInt = FUN(x y -> + case x of { + Num i -> case y of { + Num j -> case i +# j of { + x -> let result = CON(Num x) + in result; + }; + }; + } + ) + foldl = FUN(f acc list -> + case list of { + Nil -> acc; + Cons h t -> let newAcc = THUNK(f acc h) + in foldl f newAcc t; + } + ) + sum = FUN(list -> foldl plusInt zero list) + list1 = CON(Cons one nil) + list2 = CON(Cons two list1) + list3 = CON(Cons three list2) + main = THUNK(sum list3) diff --git a/src/content/examples/too-many.yaml b/src/content/examples/too-many.yaml new file mode 100644 index 0000000..a27fee0 --- /dev/null +++ b/src/content/examples/too-many.yaml @@ -0,0 +1,11 @@ +name: Too many args +code: |2 + data Number a = Num a + plusN = FUN(n -> let f = FUN(a -> + case a of { + x -> case x +# n of { + y -> let result = CON(Num y) + in result; + }; + }) in f) + main = THUNK(plusN 1 2) diff --git a/src/pages/index.astro b/src/pages/index.astro index 96d973b..0418592 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,10 +1,16 @@ --- +import { getCollection } from "astro:content"; import Layout from "@/layouts/Layout.astro"; import Machine from "@/components/Machine.tsx"; import "@/styles/syntax.css"; import "@/styles/react_flow_overrides.css"; ---- +const examples = (await getCollection("examples")).map(x => x.data); +const default_program = "Sum foldl"; +if (!examples.find(x => x.name === default_program)) { + throw new Error("Default program not in examples") +} +--- - + diff --git a/src/stglang/examples.ts b/src/stglang/examples.ts deleted file mode 100644 index 0b3ec8a..0000000 --- a/src/stglang/examples.ts +++ /dev/null @@ -1,189 +0,0 @@ -// Looking for better options to do this -export default [ - { - name: "Fibonacci zipWith", - code: `data Number a = Num a -data Boolean = True | False -data List a = Nil | Cons a (List a) -nil = CON Nil -plusInt = FUN(x y -> - case x of { - Num i -> case y of { - Num j -> case i +# j of { - x -> let result = CON(Num x) - in result; - }; - }; - } -) -zipWith = FUN(f x y -> - case x of { - Nil -> nil; - Cons hx tx -> case y of { - Nil -> nil; - Cons hy ty -> let fxy = THUNK(f hx hy) - fxys = THUNK(zipWith f tx ty) - zippedList = CON(Cons fxy fxys) - in zippedList; - }; - } -) -forcen = FUN(n list -> - case n ># 0 of { - False -> nil; - True -> case list of { - Nil -> nil; - Cons h t -> case h of { - x -> case n -# 1 of { - n -> case forcen n t of { - y -> let result = CON(Cons x y) - in result; - }; - }; - }; - }; - } -) -zero = CON(Num 0) -fib = THUNK(letrec fib0 = CON(Cons zero fib1) - fib1 = THUNK(let one = CON(Num 1) - tmp = CON(Cons one fib2) - in tmp) - fib2 = THUNK(zipWith plusInt fib0 fib1) - in fib2) -main = THUNK(forcen 10 fib)` - }, - { - name: "Partial application", - code: `data Number a = Num a -data List a = Nil | Cons a (List a) -nil = CON Nil -map = FUN(f xs -> - case xs of { - Nil -> nil; - Cons y ys -> let h = THUNK(f y) - t = THUNK(map f ys) - r = CON(Cons h t) - in r; - } -) -times2 = FUN(x -> - case x of { - Num x -> case x *# 2 of { - x -> let result = CON(Num x) - in result; - }; - } -) -times2list = THUNK(map times2) -forcelist = FUN(list -> - case list of { - Nil -> nil; - Cons h t -> case h of { - x -> case forcelist t of { - y -> let result = CON(Cons x y) - in result; - }; - }; - } -) -main = THUNK(let one = CON(Num 1) - two = CON(Num 2) - three = CON(Num 3) - list1 = CON(Cons one nil) - list2 = CON(Cons two list1) - list3 = CON(Cons three list2) - list_x2 = THUNK(times2list list3) - list_x4 = THUNK(times2list list_x2) -in forcelist list_x4)` - }, - { - name: "Sum foldl", - code: `data Number a = Num a -data List a = Nil | Cons a (List a) -nil = CON Nil -zero = CON(Num 0) -one = CON(Num 1) -two = CON(Num 2) -three = CON(Num 3) -plusInt = FUN(x y -> - case x of { - Num i -> case y of { - Num j -> case i +# j of { - x -> let result = CON(Num x) - in result; - }; - }; - } -) -foldl = FUN(f acc list -> - case list of { - Nil -> acc; - Cons h t -> let newAcc = THUNK(f acc h) - in foldl f newAcc t; - } -) -sum = FUN(list -> foldl plusInt zero list) -list1 = CON(Cons one nil) -list2 = CON(Cons two list1) -list3 = CON(Cons three list2) -main = THUNK(sum list3)` - }, - { - name: "Too many args", - code: `data Number a = Num a -plusN = FUN(n -> let f = FUN(a -> - case a of { - x -> case x +# n of { - y -> let result = CON(Num y) - in result; - }; - }) in f) -main = THUNK(plusN 1 2)` - }, - { - name: "Primitive operator", - code: `data Number a = Num a -main = THUNK(case 2 +# 3 of { - x -> let result = CON(Num x) - in result; -})` - }, - { - name: "Not so simple map", - code: `data Number a = Num a -data List a = Nil | Cons a (List a) -nil = CON Nil -one = CON(Num 1) -two = CON(Num 2) -three = CON(Num 3) -plusOne = FUN(x -> - case x of { - Num i -> case i +# 1 of { - y -> let result = CON(Num y) - in result; - }; - } -) -list1 = CON(Cons one nil) -list2 = CON(Cons two list1) -list3 = CON(Cons three list2) -map = FUN(f xs -> - case xs of { - Nil -> nil; - Cons h t -> let fh = THUNK(f h) - ft = THUNK(map f t) - result = CON(Cons fh ft) - in result; - }) -last = FUN(xs -> - case xs of { - Cons h t -> case t of { - Nil -> h; - Cons h2 t2 -> last t; - }; - }) -mapped = THUNK(map plusOne list3) -main = THUNK(last mapped)` - } -]; \ No newline at end of file