Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## What this is

The LiaScript macro that embeds glpython in courses. `README.md` is both the documentation and the macro itself: its header comment defines `@GLPython.eval`, `@GLPython.turtle` and `@GLPython.visual` and holds two `<script>` blocks (the per-run block and a `run-once` block with the session logic). Courses import it from `main`:
The LiaScript macro that embeds glpython in courses. `README.md` is both the documentation and the macro itself: its header comment defines `@GLPython.eval`, `@GLPython.turtle` and `@GLPython.visual` and holds the session logic in `@onload` (`window.__glpythonMount`, run once per course) plus two `<script>` blocks (the per-run block and a short `run-once` block that only passes the block's arguments to `__glpythonMount`). Keep the code in `@onload`, not in the macro: LiaScript expands a macro into every slide that uses it, and long expansions break slides in Chrome (`Regular expression too large`, when a slide with a choice quiz is opened directly; cause: the column LiaScript's parser computes after a macro expansion). Courses import it from `main`:

```
import: https://raw.githubusercontent.com/glpython-edu/glpython-liascript/main/README.md
Expand Down
69 changes: 43 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,41 +13,26 @@ comment: Embed the glpython editor, execution visualizer, and turtle graphics

@GLPython.visual: @GLPython.embed(@uid,visual,@0,@1,@2,@3,@4,@5,@6,@7)

@GLPython.embed
<script>
(() => {
const session = window.__glpythonSessions?.get("@0")
const source = "@'input"
if (!session) {
const primary = (tag) => (tag || "").toLowerCase().split("-")[0]
const german = [primary(document.documentElement.lang), primary(navigator.language)].find((language) => ["de", "en"].includes(language)) === "de"
const message = german
? "Die glpython-Laufzeit ist noch nicht bereit."
: "The glpython runtime is not ready."
send.lia(`HTML: <pre style='color:red'>${message}</pre>`)
return
}
session.run(source)
window.setTimeout(() => send.lia("LIA: stop"), 0)
})()

"LIA: wait"
</script>

<script run-once>
(() => {
@onload
// The shared code of every glpython block, defined once per course. The
// macro's own run-once block only passes its arguments: LiaScript expands a
// macro into the slide, and the whole code in every block made slides so long
// that Chrome failed on them (LiaScript then builds a regular expression from
// a wrong column; it happened when a slide with several blocks and a choice
// quiz was opened directly). Blocks that run before this code wait in
// window.__glpythonPending.
window.__glpythonMount = (args, send) => {
const protocolVersion = 4
const embedOrigin = "https://glpython-edu.github.io"
const embedUrl = `${embedOrigin}/glpython-editor/index.html`
const sessionId = "@0"
const mode = "@1"
const [sessionId, mode, ...optionArguments] = args
// Optional per block, separated by spaces in any order: "box" or
// "substitution" presets the visualizer's mode, and URLs of shared Python
// files to load as libraries, e.g. @GLPython.eval(box https://…/parser.py).
// LiaScript splits macro arguments at commas, so "features=run,repl" arrives
// as several arguments: they are joined back together here. Arguments that
// weren't given stay literal ("@3") and are left out.
const options = ["@2", "@3", "@4", "@5", "@6", "@7", "@8", "@9"]
const options = optionArguments
.filter((argument) => !/^@[0-9]$/.test(argument))
.join(",")
.split(/\s+/)
Expand Down Expand Up @@ -683,6 +668,38 @@ comment: Embed the glpython editor, execution visualizer, and turtle graphics
window.addEventListener("message", onMessage)
new MutationObserver(attach).observe(document.body, { childList: true, subtree: true })
attach()
}
for (const mount of window.__glpythonPending ?? []) mount()
window.__glpythonPending = []
@end

@GLPython.embed
<script>
(() => {
const session = window.__glpythonSessions?.get("@0")
const source = "@'input"
if (!session) {
const primary = (tag) => (tag || "").toLowerCase().split("-")[0]
const german = [primary(document.documentElement.lang), primary(navigator.language)].find((language) => ["de", "en"].includes(language)) === "de"
const message = german
? "Die glpython-Laufzeit ist noch nicht bereit."
: "The glpython runtime is not ready."
send.lia(`HTML: <pre style='color:red'>${message}</pre>`)
return
}
session.run(source)
window.setTimeout(() => send.lia("LIA: stop"), 0)
})()

"LIA: wait"
</script>

<script run-once>
(() => {
// The code itself is in @onload, see there.
const mount = () => window.__glpythonMount(["@0", "@1", "@2", "@3", "@4", "@5", "@6", "@7", "@8", "@9"], send)
if (window.__glpythonMount) mount()
else (window.__glpythonPending ??= []).push(mount)
})()

"LIA: wait"
Expand Down
7 changes: 6 additions & 1 deletion scripts/check-macro.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ const blocks = [...readme.matchAll(/<script\b[^>]*>([\s\S]*?)<\/script>/g)].map(
code: match[1],
line: readme.slice(0, match.index).split('\n').length,
}))
// The shared code in @onload, which LiaScript runs once per course as written.
// Its first line is the one with @onload, like <script> above.
for (const match of readme.matchAll(/^@onload\n([\s\S]*?)\n@end$/gm)) {
blocks.push({ code: `\n${match[1]}`, line: readme.slice(0, match.index).split('\n').length })
}
if (blocks.length === 0) {
console.error('No <script> blocks found in README.md.')
process.exit(1)
Expand Down Expand Up @@ -43,4 +48,4 @@ for (const [index, block] of blocks.entries()) {
}
}
if (failed > 0) process.exit(1)
console.log(`Macro check passed: ${blocks.length} <script> blocks.`)
console.log(`Macro check passed: ${blocks.length} blocks (<script> and @onload).`)
Loading