Quick Start
Get a Pascal compiler widget running on your page in under a minute.
Copy the HTML below, adjust the src to point at your
hosted Pas2JS compiler, and open it in a browser.
<!doctype html>
<html>
<head><title>My Pascal Page</title></head>
<body>
<h1>Try Pascal</h1>
<iframe src="index.html?mode=embed"
style="width:100%; height:500px; border:1px solid #ccc;">
</iframe>
</body>
</html>
The ?mode=embed parameter activates embed
mode, which shows a minimal UI with just the code editor and
Compile/Run buttons. The toolbar, sidebar, tabs, and console panel are
hidden.
For the iframe src, use the URL where your Pas2JS
compiler is hosted.
Creating Assignments
Assignments let you create graded exercises. Students see
instructions, write code, and get automatic feedback. There are three
ways to deliver an assignment:
-
URL parameter — host a JSON file and link to it:
index.html?assignment=https://example.com/hello.json
-
Hash parameter — base64-encode the JSON
directly in the URL:
index.html#assignment=eyJ0aXRsZSI6Ikhl...
See the worked example in the API
Reference.
-
postMessage — for embedded widgets, send
assignment fields via
p2js_configure.
Assignment JSON Example
{
"title": "Hello World",
"description": "<p>Write a program that prints <strong>Hello, World!</strong></p>",
"source": "program Hello;\nuses browserconsole;\nbegin\n { your code here }\nend.",
"captureDelay": 100,
"hints": [
"Use the WriteLn procedure to print text to the console.",
"The string must be exactly 'Hello, World!' \u2014 check capitalization and punctuation.",
{"solution": "program Hello;\nuses browserconsole;\nbegin\n WriteLn('Hello, World!');\nend."}
],
"validation": [
{
"target": "console",
"type": "contains",
"value": "Hello, World!",
"message": "Output must contain 'Hello, World!'"
}
]
}
See the Assignment JSON Schema for a
complete field reference.
What the Student Sees
When an assignment is loaded, the student experience changes in
several ways:
-
An assignment panel appears above the editor,
showing the title and description (which can contain HTML).
-
The starter code from the
source field
is loaded into the editor automatically.
Hints
A “Need a hint? (N remaining)” button
reveals hints one at a time:
-
Text hints appear in yellow notification boxes with
guidance on how to proceed.
-
Solution hints appear in green notification boxes
and include a Load Solution button that replaces
the editor content with a working solution.
Arrow buttons navigate between revealed hints with a
“Hint M of N” counter. If the next hint is a solution,
the button text changes to “Show Solution”.
Validation
After running the program, output is automatically checked against
the assignment’s validation rules:
-
A green
“All checks passed!” notification means the solution is
correct.
-
A red
“N of M checks passed” notification lists which checks
failed with their messages.
Results can be dismissed so the student can keep iterating.
The captureDelay field controls how many milliseconds to
wait before running validation after program output is captured. This
is useful when the program modifies the DOM and you need to wait for
rendering.
Writing Tutorials
For multi-exercise tutorial pages, embed multiple compiler widgets
and configure each one via the postMessage API.
Flow
- Embed one or more iframes with
?mode=embed.
- Listen for the
p2js_ready event from each widget.
- Send
p2js_configure to each with its exercise data.
- Optionally listen for
p2js_compiled, p2js_runComplete, and p2js_validationResult.
Complete Example
<!doctype html>
<html>
<head>
<title>Pascal Tutorial</title>
</head>
<body>
<h1>Lesson 1: Hello World</h1>
<p>Write a program that prints a greeting.</p>
<iframe id="exercise-1" src="index.html?mode=embed"
style="width:100%; height:450px; border:1px solid #ccc;"></iframe>
<h1>Lesson 2: Variables</h1>
<p>Declare a variable and print its value.</p>
<iframe id="exercise-2" src="index.html?mode=embed"
style="width:100%; height:450px; border:1px solid #ccc;"></iframe>
<script>
var exercises = [
{
widgetId: 'exercise-1',
config: {
command: 'p2js_configure',
title: 'Hello World',
description: '<p>Print <strong>Hello!</strong></p>',
source: "program Ex1;\nuses browserconsole;\nbegin\n\nend.",
validation: [
{target: 'console', type: 'contains',
value: 'Hello!', message: 'Must print Hello!'}
]
}
},
{
widgetId: 'exercise-2',
config: {
command: 'p2js_configure',
title: 'Variables',
description: '<p>Declare an integer, assign 42, print it.</p>',
source: "program Ex2;\nuses browserconsole;\nvar\n X: Integer;\nbegin\n\nend.",
validation: [
{target: 'console', type: 'contains',
value: '42', message: 'Must print 42'}
]
}
}
];
window.addEventListener('message', function(e) {
if (!e.data || e.data.command !== 'p2js_ready') return;
exercises.forEach(function(ex) {
var frame = document.getElementById(ex.widgetId);
if (frame && frame.contentWindow === e.source) {
frame.contentWindow.postMessage(ex.config, '*');
}
});
});
</script>
</body>
</html>
See tutorial-sample.html
and test-embed.html for
complete working examples.
Custom Examples
The Examples dropdown in the standalone compiler loads
from example-manifest.json by default. You can customize
this file to provide your own sample programs, or use the
?examples=URL query parameter to load an external manifest:
index.html?examples=https://example.com/my-examples.json
In embed mode, the ?examples= parameter is silently ignored
(the Examples dropdown is hidden). This feature is for standalone
(non-embedded) deployments only.
External manifest URLs must support
CORS. If the server does not send appropriate
Access-Control-Allow-Origin headers, the fetch will fail
silently and the Examples dropdown will be hidden.
The file is a JSON array of objects. Each object represents one
example in the dropdown:
| Field | Type | Required | Description |
title | String | Yes | Display name in the dropdown |
description | String | No | Brief description shown below the title |
source | String | No | Inline Pascal source code |
url | String | No | URL to fetch source code from |
source and url are mutually exclusive. If
source is present and non-empty, it is used directly (no
network request). Otherwise url is fetched via HTTP GET.
If both are empty, the entry still appears in the dropdown but
selecting it does nothing.
Example
[
{
"title": "Hello World",
"description": "Minimal program with console output",
"source": "program HelloWorld;\nuses browserconsole;\nbegin\n WriteLn('Hello, World!');\nend."
},
{
"title": "Advanced Example",
"description": "Loaded from an external file",
"url": "https://example.com/advanced.pas"
}
]
See Examples Manifest in the API
Reference for the complete field reference.
Custom Snippets
The Snippets sidebar panel and
Ctrl+J picker load from
snippet-manifest.json by default. You can customize this
file to provide your own code templates, or use the
?snippets=URL query parameter to load an external manifest:
index.html?snippets=https://example.com/my-snippets.json
Snippets in Embed Mode
Unlike examples, snippets work in embed mode. When a
custom snippets manifest is loaded (via ?snippets=URL or
p2js_configure with snippetsUrl), a
Snippets button appears in the embed toolbar. Clicking
it opens a dropdown listing all available snippets. The
Ctrl+J picker also works in embed mode.
<!-- Embed with custom snippets -->
<iframe src="index.html?mode=embed&snippets=https://example.com/snippets.json"
style="width:100%; height:500px; border:1px solid #ccc;">
</iframe>
External manifest URLs must support
CORS. If the server does not send appropriate
Access-Control-Allow-Origin headers, the fetch will fail
silently and the Snippets button will not appear.
| Field | Type | Required | Description |
name | String | Yes | Trigger name — matched by Ctrl+J prefix (case-insensitive) |
category | String | Yes | Group name for categorized view |
description | String | Yes | Help text shown in the snippet list |
source | String | Yes | Pascal code to insert; | marks cursor position |
Cursor Marker
Place a | character in the source where you
want the cursor to land after insertion. The | is removed
from the inserted code. If no | is present, the cursor
stays at the end.
Ctrl+J Matching
When the user types a prefix in the editor and presses
Ctrl+J (Cmd+J on Mac),
snippets whose name starts with that prefix
(case-insensitive) are matched:
- One match — inserts directly.
- Multiple matches — shows a picker popup.
- No matches — nothing happens.
Example
[
{
"name": "for",
"category": "Control Flow",
"description": "For loop with counter",
"source": "for I := 0 to 9 do\nbegin\n |\nend;"
},
{
"name": "writeln",
"category": "Console I/O",
"description": "WriteLn to console",
"source": "WriteLn('|');"
}
]
See Snippet Manifest in the API
Reference for the complete field reference.
URL Parameters
These parameters control the compiler’s behavior when loaded
via URL.
| Parameter | Location | Value | Description |
mode |
Query string |
embed |
Enables embed mode — minimal UI with editor and Compile/Run buttons only |
assignment |
Query string |
URL |
Loads assignment JSON from the given URL |
examples |
Query string |
URL |
Loads example manifest from the given URL instead of the default. Silently ignored in embed mode. |
snippets |
Query string |
URL |
Loads snippet manifest from the given URL instead of the default. Works in both normal and embed mode. |
storage |
Query string |
indexeddb |
Forces IndexedDB storage instead of localStorage |
#assignment= |
Hash |
Base64 string |
Base64-encoded assignment JSON; used only if the assignment query parameter is absent |
#code= |
Hash |
URI-encoded string |
Pre-fills the editor with shared code (generated by the Share button, encoded via encodeURIComponent) |
The #assignment= and #code= hash
parameters are mutually exclusive — the URL hash can only
start with one prefix. If you need both an assignment and
pre-filled code, use the ?assignment=URL query
parameter for the assignment and send the source via
p2js_configure after the widget is ready.
Worked Example: Base64 Assignment URL
Start with a minimal assignment JSON:
{
"title": "Quick Test",
"source": "program T;\nbegin\nend."
}
Encode it in JavaScript:
var json = JSON.stringify({
title: 'Quick Test',
source: 'program T;\nbegin\nend.'
});
var url = 'index.html#assignment=' + btoa(json);
// Result: index.html#assignment=eyJ0aXRsZSI6Ik...
For larger assignments, prefer the ?assignment=URL
approach with a hosted JSON file.
In JSON strings, use \n for newlines (not
\\n, which is a literal backslash followed by
n).
postMessage Commands
Send commands from your parent page to the embedded compiler widget.
All commands are sent via
iframe.contentWindow.postMessage(payload, '*').
Important: Always wait for the
p2js_ready event
before sending any commands. Messages sent before the widget is
ready are silently ignored.
| Command | Required Fields | Description |
p2js_configure |
(see below) |
Configure JIT settings, load an assignment, and/or set a custom snippets URL |
p2js_setSource |
source: String |
Replace editor content |
p2js_reset |
(none) |
Reset editor to last loaded source |
p2js_setTheme |
theme: 'light' | 'dark' |
Switch editor theme |
p2js_runValidation |
(none) |
Trigger validation of captured program output |
p2js_configure — JIT Configuration Only
widget.contentWindow.postMessage({
command: 'p2js_configure',
jitCompile: true,
jitDebounce: 5
}, '*');
jitCompile enables background syntax checking as the
student types. jitDebounce is the delay in seconds after
the last keystroke (minimum 3). If omitted, it defaults to 3.
Values below 3 are silently ignored.
p2js_configure — Assignment Loading Only
widget.contentWindow.postMessage({
command: 'p2js_configure',
title: 'Hello World',
description: '<p>Print a greeting.</p>',
source: "program Hello;\nuses browserconsole;\nbegin\n\nend.",
hints: ['Use WriteLn()'],
validation: [{
target: 'console', type: 'contains',
value: 'Hello', message: 'Must say Hello'
}],
captureDelay: 100
}, '*');
p2js_configure — Combined
widget.contentWindow.postMessage({
command: 'p2js_configure',
jitCompile: true,
jitDebounce: 3,
title: 'Exercise 1',
description: '<p>...</p>',
source: '...'
}, '*');
p2js_configure — Custom Snippets
widget.contentWindow.postMessage({
command: 'p2js_configure',
snippetsUrl: 'https://example.com/my-snippets.json'
}, '*');
snippetsUrl dynamically loads a snippet manifest from the
given URL. The Snippets button in the embed toolbar is disabled during
the fetch and re-enabled on success. If the URL is invalid or the
server does not support CORS, the button remains disabled. Sending a
new snippetsUrl replaces any previously loaded snippets.
Other Commands
// Set source
widget.contentWindow.postMessage({
command: 'p2js_setSource',
source: "program Test;\nbegin\nend."
}, '*');
// Reset
widget.contentWindow.postMessage({command: 'p2js_reset'}, '*');
// Set theme
widget.contentWindow.postMessage({
command: 'p2js_setTheme', theme: 'dark'
}, '*');
// Run validation
widget.contentWindow.postMessage({
command: 'p2js_runValidation'
}, '*');
Security & Notes
In embed mode, the widget only accepts messages whose
source matches window.parent (i.e. the
embedding page). Messages from other origins are ignored.
Outbound events (p2js_ready, p2js_compiled,
etc.) are only sent when the widget is running in embed mode.
p2js_programOutput is an internal command used by the
run iframe to deliver captured program output back to the widget for
validation. It is not intended for direct use by the embedding page.
postMessage Events
The widget sends events to the parent page via
window.parent.postMessage(). Listen for them with
window.addEventListener('message', ...).
| Event | Fields | Fired When |
p2js_ready |
version: Integer |
Widget finished loading — safe to send commands |
p2js_compiled |
success: Boolean |
Compilation completed (no error details included) |
p2js_runComplete |
consoleOutput: Array, html: String |
Program execution finished |
p2js_validationResult |
results: Array |
Validation rules have been checked |
Field Details
consoleOutput — array of objects,
each with:
stream: 'log', 'warn', or 'error' (corresponding to console.log, console.warn, console.error)
text: the output line text
html — string containing the
document.body.innerHTML snapshot from the run iframe.
This is the HTML that validation rules with
target: "html" are checked against.
results — array of objects, each
with:
Passed: Boolean — whether the check passed
Rule: object with Message, Pattern, Target, and RuleType fields
Complete Listener Example
window.addEventListener('message', function(e) {
if (!e.data || !e.data.command) return;
switch (e.data.command) {
case 'p2js_ready':
console.log('Widget ready, protocol v' + e.data.version);
// Now safe to send commands
break;
case 'p2js_compiled':
console.log('Compiled: ' + (e.data.success ? 'OK' : 'FAILED'));
break;
case 'p2js_runComplete':
e.data.consoleOutput.forEach(function(entry) {
console.log('[' + entry.stream + '] ' + entry.text);
});
if (e.data.html) {
console.log('HTML length: ' + e.data.html.length);
}
break;
case 'p2js_validationResult':
e.data.results.forEach(function(r) {
console.log((r.Passed ? 'PASS' : 'FAIL') + ': ' + r.Rule.Message);
});
break;
}
});
Assignment JSON Schema
Complete reference for the assignment JSON format used with
?assignment=URL,
#assignment=base64, or the
p2js_configure postMessage command.
Top-Level Fields
| Field | Type | Required | Description |
title | String | No | Assignment title displayed above the editor |
description | String | No | Instructions shown to the student (HTML allowed) |
source | String | No | Starter code loaded into the editor |
captureDelay | Integer | No | Milliseconds to wait before running validation (default: 0) |
hints | Array | No | Array of hints — strings or solution objects |
validation | Array | No | Array of validation rules |
Hint Entries
- String:
"Use WriteLn to print" — displayed as a yellow notification.
- Solution object:
{"solution": "program..."} — displayed as a green notification with a “Load Solution” button.
Validation Rules
| Field | Type | Required | Description |
target | String | No | "console" (default) or "html" |
type | String | No | "contains" (default) or "match" |
value | String | No | Substring to search for (used when type is "contains") |
pattern | String | No | Regular expression pattern (used when type is "match") |
message | String | No | Feedback text shown in results |
Console text is assembled from all output entries. Non-breaking spaces
are normalized to regular spaces before matching.
Examples Manifest
The example-manifest.json file defines the sample
programs shown in the Examples dropdown.
| Field | Type | Required | Description |
title | String | Yes | Display name |
description | String | No | Shown below title in dropdown |
source | String | No | Inline Pascal code |
url | String | No | URL to fetch code from |
source takes priority over url. If both are
empty, the entry still appears in the dropdown but selecting it does
nothing.
Snippet Manifest
The snippet-manifest.json file defines code templates
for the Snippets sidebar and Ctrl+J picker.
| Field | Type | Required | Description |
name | String | Yes | Trigger name for Ctrl+J matching |
category | String | Yes | Group name |
description | String | Yes | Help text |
source | String | Yes | Code to insert; | marks cursor position |
Matching is case-insensitive prefix-based. Snippets are displayed
grouped by category (default) or flat alphabetically.