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
39 changes: 26 additions & 13 deletions _includes/runners/code.html
Original file line number Diff line number Diff line change
Expand Up @@ -60,21 +60,25 @@
// AP CSP Pseudocode syntax highlighting mode for CodeMirror 5
if (typeof CodeMirror !== 'undefined' && !CodeMirror.modes['pseudocode']) {
CodeMirror.defineMode('pseudocode', function() {
const KW = new Set(['IF','ELSE','REPEAT','TIMES','UNTIL','FOR','EACH','IN','PROCEDURE','RETURN','AND','OR','NOT','MOD']);
const BI = new Set(['DISPLAY','INPUT','APPEND','INSERT','REMOVE','LENGTH']);
const BOOL= new Set(['TRUE','FALSE']);
const KW = new Set(['IF','ELSE','REPEAT','TIMES','UNTIL','FOR','EACH','IN','PROCEDURE','RETURN','AND','OR','NOT','MOD']);
const BI = new Set(['DISPLAY','INPUT','APPEND','INSERT','REMOVE','LENGTH']);
const BOOL = new Set(['TRUE','FALSE']);
// Robot commands folded into existing token types for colour consistency
const BI_EXTRA = new Set(['MOVE_FORWARD','ROTATE_LEFT','ROTATE_RIGHT','CAN_MOVE','RENDER']);
const ROBOT_BLUE = new Set(['FROM','TILEMAPS','IMPORT','SPAWN']);
const ROBOT_YELLOW = new Set(['CHARACTER','GOAL']);
return {
token: function(stream) {
if (stream.eatSpace()) return null;

// Comments
if (stream.match('//')) { stream.skipToEnd(); return 'comment'; }

// Strings — handles curly and straight quotes
// Strings — handles curly (“ ”) and straight quotes
const q = stream.peek();
if (q === '"' || q === '' || q === '' || q === "'") {
if (q === '"' || q === '\u201c' || q === '\u201d' || q === "'") {
stream.next();
const close = (q === '') ? '' : q;
const close = (q === '\u201c') ? '\u201d' : q;
while (!stream.eol() && stream.peek() !== close) stream.next();
if (!stream.eol()) stream.next();
return 'string';
Expand All @@ -83,12 +87,14 @@
// Numbers
if (stream.match(/^-?\d+(\.\d*)?/)) return 'number';

// Words: keywords / builtins / booleans / identifiers
// Words: robot commands take priority, then pseudocode keywords
if (stream.match(/^[a-zA-Z_][a-zA-Z0-9_]*/)) {
const w = stream.current().toUpperCase();
if (KW.has(w)) return 'keyword';
if (BI.has(w)) return 'builtin';
if (BOOL.has(w)) return 'atom';
const w = stream.current();
if (KW.has(w)) return 'keyword';
if (BI.has(w) || BI_EXTRA.has(w)) return 'builtin';
if (BOOL.has(w)) return 'atom';
if (ROBOT_BLUE.has(w)) return 'attribute';
if (ROBOT_YELLOW.has(w)) return 'property';
return 'variable';
}

Expand All @@ -108,7 +114,7 @@
</script>

<script type="module">
import { BaseRunner, CodeExecutor, PseudocodeExecutor } from '{{site.baseurl}}/assets/js/pages/runners/index.js';
import { BaseRunner, CodeExecutor, PseudocodeExecutor, RobotExecutor } from '{{site.baseurl}}/assets/js/pages/runners/index.js';
import { pythonURI, javaURI, fetchOptions } from '{{site.baseurl}}/assets/js/api/config.js';

(function() {
Expand Down Expand Up @@ -154,6 +160,11 @@
execTimeElement: execTimeSpan,
});

const robotExecutor = new RobotExecutor({
outputElement: outputDiv,
execTimeElement: execTimeSpan,
});

// Set initial language selection
if (initialLang) {
langSelect.value = initialLang;
Expand Down Expand Up @@ -209,7 +220,9 @@
// Run button logic
function runCode() {
if (langSelect.value === "pseudocode") {
return pseudoExecutor.run(editor.getValue());
const src = editor.getValue();
if (RobotExecutor.detect(src)) return robotExecutor.run(src);
return pseudoExecutor.run(src);
}
return codeExecutor.run();
}
Expand Down
31 changes: 29 additions & 2 deletions _notebooks/CSP/AP-prep/2026-05-12-AP-CSP_Pseudocode_runner.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,19 @@
"cell_type": "markdown",
"id": "9d9630e1",
"metadata": {},
"source": "---\nlayout: post\ntitle: AP CSP Pseudocode Runner\ndescription: A tool to run AP CSP Pseudocode.\nbreadcrumb: True\ncourses: { csp: {week: 30} }\ncomments: false\ntype: ccc\ncodemirror: true\npermalink: /csp/pseudocode-runner/\n---"
"source": [
"---\n",
"layout: post\n",
"title: AP CSP Pseudocode Runner\n",
"description: A tool to run AP CSP Pseudocode.\n",
"breadcrumb: True\n",
"courses: { csp: {week: 30} }\n",
"comments: false\n",
"type: ccc\n",
"codemirror: true\n",
"permalink: /csp/pseudocode-runner/\n",
"---"
]
},
{
"cell_type": "markdown",
Expand All @@ -17,11 +29,26 @@
"\n"
]
},
{
"cell_type": "markdown",
"id": "f63ef927",
"metadata": {},
"source": "<details>\n<summary><strong>Pseudocode Keyword Reference</strong> (click to expand)</summary>\n\nAll keywords are **case-sensitive and must be written in ALL CAPS**. Type `<--` for `\u2190`, `!=` for `\u2260`, `>=` for `\u2265`, `<=` for `\u2264`.\n\n<details>\n<summary><strong>Variables &amp; Assignment</strong></summary>\n\nUse `\u2190` to assign a value. Variables are created on first assignment. Variable names are case-sensitive.\n\n```\nx \u2190 5\nname \u2190 \"Alice\"\nflag \u2190 TRUE\n```\n\n> Types: numbers, strings (`\"double quotes\"`), `TRUE` / `FALSE`\n\n</details>\n\n<details>\n<summary><strong>DISPLAY &amp; INPUT</strong></summary>\n\n| Command | What it does |\n|---|---|\n| `DISPLAY(value)` | Prints a value to the output box |\n| `INPUT(\"prompt\")` | Prompts the user; auto-converts to number/boolean if possible |\n\n```\nDISPLAY(\"Hello!\")\nage \u2190 INPUT(\"Enter your age: \")\nDISPLAY(age)\n```\n\n</details>\n\n<details>\n<summary><strong>Operators</strong></summary>\n\n**Arithmetic:** `+` `-` `*` `/` `MOD`\n> `+` also concatenates strings\n\n**Comparison:** `=` `\u2260` `<` `>` `\u2264` `\u2265`\n\n**Logical:** `AND` `OR` `NOT`\n\n```\nDISPLAY(10 MOD 3) // 1\nDISPLAY(5 = 5) // TRUE\nDISPLAY(TRUE AND FALSE) // FALSE\nDISPLAY(NOT FALSE) // TRUE\n```\n\n</details>\n\n<details>\n<summary><strong>IF / ELSE</strong></summary>\n\n```\nIF (condition) {\n // runs if TRUE\n} ELSE {\n // runs if FALSE (ELSE block is optional)\n}\n```\n\n```\nx \u2190 7\nIF (x > 5) {\n DISPLAY(\"big\")\n} ELSE {\n DISPLAY(\"small\")\n}\n```\n\n</details>\n\n<details>\n<summary><strong>Loops</strong></summary>\n\n**REPEAT n TIMES**\n```\nREPEAT 5 TIMES {\n DISPLAY(\"hello\")\n}\n```\n\n**REPEAT UNTIL** \u2014 loops until condition is `TRUE`\n```\nx \u2190 0\nREPEAT UNTIL (x = 5) {\n x \u2190 x + 1\n}\n```\n\n**FOR EACH IN** \u2014 iterates over a list\n```\nFOR EACH n IN [1, 2, 3] {\n DISPLAY(n * 2)\n}\n```\n\n</details>\n\n<details>\n<summary><strong>Lists</strong></summary>\n\n**1-based indexing** \u2014 first element is at index `1`.\n\n| Command | What it does |\n|---|---|\n| `list[i]` | Get element at index `i` |\n| `list[i] \u2190 value` | Set element at index `i` |\n| `APPEND(list, value)` | Add to end |\n| `INSERT(list, i, value)` | Insert at index `i` |\n| `REMOVE(list, i)` | Remove at index `i` |\n| `LENGTH(list)` | Number of elements |\n\n```\nnums \u2190 [10, 20, 30]\nAPPEND(nums, 40)\nDISPLAY(LENGTH(nums)) // 4\nDISPLAY(nums[1]) // 10\n```\n\n</details>\n\n<details>\n<summary><strong>PROCEDURE &amp; RETURN</strong></summary>\n\n```\nPROCEDURE add(a, b) {\n RETURN a + b\n}\nDISPLAY(add(3, 4)) // 7\n```\n\nProcedures without `RETURN` can still be called for side effects:\n```\nPROCEDURE greet(name) {\n DISPLAY(\"Hello, \" + name)\n}\ngreet(\"Alice\")\n```\n\n</details>\n\n<details>\n<summary><strong>Robot / Grid Commands</strong></summary>\n\nThese activate the visual 7x7 grid. Write setup lines first, then movement logic below.\n\n**Setup** (stripped before execution)\n\n| Command | What it does |\n|---|---|\n| `FROM TILEMAPS IMPORT N` | Load preset map N (1-25) |\n| `SPAWN CHARACTER AT (col, row)` | Place robot at col, row (1-based), facing up |\n| `SPAWN GOAL AT (col, row)` | Place a goal star |\n\n**Movement** (called at runtime)\n\n| Command | What it does |\n|---|---|\n| `MOVE_FORWARD()` | Move one cell forward |\n| `ROTATE_LEFT()` | Turn 90 degrees counter-clockwise |\n| `ROTATE_RIGHT()` | Turn 90 degrees clockwise |\n| `CAN_MOVE(\"direction\")` | TRUE if passable - \"forward\" \"backward\" \"left\" \"right\" |\n\n```\nFROM TILEMAPS IMPORT 12\nSPAWN CHARACTER AT (1,1)\nSPAWN GOAL AT (7,7)\n\nREPEAT UNTIL (CAN_MOVE(\"forward\") = FALSE) {\n MOVE_FORWARD()\n}\nROTATE_RIGHT()\nMOVE_FORWARD()\n```\n\n</details>\n\n<details>\n<summary><strong>Custom Tilemaps (RENDER)</strong></summary>\n\nInstead of loading a preset with `FROM TILEMAPS IMPORT`, define your own 7x7 grid as a variable and pass it to `RENDER()`.\n\n**Rules:**\n- The matrix must be a **list of 7 lists**, each with exactly **7 values**\n- `0` = open cell, `1` = wall\n- The entire assignment must be on **one line**\n- Call `RENDER(variable)` before any movement commands\n\n```\nmyMap \u2190 [[0,0,0,0,0,0,0],[0,1,1,0,0,1,0],[0,0,1,0,0,0,0],[0,0,0,0,1,0,0],[0,1,0,0,1,0,0],[0,1,0,0,0,0,0],[0,0,0,0,0,0,0]]\n\nSPAWN CHARACTER AT (1,1)\nSPAWN GOAL AT (7,7)\n\nRENDER(myMap)\nMOVE_FORWARD()\nROTATE_RIGHT()\nMOVE_FORWARD()\n```\n\n> You can combine both: use `FROM TILEMAPS IMPORT N` to load a preset, then call `RENDER(newMap)` later to swap the grid mid-execution.\n\n</details>\n\n</details>"
},
{
"cell_type": "markdown",
"id": "513a92fa",
"metadata": {},
"source": "<details>\n<summary><strong>Tilemap Directory</strong> \u2014 IDs 1\u201325 (click to expand)</summary>\n\nUse these IDs with `from tilemaps import N`. All maps are 7\u00d77, 0 = open, 1 = wall.\n\n| ID | Name | Description |\n|---|---|---|\n| 1 | Scattered walls | Open grid with a few wall clusters \u2014 good starter map |\n| 2 | Column gaps | Vertical wall pairs with clear corridors between them |\n| 3 | Double gate | Two horizontal barriers each with a single opening in the centre |\n| 4 | Staggered columns | Alternating column walls; open row in the middle connects them |\n| 5 | Ring | Solid 5\u00d75 wall ring with an open interior and outer path |\n| 6 | Diamond obstacles | Symmetric diagonal obstacles around a centre wall |\n| 7 | Cross barrier | Plus-sign wall with a single gap on each axis |\n| 8 | Dot grid | Regular 2\u00d72 wall dots across the grid \u2014 many equal paths |\n| 9 | S-curve | Single-width path: left \u2192 down \u2192 right \u2192 down |\n| 10 | T-junction | Open top row splits into two shafts that merge to a single exit |\n| 11 | Two rooms | Wall divides the grid; one corridor connects the rooms at row 4 |\n| 12 | Zigzag slalom | Barriers alternate sides \u2014 forces full end-to-end traversal |\n| 13 | Diagonal obstacles | Diagonal line of single-cell walls; open space on both sides |\n| 14 | Slalom barriers | Two staggered barriers each with one opening \u2014 weave through |\n| 15 | Winding river | Narrow single-width path snaking from top-left to bottom-right |\n| 16 | Cross open | Plus-sign open area in the centre; corners are walled off |\n| 17 | Perimeter loop | Outer ring is fully open; solid 5\u00d75 block in the interior |\n| 18 | Grid maze | Open intersections with wall segments \u2014 multiple valid routes |\n| 19 | Columns | Tall wall columns with one open row in the middle |\n| 20 | Honeycomb | Staggered single-cell obstacles; lots of routing options |\n| 21 | Staircase | 2-wide diagonal path from top-left to bottom-right |\n| 22 | Scattered obstacles | Mostly open with light random single-cell walls |\n| 23 | L-corridor | Left column + bottom row form an L-shaped path |\n| 24 | Double loop | Four 2\u00d72 wall blocks leave open rings and a centre cross |\n| 25 | Funnel | Wide open top narrows to a single-column corridor at the bottom |\n\n</details>"
},
{
"cell_type": "markdown",
"id": "16a6ead3",
"metadata": {},
"source": "{% capture csp_default %}DISPLAY(\"HELLO WORLD!\"){% endcapture %}\n{% include runners/code.html runner_id=\"pseudocode-main\" language=\"pseudocode\" code=csp_default %}"
"source": [
"{% capture csp_default %}DISPLAY(\"HELLO WORLD!\"){% endcapture %}\n",
"{% include runners/code.html runner_id=\"pseudocode-main\" language=\"pseudocode\" code=csp_default %}"
]
}
],
"metadata": {
Expand Down
5 changes: 5 additions & 0 deletions _sass/open-coding/forms/code-runner.scss
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,9 @@
.copyOutputBtn {
@include icon-button(38px, 0);
}

// Override Darcula's green comment colour with gray
.cm-comment {
color: #7f849c !important;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ function tokenize(src) {
}
if (/[a-zA-Z_]/.test(c)) {
let s = ''; while (i < n && /[a-zA-Z0-9_]/.test(src[i])) s += src[i++];
const u = s.toUpperCase();
out.push({t: KW.has(u) ? 'K' : 'I', v: KW.has(u) ? u : s}); continue;
out.push({t: KW.has(s) ? 'K' : 'I', v: s}); continue;
}
const sl = src.slice(i);
if (sl.slice(0,3) === '<--') { out.push({t:'O',v:'←'}); i+=3; continue; }
Expand Down Expand Up @@ -250,7 +249,7 @@ function gen(ast) {
return stmts(ast);
}

function compile(src) {
export function compile(src) {
return gen(new Parser(tokenize(src)).parse());
}

Expand Down
Loading