Writing Parsley Code
This skill helps you write and debug Parsley code.
When to use this skill
Use this skill when you need to:
- •Create new tests in Parsley code
- •When debugging functionality during development
- •Creating examples of how to use a feature
- •Discussing new features and how they might fit into Parsley/Basil ecosystem
- •Creating workflow docs: designs, specs, plans, bugs, reports
- •Creating documentation that has Parsley code examples
- •Creating or editing: *.pars or *.part files
Parsley vs Basil Context
Important distinction:
- •Parsley is the language - works standalone with
./pars - •Basil is the web framework - adds server context (request, response, sessions, etc.)
Basil-only features (not available in standalone Parsley):
- •
@params- URL/form parameters - •
import @basil/http- request, response, route, method (query removed; use @params) - •
import @basil/auth- session, auth, user - •HTTP-specific functionality (cookies, headers, sessions)
Parsley features work everywhere:
- •Core language (for, if, fn, etc.)
- •
import @std/*modules (math, valid, schema, etc.) - •File I/O, JSON, CSV operations
- •All basic data types and operators
When writing code, consider:
- •For tests/examples: Use
./parsto test pure Parsley code - •For handlers: Basil context available, can use
@basil/*imports - •For documentation: Mark Basil-specific features clearly
Writing code
- •Read
docs/parsley/CHEATSHEET.mdto learn Parsley code especially common mistakes - •Use
parsto test and validate Parsley code snippets
Notes:
- •Parsley code looks like Javascript, but it is expression-based
- •Parsley code looks like React/JSX code, but tags and code co-exist — code is not interpolated inside { } brackets
- •
ifandforare expressions that return values - •functions (
fn) return everything by default, so do not needreturnstatement - •Parsley has rich pseudo-types for dates, times, money with their own literals with
@constructors - •Parsley has a standard library
import @std/… - •By convention, Parsley files use
.parsfile-ending
Common Pitfalls
(see docs/parsley/CHEATSHEET.md for more Major Gotchas (Common Mistakes))
1. Code within tag pairs needs no { and } brackets
parsley
export Table = fn({rows, hidden, ...props}){
<table ...props>
<thead>
<tr>
for (k,_ in rows[0]){
if (k not in hidden)
<th class={"th-"+k}>k.toTitle()</th>
}
</tr>
</thead>
<tbody>
for (row in rows){
<tr>
for (k,v in row){
if (k not in hidden)
<td class={"td-"+k}>v</td>
}
</tr>
}
</tbody>
</table>
}
2. Import syntax uses path literals and destructuring
parsley
// Standard library (works everywhere)
let {floor, ceil} = import @std/math
let {table} = import @std/table
// Basil context (handler-only)
let {request, response, route, method} = import @basil/http // Basil-only
let {session, auth, user} = import @basil/auth // Basil-only
// Local modules (works everywhere)
let {People} = import @~/schema/birthdays.pars
3. for returns an array of values, making it more like map and filter
parsley
let doubled = for (n in [1,2,3]) { n * 2 } // [2, 4, 6]
// Filter pattern - if returns null, element is omitted from result
let evens = for (n in [1,2,3,4]) {
if (n % 2 == 0) { n }
}
// evens => [2, 4]
4. If Parentheses are optional but recommended
parsley
if age >= 18 { "adult" }
if (age >= 18) { "adult" }
let status = if (age >= 18) "adult" else "minor"
5. Literals Use @ (Path, Date, Time, Duration, ...)
parsley
// ✅ CORRECT let path = @./config.json // Relative to current file let rootPath = @~/components/nav // Relative to project root (Basil) // ❌ WRONG let path = "./config.json" // This is just a string // Other literals let url = @https://example.com let date = @2024-11-29 let time = @14:30 let duration = @1d
6. No Arrow Functions - Use fn() { }
parsley
// ❌ WRONG (JavaScript arrow functions)
arr.map(x => x * 2)
// ✅ CORRECT - Use fn() { } syntax
arr.map(fn(x) { x * 2 })
arr.filter(fn(x) { x > 0 })
// Named functions
let double = fn(x) { x * 2 }
7. Strings in HTML Must Be Quoted
parsley
<h3>Welcome to Parsley</h3> // ❌ WRONG - bare text in tags
<h3>"Welcome to Parsley"</h3> // ✅ CORRECT - strings need quotes
<h3>`Welcome to {name}`</h3> // Template literal style also works
8. Tag Attributes Don't Need Quotes for Simple Values
parsley
// ✅ CORRECT - no quotes needed for simple identifiers
<div class=container id=main disabled=true>
<button type=submit disabled={isDisabled}>
// ✅ ALSO CORRECT - quotes when you need them
<div class="user-profile" id={userId}>
<a href="/about">
// Use quotes for:
// - Multi-word values: class="nav item"
// - Values with special chars: onclick="alert('hi')"
// - String literals vs expressions
9. Tag Attributes: Strings vs Expressions
parsley
// Tag attributes have THREE forms:
// 1. Double-quoted strings - literal, no interpolation
<button onclick="alert('hello')">
<a href="/about">
// 2. Single-quoted strings - RAW, for embedding JavaScript
<button onclick='Parts.refresh("editor", {id: 1})'>
// ^ Double quotes and braces stay literal - perfect for JS!
// Use @{} for dynamic values:
<button onclick='Parts.refresh("editor", {id: @{myId}})'>
// 3. Expression braces - Parsley code
<div class={`user-{id}`}> // Template string for dynamic class
<button disabled={!isValid}> // Boolean expression
<img width={width} height={height}>
// ❌ WRONG - interpolation in quoted strings
<div class="user-{id}"> // {id} is literal text
// ✅ CORRECT - use expression braces with template string
<div class={`user-{id}`}>
10. Three String Types: "", '', ``
parsley
// Double quotes: Plain strings (no interpolation)
let msg = "Hello {name}" // literal braces, no interpolation
// Backticks: Template literals (JavaScript style, with interpolation)
let msg = `Hello {name}`
// Single quotes: RAW strings - {braces} stay literal
let js = 'Parts.refresh("editor", {id: 1})'
let regex = '\d+\.\d+' // Backslashes stay literal
// Use @{} for interpolation inside raw strings
let id = 42
let js = 'Parts.refresh("editor", {id: @{id}})' // id interpolated
// Perfect for onclick handlers with dynamic values:
let myId = 5
<button onclick='Parts.refresh("editor", {id: @{myId}, view: "delete"})'/>
// Static JS (no interpolation needed):
<button onclick='Parts.refresh("editor", {id: 1, view: "delete"})'/>
// Escape @ with \@ if you need a literal @
'email: user\@domain.com' // literal @
11. Singleton Tags MUST Self-Close, Paired Tags Can Be Empty
parsley
// Singleton tags (HTML void elements) MUST self-close:
// ❌ WRONG
<br>
<img src="photo.jpg">
<input type="text">
<Part src={@./foo.part}>
// ✅ CORRECT - singleton tags need />
<br/>
<img src="photo.jpg"/>
<input type="text"/>
<Part src={@./foo.part}/>
// Paired tags can be empty (no /> needed):
// ✅ CORRECT
<div></div>
<script></script>
<button></button>
12. Dictionary Iteration Order
parsley
// Dictionaries iterate in insertion order by default
for (k, v in {b: 2, a: 1}) { k } // ["b", "a"]
// To iterate in sorted key order, extract and sort keys first
let d = {b: 2, a: 1}
let sortedKeys = (for (k, _ in d) { k }).sort()
for (k in sortedKeys) { d[k] } // values in key order: [1, 2]
13. Use .length() to find the length of something
parsley
[1,2,3].length() // => 3 "ABC".length() // => 3
14. Use .type() to get the type name of a value (as a string)
parsley
123.type() // => "integer" [1,2,3].type() // => "array" "hello".type() // => "string" @1968-11-21.type() // => "datetime" $5.type() // => "money" @now.type() // => "datetime"
15. Module System: let vs export
parsley
// Export functions/values to make them available to importers
export myFunc = fn(x) { x * 2 }
export myVar = 42
// Each export must be declared separately
// (no "export {a, b}" shorthand)
// let without export is file-local (private)
let private = 123 // Not available to importers
export public = 456 // Available to importers
// Default export
export default = fn(props) { <div>props.text</div> }
16. Standard Library Modules (@std/*)
parsley
// Works everywhere (standalone pars and Basil handlers) import @std/mdDoc // Markdown (mdDoc pseudo-type) import @std/table // Table DSL import @std/math // Math functions import @std/valid // Validation functions import @std/schema // Schema validation import @std/id // ID generation (UUID, ULID, etc) import @std/dev // Dev tools // Requires Basil server (not available in standalone pars) import @std/api // API helpers (redirect, notFound, etc) import @std/html // HTML components (Page, Button, etc.) // ❌ DEPRECATED/REMOVED - don't use these: // @std/markdown - removed, use @std/mdDoc // now() builtin - removed, use @now
17. Basil Framework Imports (@basil/*)
ONLY available in Basil handlers, NOT in standalone pars:
parsley
import @basil/http // request, response, route, method import @basil/auth // session, auth, user
18. Magic Variables
parsley
// Works everywhere @now // Current datetime @now.year // 2026 @now.format() // "January 11, 2026" (human-readable) @env.HOME // Environment variables @env.PATH // Basil-only (ONLY in handlers) @params // URL/form parameters @params.id // Individual parameter
Running pars
To test Parsley code locally:
bash
./pars # Start interactive REPL ./pars script.pars # Execute a script ./pars -pp page.pars # Pretty-print HTML output ./pars -x script.pars # Allow unrestricted script execution ./pars --no-write script.pars # Deny file writes ./pars --restrict-read=/etc script.pars # Deny reads from path
NOTE: that .pars converts values to their default string representation and concatenates them:
- •
[1,2,3]->123 - •
["a","b","c"] - >abc`` - •
s = fn(){"Sam" "Was" "Here"}; s()->SamWasHere
Testing Context
When writing tests and examples:
- •Unit tests: Place in
pkg/parsley/tests/*.parsor*_test.go - •Integration tests: Use Go test files with Parsley evaluation
- •Examples: Create in
examples/*/handlers/*.pars - •Quick validation: Use
./parsto test snippets before documenting - •Handler testing: Run
./basil --devand test in browser
Query DSL Documentation for @schema{}, @query(), etc.
For comprehensive guide to:
- •
@schema{} - •
@query() - •
@insert() - •
@update() - •
@delete() - •
@transaction{}
See the Query DSL SKILL:
- •
.../parsley-query-dsl/SKILL.md
Detailed Documentation
For comprehensive guides, see:
- •Parsley:
docs/parsley/CHEATSHEET.mdanddocs/parsley/reference.md - •Basil API:
docs/basil/reference.md- Full language reference with all operators and builtins
Best practices
- •Consult cheatsheet
- •Run code snippets in
parsbefore using in documentation - •Run all examples to be sure they are valid code