GlyphLang Documentation
GlyphLang™ is an AI-first backend language designed for LLM code generation. Minimal ceremony—just routes and types. Uses 23% fewer tokens than FastAPI, 57% fewer than Java.
Why GlyphLang?
- AI-Optimized: Minimal ceremony means 23% fewer tokens than FastAPI, 57% fewer than Java
- Blazing Fast: 867 nanosecond compilation, 2.93 ns/op execution
- Secure by Default: Built-in SQL injection and XSS detection
- 100% Go: Single binary deployment, no runtime dependencies
Quick Reference
| Symbol | Purpose | Example |
|---|---|---|
@ |
Route/endpoint declaration | @ GET /api/users |
: |
Type definition | : User { id: int!, name: str! } |
$ |
Variable assignment | $ user = db.users.get(id) |
% |
Dependency injection | % db: Database |
> |
Return statement | > {message: "Hello"} |
+ |
Middleware/modifier | + auth(jwt) |
? |
Validation | ? validate_email(input.email) |
! |
CLI command | ! migrate "migrate database" |
* |
Cron task | * "0 0 * * *" cleanup { } |
~ |
Event handler | ~ "user.created" on_create { } |
& |
Queue worker | & "emails" email_worker { } |
Installation
GlyphLang is built with 100% pure Go and compiles to a single binary with no runtime dependencies.
From Source
# Clone the repository
git clone https://github.com/GlyphLang/GlyphLang.git
cd glyph
# Build the compiler
make build
# Verify installation
./glyph --version
Using Go Install
go install github.com/GlyphLang/GlyphLang@latest
System Requirements
- Go 1.24 or later (for building from source)
- Any OS: Windows, macOS, Linux
- No runtime dependencies
Editor Support
GlyphLang provides syntax highlighting and LSP support for popular editors.
Neovim
Full Neovim support is available with syntax highlighting, LSP integration, and Tree-sitter queries.
Manual Installation
# Copy syntax files to your Neovim runtime
cp -r editors/neovim/syntax ~/.config/nvim/
cp -r editors/neovim/ftdetect ~/.config/nvim/
cp -r editors/neovim/ftplugin ~/.config/nvim/
cp -r editors/neovim/queries ~/.config/nvim/
Using lazy.nvim
{
"GlyphLang/GlyphLang",
config = function()
vim.opt.runtimepath:append(vim.fn.stdpath("data") .. "/lazy/GlyphLang/editors/neovim")
dofile(vim.fn.stdpath("data") .. "/lazy/GlyphLang/editors/neovim/lsp.lua").setup()
end,
}
LSP Configuration
-- Add to your Neovim configuration
require('glyph-lsp').setup({
on_attach = function(client, bufnr)
-- Your custom on_attach configuration
end,
})
Features
- Syntax Highlighting: Full support for all GlyphLang symbols and constructs
- LSP Integration: Go to definition, find references, hover documentation
- Tree-sitter: Advanced highlighting and indentation queries
- Code Actions: Rename symbol, format document
Requirements
- Neovim 0.8.0 or later
- nvim-lspconfig for LSP support
glyphCLI installed and in PATH
Hello World
Create your first GlyphLang application in three simple steps.
Step 1: Create a file
Create a file named hello.glyph:
# Simple greeting endpoint
@ GET /hello/:name {
$ greeting = "Hello, " + name + "!"
> {message: greeting}
}
Step 2: Run the server
./glyph dev hello.glyph
Step 3: Test it
curl http://localhost:3000/hello/World
# Output: {"message": "Hello, World!"}
That's it!
You've just created a fully functional HTTP endpoint. The route captures the :name path parameter and returns a JSON greeting.
Symbol Syntax
GlyphLang uses symbols instead of keywords to eliminate ambiguity for AI code generation. Each symbol has a single, clear purpose.
@ - Route Declaration
The @ symbol declares HTTP routes or WebSocket endpoints. The HTTP method follows the @ symbol.
# Basic route (GET is most common)
@ GET /api/users {
> {users: []}
}
# POST route
@ POST /api/users {
> {created: true}
}
# Route with return type
@ GET /api/users/:id -> User | Error {
> {id: id}
}
# WebSocket route
@ ws /chat { ... }
Note: The route keyword can optionally be used after @ for explicit syntax (e.g., @route /api/users [GET] { }), but the shorthand @ GET /path { } is preferred.
: - Type Definition
The : symbol defines custom types (schemas/models).
# Define a User type
: User {
id: int!
name: str!
email: str!
age: int?
tags: List[str]
created_at: timestamp
}
$ - Variable Assignment
The $ symbol assigns values to variables.
# Simple assignment
$ name = "Alice"
# From expression
$ total = price * quantity
# From function call
$ user = db.users.get(id)
# From object literal
$ config = {timeout: 30, retries: 3}
% - Dependency Injection
The % symbol injects services into routes.
@ GET /api/users {
% db: Database # Inject database service
% cache: Cache # Inject cache service
$ users = db.users.all()
> {users: users}
}
> - Return Statement
The > symbol returns values from routes or functions. The return keyword can also be used as an alternative.
# Return object
> {message: "Success", data: users}
# Return variable
> user
# Return array
> [1, 2, 3, 4, 5]
# Alternative: using 'return' keyword
return {message: "Success", data: users}
return user
+ - Middleware
The + symbol applies middleware to routes.
@ GET /api/admin {
+ auth(jwt) # Require JWT authentication
+ auth(jwt, role: admin) # Require admin role
+ ratelimit(100/min) # Rate limit to 100 req/min
> {status: "admin area"}
}
? - Validation
The ? symbol performs input validation.
@ POST /api/users {
? validate_email(input.email)
? validate_length(input.name, 1, 100)
? validate_range(input.age, 0, 150)
$ user = db.users.create(input)
> {user: user}
}
# or // - Comments
Comments are ignored by the compiler.
# This is a comment
// This is also a comment
@ GET /api/users # Inline comment {
$ users = db.users.all()
> {users: users}
}
! - CLI Command
The ! symbol defines command-line commands.
# Define a CLI command
! migrate "Run database migrations" {
% db: Database
$ result = db.migrate()
> {status: "complete", migrations: result}
}
# Command with description
! seed "Seed the database with test data" {
% db: Database
$ users = db.users.createMany([...])
> {created: length(users)}
}
* - Cron Task
The * symbol schedules recurring tasks using cron expressions.
# Run daily at midnight
* "0 0 * * *" cleanup {
% db: Database
$ deleted = db.sessions.deleteExpired()
> {deleted: deleted}
}
# Run every hour
* "0 * * * *" health_check {
$ status = checkServices()
if status != "ok" {
sendAlert("Service degraded")
}
}
~ - Event Handler
The ~ symbol defines handlers for application events.
# Handle user creation events
~ "user.created" on_user_created {
% email: EmailService
$ user = input.user
email.send({to: user.email, subject: "Welcome!"})
}
# Wildcard event matching
~ "user.*" on_any_user_event {
% db: Database
$ event_type = input.event
db.event_logs.create({type: event_type, timestamp: now()})
}
& - Queue Worker
The & symbol defines background job processors.
# Email queue worker
& "emails" email_worker {
% email: EmailService
$ job = input.job
email.send({to: job.to, subject: job.subject})
> {processed: true}
}
# Image processing queue
& "images" image_processor {
% storage: StorageService
$ processed = processImage(input.job.image)
storage.save(input.job.output_path, processed)
}
Data Types
GlyphLang has a rich type system with primitive types, collection types, and custom type definitions.
Primitive Types
| Type | Description | Example |
|---|---|---|
int |
Integer numbers | 42, -17, 0 |
float |
Floating-point numbers | 3.14, -0.5, 2.0 |
str / string |
Text strings | "Hello", 'World' |
bool |
Boolean values | true, false |
timestamp |
Unix timestamp | 1702656000 |
null |
Null/empty value | null |
any |
Any type (dynamic) | Any value |
Collection Types
# Arrays/Lists
$ numbers: List[int] = [1, 2, 3, 4, 5]
$ names: List[str] = ["Alice", "Bob", "Charlie"]
$ users: List[User] = db.users.all()
# Sets (unique values)
$ tags: Set[str] = {"golang", "rust", "python"}
# Maps (key-value pairs)
$ config: Map[str, int] = {"timeout": 30, "retries": 3}
Type Modifiers
: User {
id: int! # Required (non-nullable) - must have a value
name: str! # Required
email: str # Optional (nullable by default)
age: int? # Explicitly optional
bio: str? # Optional with ? suffix
}
Union Types
# Route can return User or Error
@ GET /api/users/:id -> User | Error {
% db: Database
$ user = db.users.get(id)
if user == null {
> {error: "User not found", code: 404}
}
> user
}
Type Definitions
Define custom types to model your data structures. Types are used for validation, documentation, and database schemas.
Basic Type Definition
: User {
id: int!
name: str!
email: str!
created_at: timestamp
}
Nested Types
: Address {
street: str!
city: str!
country: str!
zip: str
}
: Customer {
id: int!
name: str!
email: str!
address: Address
shipping_address: Address?
}
Types with Arrays
: BlogPost {
id: int!
title: str!
content: str!
tags: List[str]
comments: List[Comment]
author: User!
}
: Comment {
id: int!
text: str!
author: str!
created_at: timestamp
}
Types for API Responses
: ApiResponse {
success: bool!
data: any
error: str?
timestamp: timestamp
}
: PaginatedResponse {
items: List[any]
total: int!
page: int!
per_page: int!
has_more: bool!
}
Variables
Variables are declared using the $ symbol and can hold any type of value.
Variable Declaration
# String assignment
$ name = "Alice"
# Number assignment
$ age = 30
$ price = 19.99
# Boolean assignment
$ is_active = true
# Array assignment
$ tags = ["golang", "rust", "python"]
# Object assignment
$ user = {
name: "Alice",
age: 30,
email: "alice@example.com"
}
Variable Reassignment
Use bare assignment (without $) to update existing variables:
$ counter = 0 # Declaration with $
counter = counter + 1 # Reassignment without $ - counter is now 1
counter = counter * 2 # Reassignment without $ - counter is now 2
Note: Using $ to redeclare a variable in the same scope will result in an error. Use bare assignment for updates.
Variable Scope
Variables are scoped to their containing block (route, function, or control structure).
@ GET /example {
$ outer = "I'm outside"
if true {
$ inner = "I'm inside"
outer = "Modified" # Reassign outer scope variable (no $)
}
# inner is not accessible here
> {value: outer} # Returns "Modified"
}
Alternative Syntax
The let keyword can be used as an alternative to $ for variable declaration.
# These are equivalent:
$ name = "Alice"
let name = "Alice"
# Both work in any context
@ GET /api/example {
let count = 0
let items = ["a", "b", "c"]
> {count: count, items: items}
}
Special Variables
@ POST /api/users/:id {
# input - contains request body/query params
$ name = input.name
$ email = input.email
# Path parameters are available directly
$ user_id = id # From :id in path
> {id: user_id, name: name}
}
Operators
Arithmetic Operators
| Operator | Description | Example |
|---|---|---|
+ |
Addition / String concatenation | 5 + 3, "Hello" + " World" |
- |
Subtraction | 10 - 4 |
* |
Multiplication | 6 * 7 |
/ |
Division | 20 / 4 |
% |
Modulo (remainder) | 17 % 5 (returns 2) |
Comparison Operators
| Operator | Description | Example |
|---|---|---|
== |
Equal to | x == 5 |
!= |
Not equal to | x != 0 |
< |
Less than | x < 10 |
<= |
Less than or equal | x <= 100 |
> |
Greater than | x > 0 |
>= |
Greater than or equal | x >= 18 |
Logical Operators
| Operator | Description | Example |
|---|---|---|
&& |
Logical AND | x > 0 && x < 100 |
|| |
Logical OR | x == 0 || x == 1 |
! |
Logical NOT | !is_valid |
Access Operators
# Dot notation for object fields
$ name = user.name
$ city = user.address.city
# Bracket notation for arrays
$ first = items[0]
$ last = items[length(items) - 1]
# Bracket notation for dynamic keys
$ value = object["dynamic-key"]
$ item = data[index]
Expressions
Object Literals
# Simple object
$ user = {
name: "Alice",
age: 30,
email: "alice@example.com"
}
# Nested objects
$ order = {
id: 123,
customer: {
name: "Bob",
email: "bob@example.com"
},
items: [
{product: "Widget", qty: 2},
{product: "Gadget", qty: 1}
],
total: 99.99
}
Array Literals
# Number array
$ numbers = [1, 2, 3, 4, 5]
# String array
$ fruits = ["apple", "banana", "cherry"]
# Mixed array (using any type)
$ mixed = [1, "two", true, null]
# Array of objects
$ users = [
{id: 1, name: "Alice"},
{id: 2, name: "Bob"},
{id: 3, name: "Charlie"}
]
Function Calls
# Built-in functions
$ timestamp = now()
$ len = length(items)
$ upper_name = upper(name)
# Method calls on services
$ user = db.users.get(id)
$ users = db.users.filter("status", "active")
# Chained calls
$ result = db.users.filter("active", true).sortBy("name", "asc")
String Concatenation
$ first = "Hello"
$ second = "World"
$ greeting = first + ", " + second + "!"
# Result: "Hello, World!"
$ name = "Alice"
$ message = "Welcome, " + name + "! You have " + count + " notifications."
Async/Await
GlyphLang supports asynchronous programming with async blocks and await expressions. Async blocks execute in the background and return Futures that can be awaited.
Basic Async Block
Use async { } to create an asynchronous computation that returns a Future:
@ GET /compute {
# Create an async block - returns a Future
$ future = async {
$ x = 10
$ y = 20
> x + y
}
# Await the result
$ result = await future
> {value: result}
}
Parallel Execution
Run multiple async operations concurrently for better performance:
@ GET /dashboard {
% db: Database
# Start three async operations at once
$ userFuture = async { > db.getUser(userId) }
$ ordersFuture = async { > db.getOrders(userId) }
$ statsFuture = async { > db.getStats(userId) }
# Await all results - runs in parallel
$ user = await userFuture
$ orders = await ordersFuture
$ stats = await statsFuture
> {user: user, orders: orders, stats: stats}
}
Async with Objects
Async blocks can return any type, including objects:
@ GET /async-object {
$ future = async {
> {
id: 1,
name: "Async User",
email: "async@example.com"
}
}
$ user = await future
> user
}
Sequential Async
Chain async operations when one depends on another:
@ GET /async-sequential {
# First async operation
$ step1 = async {
$ base = 10
> base * 2
}
$ first = await step1
# Second depends on first
$ step2 = async {
> first + 5
}
$ second = await step2
> {step1: first, step2: second}
}
Async with Conditionals
@ GET /async-conditional/:value {
$ future = async {
if value > 50 {
> {status: "high", value: value}
} else {
> {status: "low", value: value}
}
}
> await future
}
Key Points
async { }creates a Future that executes in the backgroundawaitblocks until the Future resolves and returns its value- Multiple async blocks can run in parallel for concurrent operations
- Futures can be awaited multiple times (they cache their result)
Modules
GlyphLang supports modular code organization with imports and exports. Split your code across multiple files for better organization and reusability.
Creating a Module
Define a module with the module keyword:
# utils.glyph
module "utils"
# Export functions using ! syntax
! formatName(first: str, last: str): str {
> first + " " + last
}
! isAdult(age: int): bool {
> age >= 18
}
! calculateAge(birthYear: int, currentYear: int): int {
> currentYear - birthYear
}
Importing Modules
Import an entire module:
# main.glyph
import "./utils"
@ GET /users/:id {
% db: Database
$ user = db.users.find(id)
$ name = utils.formatName(user.firstName, user.lastName)
> {displayName: name}
}
Import with Alias
Use an alias for shorter references:
import "./models" as m
@ GET /users/:id -> m.UserResponse {
% db: Database
$ user = db.users.find(id)
> {id: user.id, email: user.email}
}
Selective Imports
Import specific functions from a module:
from "./utils" import { formatName, isAdult }
@ GET /users/:id {
% db: Database
$ user = db.users.find(id)
# Use imported functions directly (no prefix)
$ name = formatName(user.firstName, user.lastName)
$ adult = isAdult(user.age)
> {displayName: name, isAdult: adult}
}
Model Definitions Module
Organize type definitions in a separate module:
# models.glyph
module "models"
: User {
id: int!
email: str!
firstName: str!
lastName: str!
age: int
role: str = "user"
}
: UserResponse {
id: int!
email: str!
displayName: str!
isAdult: bool
}
! isValidEmail(email: str): bool {
> contains(email, "@")
}
Using Multiple Modules
import "./utils"
import "./models" as m
from "./utils" import { formatName, isAdult }
@ GET /users/:id/stats {
% db: Database
$ user = utils.getUserById(db, parseInt(id))
$ age = utils.calculateAge(user.birthYear, 2024)
$ adult = isAdult(age)
> {
userId: user.id,
age: age,
isAdult: adult,
displayName: formatName(user.firstName, user.lastName)
}
}
Module Best Practices
- Use
module "name"at the top of module files - Export functions with
!and types with: - Use aliases for frequently accessed modules
- Use selective imports for commonly used functions
- Organize types in a dedicated
models.glyphfile
Pattern Matching
GlyphLang provides powerful pattern matching with the match expression. Match against literals, use guards for conditions, and destructure objects and arrays.
Basic Literal Matching
Match against specific values with the => operator:
@ GET /status/:code {
$ result = match code {
200 => "OK"
201 => "Created"
400 => "Bad Request"
404 => "Not Found"
500 => "Internal Server Error"
_ => "Unknown Status"
}
> {status: code, message: result}
}
Note: The underscore _ is the wildcard pattern that matches anything not matched by previous patterns.
Guards with when
Add conditions to patterns using when:
@ GET /grade/:score {
$ grade = match score {
n when n >= 90 => "A"
n when n >= 80 => "B"
n when n >= 70 => "C"
n when n >= 60 => "D"
_ => "F"
}
> {score: score, grade: grade}
}
Object Destructuring
Extract and match object properties:
@ GET /user/:id {
$ user = {name: "Alice", age: 30, role: "admin"}
$ greeting = match user {
{name, role} when role == "admin" => "Welcome Administrator " + name
{name} => "Hello " + name
_ => "Hello Guest"
}
> {greeting: greeting}
}
Array Destructuring
Match and extract array elements:
@ POST /first-element {
$ items = input.items
$ result = match items {
[first] => {count: 1, first: first}
[first, second] => {count: 2, first: first, second: second}
[first, second, third] => {count: 3, first: first}
_ => {count: 0, message: "Empty or too many items"}
}
> result
}
Boolean Patterns
@ GET /toggle/:state {
$ isEnabled = state == "on"
$ result = match isEnabled {
true => {enabled: true, message: "Feature is ON"}
false => {enabled: false, message: "Feature is OFF"}
}
> result
}
String Patterns
@ GET /method-info/:method {
$ info = match method {
"GET" => {safe: true, idempotent: true}
"POST" => {safe: false, idempotent: false}
"PUT" => {safe: false, idempotent: true}
"DELETE" => {safe: false, idempotent: true}
_ => {safe: false, idempotent: false}
}
> info
}
Variable Binding
Bind the matched value to a variable for use in the result:
@ GET /double/:num {
$ result = match num {
x => x + x
}
> {original: num, doubled: result}
}
Complex Guards
Combine object destructuring with guards for complex matching:
@ POST /shipping {
$ order = input
$ shippingCost = match order {
{priority, weight} when priority == "express" => weight * 2
{weight} when weight > 10 => weight * 0.5
{weight} => weight * 0.25
_ => 0
}
> {orderId: order.id, shippingCost: shippingCost}
}
Pattern Matching Features
- Literal patterns: Match exact values (numbers, strings, booleans)
- Wildcard
_: Matches any value (default/fallback case) - Guards
when: Add conditions to patterns - Object destructuring: Extract and match object properties
- Array destructuring: Match and extract array elements
- Variable binding: Capture matched values for use in expressions
Generics
GlyphLang supports generic programming with type parameters, constraints, and type inference. Write reusable functions and types that work with any data type.
Generic Functions
Define functions with type parameters using angle brackets <T>:
# Generic identity function - works with any type
! identity<T>(x: T): T {
> x
}
# Generic function with two type parameters
! pair<T, U>(first: T, second: U): object {
> {first: first, second: second}
}
# Generic function returning same type
! first<T>(a: T, b: T): T {
> a
}
Calling Generic Functions
Call with explicit type arguments or let the compiler infer types:
@ GET /demo {
# Explicit type argument
$ str = identity<string>("hello")
# Type inference - compiler determines type from argument
$ num = identity(42)
# Multiple type parameters
$ p = pair<string, int>("age", 30)
> {str: str, num: num, pair: p}
}
Type Constraints
Restrict type parameters to types that implement certain capabilities:
# Only accepts numeric types
! double<T: Numeric>(x: T): T {
> x * 2
}
# Only accepts comparable types
! compare<T: Comparable>(a: T, b: T): bool {
> a == b
}
# Multiple constraints
! process<T: Numeric, U: Comparable>(x: T, y: U): object {
> {value: x, key: y}
}
Generic Type Definitions
Define generic types (structs) with type parameters:
# Generic Result type for error handling
: Result<T, E> {
value: T?
error: E?
success: bool!
}
# Generic Option type
: Option<T> {
value: T?
hasValue: bool!
}
# Generic Pair type
: Pair<T, U> {
first: T!
second: U!
}
# Generic API response wrapper
: ApiResponse<T> {
data: T?
error: string?
success: bool!
}
Using Generic Types
@ GET /users/:id -> ApiResponse<User> {
% db: Database
$ user = db.users.find(id)
if user == null {
> {data: null, error: "User not found", success: false}
}
> {data: user, error: null, success: true}
}
Generic Factory Functions
# Create a success result
! createSuccess<T>(value: T): object {
> {value: value, error: null, success: true}
}
# Create an error result
! createError<E>(error: E): object {
> {value: null, error: error, success: false}
}
@ GET /result/:value {
$ success = createSuccess(value)
$ failure = createError("Something went wrong")
> {success: success, failure: failure}
}
Higher-Order Generic Functions
# Map function - transform array elements
! map<T, U>(arr: [T], fn: (T) -> U): [U] {
$ result = []
for item in arr {
$ mapped = fn(item)
result = append(result, mapped)
}
> result
}
# Filter function - select matching elements
! filter<T>(arr: [T], predicate: (T) -> bool): [T] {
$ result = []
for item in arr {
if predicate(item) {
result = append(result, item)
}
}
> result
}
Generics Features
- Type parameters:
<T>,<T, U>for single or multiple types - Type inference: Compiler automatically determines types when possible
- Constraints:
<T: Numeric>restricts to specific type classes - Generic types: Define reusable type structures with
: TypeName<T> - Return types: Functions can return generic types
- Higher-order: Generic functions can accept function parameters
Macros
GlyphLang macros provide compile-time code generation. Define reusable code patterns that expand into actual code during compilation, reducing boilerplate and ensuring consistency.
Defining Macros
Use macro! to define a macro with parameters:
# Define a logging macro
macro! log(level, msg) {
if config.logLevel >= level {
$ logEntry = {
level: level,
message: msg,
timestamp: now()
}
> logEntry
}
}
# Define a validation macro
macro! validate_required(field) {
if field == null {
> {error: "${field} is required", status: 400}
}
}
Invoking Macros
Call macros with ! suffix:
@ POST /feedback {
# Invoke validation macros
validate_required!(input.email)
validate_required!(input.message)
# Invoke logging macro
log!("INFO", "New feedback from " + input.email)
$ result = db.insert("feedback", input)
> {success: true, id: result.lastInsertId}
}
CRUD Macro
Generate multiple routes from a single macro invocation:
# Define CRUD macro for any resource
macro! crud(resource) {
@ GET /${resource} {
> db.query("SELECT * FROM ${resource}")
}
@ GET /${resource}/:id {
> db.query("SELECT * FROM ${resource} WHERE id = ?", id)
}
@ POST /${resource} {
$ result = db.insert("${resource}", input)
> {id: result.lastInsertId}
}
@ DELETE /${resource}/:id {
> db.query("DELETE FROM ${resource} WHERE id = ?", id)
}
}
# Generate CRUD routes for users and posts
crud!(users)
crud!(posts)
Response Wrapper Macro
# Standardize API responses
macro! json_response(data, status) {
> {
data: data,
status: status,
timestamp: now()
}
}
@ GET /users/:id {
$ user = db.users.find(id)
json_response!(user, 200)
}
Auth Check Macro
# Reusable authorization check
macro! require_auth(role) {
if !auth {
> {error: "Unauthorized", status: 401}
}
if auth.role != role {
> {error: "Forbidden", status: 403}
}
}
@ GET /admin/stats {
+ auth(jwt)
require_auth!("admin")
$ userCount = db.query("SELECT COUNT(*) FROM users")
$ postCount = db.query("SELECT COUNT(*) FROM posts")
json_response!({users: userCount, posts: postCount}, 200)
}
String Interpolation in Macros
Use ${param} to interpolate macro parameters into strings:
macro! endpoint(name, table) {
@ GET /api/${name} {
> db.query("SELECT * FROM ${table}")
}
}
# Generates: @ GET /api/products with query on 'inventory' table
endpoint!(products, inventory)
Macro Features
- Compile-time expansion: Macros expand before runtime for zero overhead
- Code generation: Generate multiple routes, functions, or statements
- String interpolation: Use
${param}to insert parameters into strings - Reusable patterns: Define once, use everywhere for consistency
- Reduced boilerplate: CRUD, validation, logging patterns in one line
Functions
GlyphLang uses the ! symbol to define reusable functions. Functions encapsulate logic, accept parameters with type annotations, and return typed values.
Basic Function Definition
Define functions with the ! symbol, followed by the function name, parameters, optional return type, and body:
# Simple function with no parameters
! sayHello(): string {
> "Hello, World!"
}
# Function with parameters and return type
! greet(name: string): string {
> "Hello, " + name + "!"
}
# Function with multiple parameters
! add(a: int, b: int): int {
> a + b
}
Parameters and Types
Function parameters are declared with their types. Use ? for optional parameters:
# Required parameters
! calculateArea(width: int, height: int): int {
> width * height
}
# Optional parameter with default behavior
! formatName(firstName: string, lastName: string?): string {
if lastName == null {
> firstName
}
> firstName + " " + lastName
}
# Multiple types in parameters
! processData(id: int, data: object, validate: bool): object {
if validate {
# perform validation
}
> {id: id, processed: data}
}
Return Types
Specify return types after the parameters. Use union types for multiple possible returns:
# Single return type
! getUserName(id: int): string {
$ user = db.users.find(id)
> user.name
}
# Optional return (may return null)
! findUser(id: int): User? {
$ user = db.users.find(id)
> user
}
# Union return type
! getResult(id: int): User | Error {
$ user = db.users.find(id)
if user == null {
> {error: "User not found", code: 404}
}
> user
}
# Array return type
! getAllUsers(): [User] {
> db.users.all()
}
Calling Functions
Call functions by name with arguments:
# Define a function
! isAdult(age: int): bool {
> age >= 18
}
! calculateAge(birthYear: int, currentYear: int): int {
> currentYear - birthYear
}
# Call functions in routes
@ GET /check-age/:birthYear {
$ age = calculateAge(birthYear, 2025)
$ adult = isAdult(age)
> {age: age, isAdult: adult}
}
# Call functions in expressions
@ GET /users/:id {
$ user = db.users.find(id)
$ displayName = formatName(user.firstName, user.lastName)
> {user: user, displayName: displayName}
}
Functions with Objects
Functions can accept and return complex objects:
: User {
id: int!
name: string!
email: string?
}
: CreateUserInput {
name: string!
email: string!
}
! createUser(input: CreateUserInput): User {
$ user = db.users.insert(input)
> user
}
! updateUser(id: int, updates: object): User {
$ user = db.users.update(id, updates)
> user
}
@ POST /users {
$ newUser = createUser(input)
> {success: true, user: newUser}
}
Functions with Database Operations
Functions can encapsulate database logic for reuse:
! getAllUsers(db: Database): [User] {
$ users = db.users.all()
> users
}
! getUserById(db: Database, id: int): User? {
$ user = db.users.find(id)
> user
}
! searchUsers(db: Database, query: string): [User] {
$ results = db.query("SELECT * FROM users WHERE name LIKE ?", "%" + query + "%")
> results
}
@ GET /users {
% db: Database
$ users = getAllUsers(db)
> {users: users}
}
@ GET /users/search/:query {
% db: Database
$ results = searchUsers(db, query)
> {results: results}
}
Function Features
- Symbol syntax: Use
!to define functions - Type annotations: Parameters and return types are strongly typed
- Optional parameters: Use
?for nullable parameters - Union returns: Return multiple types with
| - Reusability: Define once, call from routes or other functions
- Encapsulation: Group related logic into named functions
Middleware
GlyphLang uses the + symbol to apply middleware to routes. Middleware intercepts requests before they reach your route handler, enabling authentication, rate limiting, logging, and more.
Basic Middleware Syntax
Apply middleware with + followed by the middleware name and optional configuration:
# Apply rate limiting middleware
@ GET /api/users {
+ ratelimit(100/min)
> {users: db.users.all()}
}
# Apply authentication middleware
@ GET /api/profile {
+ auth(jwt)
> {user: auth.user}
}
Authentication Middleware
The auth middleware validates JWT tokens and makes user data available via auth.user:
# Basic JWT authentication
@ GET /api/me {
+ auth(jwt)
$ userId = auth.user.id
$ username = auth.user.username
> {id: userId, username: username}
}
# Role-based authentication
@ GET /api/admin/dashboard {
+ auth(jwt, role: admin)
> {message: "Welcome, admin!"}
}
# Multiple role options
@ DELETE /api/posts/:id {
+ auth(jwt, role: moderator)
$ post = db.posts.get(id)
$ deleted = db.posts.delete(id)
> {success: true, deleted: post}
}
Rate Limiting Middleware
Protect endpoints from abuse with configurable rate limits:
# Limit to 100 requests per minute
@ GET /api/data {
+ ratelimit(100/min)
> db.data.all()
}
# Stricter limit for sensitive operations
@ POST /api/auth/login {
+ ratelimit(10/min)
# Login logic
> {success: true}
}
# Higher limit for public endpoints
@ GET /api/health {
+ ratelimit(1000/min)
> {status: "ok", timestamp: now()}
}
Combining Multiple Middleware
Apply multiple middleware to a single route. They execute in order from top to bottom:
# Authentication + rate limiting
@ POST /api/users {
+ auth(jwt)
+ ratelimit(50/min)
% db: Database
$ newUser = db.users.create(input)
> {success: true, user: newUser}
}
# Admin route with strict limits
@ DELETE /api/admin/users/:id {
+ auth(jwt, role: admin)
+ ratelimit(10/min)
% db: Database
$ deleted = db.users.delete(id)
> {success: true, message: "User deleted"}
}
Middleware Execution Order
Middleware executes in declaration order. If any middleware rejects the request, subsequent middleware and the route handler are not executed:
@ PUT /api/settings {
+ ratelimit(30/min) # 1. Check rate limit first
+ auth(jwt) # 2. Then verify authentication
+ auth(jwt, role: admin) # 3. Then check admin role
% db: Database
# Only reached if all middleware passes
$ updated = db.settings.update(input)
> {success: true, settings: updated}
}
Middleware Features
- Symbol syntax: Use
+to apply middleware - Authentication:
+ auth(jwt)for JWT validation - Role-based access:
+ auth(jwt, role: admin) - Rate limiting:
+ ratelimit(N/min) - Composable: Stack multiple middleware on a single route
- Order matters: Middleware executes top to bottom
Dependency Injection
GlyphLang uses the % symbol for dependency injection. This pattern provides routes with access to shared resources like databases, caches, and external services without manual instantiation.
Basic Injection Syntax
Inject dependencies with % followed by the variable name and type:
# Inject database dependency
@ GET /api/users {
% db: Database
$ users = db.users.all()
> {users: users}
}
# Inject cache dependency
@ GET /api/cached/:key {
% cache: Cache
$ value = cache.get(key)
> {key: key, value: value}
}
Database Injection
The most common use case is injecting a database connection:
# Basic database operations
@ GET /api/users/:id {
% db: Database
$ user = db.users.get(id)
if user == null {
> {error: "User not found", code: 404}
}
> user
}
@ POST /api/users {
% db: Database
$ newUser = {
id: db.users.nextId(),
username: input.username,
email: input.email,
created_at: now()
}
$ saved = db.users.create(newUser)
> {success: true, user: saved}
}
@ PUT /api/users/:id {
% db: Database
$ updated = db.users.update(id, input)
> {success: true, user: updated}
}
@ DELETE /api/users/:id {
% db: Database
$ deleted = db.users.delete(id)
> {success: true, message: "User deleted"}
}
Multiple Dependencies
Inject multiple dependencies in a single route:
@ GET /api/dashboard {
+ auth(jwt)
% db: Database
% cache: Cache
% metrics: MetricsService
# Check cache first
$ cached = cache.get("dashboard:" + auth.user.id)
if cached != null {
> cached
}
# Fetch from database
$ user = db.users.get(auth.user.id)
$ orders = db.orders.filter("user_id", auth.user.id)
$ stats = db.stats.get(auth.user.id)
# Track metrics
$ metrics.increment("dashboard.views")
$ result = {
user: user,
orders: orders,
stats: stats
}
# Cache for next time
$ cache.set("dashboard:" + auth.user.id, result, 300)
> result
}
Combining with Middleware
Dependency injection works seamlessly with middleware. Declare middleware first, then dependencies:
@ POST /api/orders {
+ auth(jwt) # Middleware first
+ ratelimit(50/min)
% db: Database # Then dependencies
$ order = {
id: db.orders.nextId(),
user_id: auth.user.id,
items: input.items,
total: input.total,
created_at: now()
}
$ saved = db.orders.create(order)
> {success: true, order: saved}
}
Available Dependencies
Common injectable dependencies in GlyphLang:
# Database connection
% db: Database
# Caching layer
% cache: Cache
# Logging service
% logger: Logger
# Metrics/telemetry
% metrics: MetricsService
# Email service
% email: EmailService
# External API client
% api: HttpClient
Dependency Injection Features
- Symbol syntax: Use
%to inject dependencies - Type-safe: Dependencies are typed for IDE support
- Automatic management: Resources are managed by the runtime
- Connection pooling: Database connections are pooled automatically
- Testability: Dependencies can be mocked in tests
- Composable: Combine multiple dependencies in one route
Error Handling
GlyphLang uses structured error responses and conditional checks for error handling. Define error types, return consistent error objects, and handle different failure scenarios gracefully.
Structured Error Responses
Define error types and return consistent error objects:
# Define error types
: NotFoundError {
code: str!
message: str!
resource: str!
resource_id: any
}
: ValidationError {
code: str!
message: str!
field: str
details: [str]
}
# Standard API response wrapper
: ApiResponse {
success: bool!
data: any
error: any
timestamp: timestamp!
}
Basic Error Handling
Use conditional checks to detect and return errors:
@ GET /api/users/:id -> ApiResponse {
% db: Database
$ user = db.users.get(id)
if user == null {
> {
success: false,
data: null,
error: {
code: "NOT_FOUND",
message: "User not found",
resource: "user",
resource_id: id
},
timestamp: now()
}
}
> {
success: true,
data: user,
error: null,
timestamp: now()
}
}
Validation Errors
Return detailed validation errors with field-specific information:
@ POST /api/users -> ApiResponse {
% db: Database
# Validate required fields
if input.username == null || input.username == "" {
> {
success: false,
data: null,
error: {
code: "VALIDATION_ERROR",
message: "Username is required",
field: "username",
details: ["Username cannot be empty"]
},
timestamp: now()
}
}
if input.email == null || input.email == "" {
> {
success: false,
data: null,
error: {
code: "VALIDATION_ERROR",
message: "Email is required",
field: "email",
details: ["Email cannot be empty"]
},
timestamp: now()
}
}
# Check for duplicates
$ existing = db.users.findOne("email", input.email)
if existing != null {
> {
success: false,
data: null,
error: {
code: "DUPLICATE",
message: "Email already registered",
field: "email",
details: []
},
timestamp: now()
}
}
$ user = db.users.create(input)
> {success: true, data: user, error: null, timestamp: now()}
}
Authorization Errors
Handle permission and access control errors:
@ DELETE /api/posts/:id -> ApiResponse {
+ auth(jwt)
% db: Database
$ post = db.posts.get(id)
if post == null {
> {
success: false,
data: null,
error: {code: "NOT_FOUND", message: "Post not found"},
timestamp: now()
}
}
# Check ownership or admin role
if post.author_id != auth.user.id && auth.user.role != "admin" {
> {
success: false,
data: null,
error: {
code: "FORBIDDEN",
message: "You do not have permission to delete this post",
required_role: "owner or admin",
action: "delete_post"
},
timestamp: now()
}
}
$ deleted = db.posts.delete(id)
> {success: true, data: {deleted: id}, error: null, timestamp: now()}
}
State Validation Errors
Check resource state before performing operations:
@ POST /api/orders/:id/cancel -> ApiResponse {
+ auth(jwt)
% db: Database
$ order = db.orders.get(id)
if order == null {
> {
success: false,
error: {code: "NOT_FOUND", message: "Order not found"},
timestamp: now()
}
}
# Check if order can be cancelled
if order.status == "shipped" {
> {
success: false,
error: {
code: "INVALID_STATE",
message: "Cannot cancel shipped orders",
resource: "order"
},
timestamp: now()
}
}
if order.status == "cancelled" {
> {
success: false,
error: {
code: "DUPLICATE",
message: "Order already cancelled"
},
timestamp: now()
}
}
$ order.status = "cancelled"
$ updated = db.orders.update(id, order)
> {success: true, data: updated, error: null, timestamp: now()}
}
Bulk Operation Error Handling
Handle partial failures in bulk operations:
@ PUT /api/products/bulk-update -> ApiResponse {
+ auth(jwt, role: admin)
% db: Database
$ successCount = 0
$ failures = []
for update in input.updates {
$ product = db.products.get(update.id)
if product == null {
failures = failures + [{
id: update.id,
error: "Product not found"
}]
} else {
$ updated = db.products.update(update.id, update)
successCount = successCount + 1
}
}
# Return partial success result
> {
success: successCount > 0,
data: {
total: input.updates.length(),
succeeded: successCount,
failed: failures.length(),
failures: failures
},
error: null,
timestamp: now()
}
}
Common Error Codes
Standard error codes for consistent API responses:
# Common error patterns
$ error = match errorType {
"not_found" => {code: "NOT_FOUND", message: "Resource not found"}
"validation" => {code: "VALIDATION_ERROR", message: "Invalid input"}
"auth" => {code: "UNAUTHORIZED", message: "Authentication required"}
"forbidden" => {code: "FORBIDDEN", message: "Access denied"}
"conflict" => {code: "CONFLICT", message: "Resource conflict"}
"duplicate" => {code: "DUPLICATE", message: "Resource already exists"}
"invalid_state" => {code: "INVALID_STATE", message: "Invalid operation"}
_ => {code: "INTERNAL_ERROR", message: "An error occurred"}
}
Error Handling Best Practices
- Consistent structure: Use a standard ApiResponse wrapper for all endpoints
- Meaningful codes: Use descriptive error codes (NOT_FOUND, VALIDATION_ERROR, etc.)
- Field-specific errors: Include the field name for validation errors
- Early returns: Check for errors first and return immediately
- Partial success: For bulk operations, report which items succeeded/failed
- Timestamps: Include timestamps for debugging and logging
Collections
GlyphLang provides arrays and objects as primary collection types. Use them to store lists of items, structured data, and build complex data structures.
Array Literals
Create arrays using square bracket syntax:
# Empty array
$ items = []
# Array with values
$ numbers = [1, 2, 3, 4, 5]
$ names = ["Alice", "Bob", "Charlie"]
$ mixed = [1, "two", true, 3.14]
# Nested arrays
$ matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
# Array of objects
$ users = [
{id: 1, name: "Alice"},
{id: 2, name: "Bob"},
{id: 3, name: "Charlie"}
]
Array Operations
Common operations for working with arrays:
# Get array length
$ count = items.length()
# Concatenate arrays with +
$ combined = [1, 2, 3] + [4, 5, 6] # [1, 2, 3, 4, 5, 6]
# Append item to array
items = items + [newItem]
# Append multiple items
items = items + [item1, item2, item3]
# Check if array contains value
$ found = false
for item in items {
if item == searchValue {
found = true
}
}
# Get first/last items (via iteration)
$ first = null
$ last = null
for item in items {
if first == null {
$ first = item
}
$ last = item
}
Object Literals
Create objects using curly brace syntax:
# Simple object
$ user = {
id: 1,
name: "Alice",
email: "alice@example.com"
}
# Nested objects
$ order = {
id: 100,
customer: {
id: 1,
name: "Alice"
},
items: [
{product_id: 10, quantity: 2},
{product_id: 20, quantity: 1}
],
total: 99.99
}
# Dynamic object creation
$ timestamp = now()
$ response = {
success: true,
data: user,
created_at: timestamp
}
Property Access
Access object properties using dot notation:
$ user = {id: 1, name: "Alice", email: "alice@example.com"}
# Access properties
$ userId = user.id
$ userName = user.name
# Nested property access
$ order = {customer: {name: "Alice", address: {city: "NYC"}}}
$ city = order.customer.address.city
# Update properties
$ user.name = "Alice Smith"
$ user.updated_at = now()
# Access array elements in objects
$ cart = {items: [{id: 1}, {id: 2}, {id: 3}]}
$ firstItem = cart.items[0]
Iteration with For Loops
Iterate over arrays using for-in loops:
# Basic iteration
$ total = 0
for item in items {
total = total + item.price
}
# Build new array from existing
$ names = []
for user in users {
names = names + [user.name]
}
# Nested iteration
for order in orders {
for item in order.items {
$ subtotal = item.price * item.quantity
}
}
# Track index during iteration
$ index = 0
for item in items {
$ item.position = index
index = index + 1
}
Filtering Collections
Filter arrays by building new arrays with matching items:
# Filter by condition
$ activeUsers = []
for user in users {
if user.active == true {
activeUsers = activeUsers + [user]
}
}
# Filter by multiple conditions
$ results = []
for product in products {
if product.price >= minPrice && product.price <= maxPrice {
if product.category == targetCategory {
results = results + [product]
}
}
}
# Filter with complex logic
$ eligible = []
for user in users {
$ isEligible = user.age >= 18 && user.verified == true
if isEligible {
eligible = eligible + [user]
}
}
Transforming Collections
Map arrays to new arrays with transformed values:
# Transform each item
$ enrichedItems = []
for item in cart.items {
$ product = db.products.get(item.product_id)
$ subtotal = product.price * item.quantity
enrichedItems = enrichedItems + [{
product_id: product.id,
product_name: product.name,
quantity: item.quantity,
unit_price: product.price,
subtotal: subtotal
}]
}
# Extract specific fields
$ emails = []
for user in users {
emails = emails + [user.email]
}
# Transform with conditional logic
$ displayItems = []
for product in products {
$ status = "available"
if product.inventory < 1 {
$ status = "out_of_stock"
}
displayItems = displayItems + [{
id: product.id,
name: product.name,
status: status
}]
}
Aggregation
Calculate aggregate values from collections:
# Sum values
$ total = 0.0
for item in items {
total = total + item.price
}
# Count matching items
$ activeCount = 0
for user in users {
if user.active == true {
activeCount = activeCount + 1
}
}
# Find min and max
$ minPrice = 999999.0
$ maxPrice = 0.0
for product in products {
if product.price < minPrice {
$ minPrice = product.price
}
if product.price > maxPrice {
$ maxPrice = product.price
}
}
# Calculate average
$ sum = 0.0
$ count = 0
for item in items {
sum = sum + item.value
count = count + 1
}
$ average = sum / count
Grouping Data
Group items by category or other criteria:
# Count by category
$ electronicsCount = 0
$ clothingCount = 0
$ otherCount = 0
for product in products {
if product.category == "electronics" {
electronicsCount = electronicsCount + 1
} else {
if product.category == "clothing" {
clothingCount = clothingCount + 1
} else {
otherCount = otherCount + 1
}
}
}
# Build category summary
$ categories = []
if electronicsCount > 0 {
categories = categories + [{
name: "electronics",
count: electronicsCount
}]
}
if clothingCount > 0 {
categories = categories + [{
name: "clothing",
count: clothingCount
}]
}
Searching Collections
Find items in collections:
# Find first match
$ found = null
for user in users {
if user.email == searchEmail && found == null {
$ found = user
}
}
# Find all matches
$ matches = []
for product in products {
if product.name.contains(searchTerm) {
matches = matches + [product]
}
}
# Check if any item matches
$ hasMatch = false
for item in items {
if item.id == targetId {
$ hasMatch = true
}
}
# Find index of item
$ foundIndex = -1
$ currentIndex = 0
for item in items {
if item.id == targetId {
$ foundIndex = currentIndex
}
currentIndex = currentIndex + 1
}
Collection Types in Definitions
Declare array types in type definitions:
# Array type with List[T] or [T]
: User {
id: int!
name: str!
roles: [str]! # Array of strings
tags: List[str] # Alternative syntax
}
: Order {
id: int!
items: [OrderItem]! # Array of OrderItem objects
notes: [str] # Optional array of strings
}
: OrderItem {
product_id: int!
quantity: int!
price: float!
}
: ApiResponse {
success: bool!
data: any # Can hold arrays or objects
errors: [str] # Array of error messages
}
Collection Features
- Array literals: Create with
[]syntax - Object literals: Create with
{}syntax - Concatenation: Use
+to append to arrays - Length: Use
.length()to get array size - Iteration: Use
for item in arrayloops - Property access: Use dot notation for objects
- Type syntax: Use
[T]orList[T]for array types
Hot Reload
GlyphLang's development server automatically reloads your application when files change. This enables rapid iteration without manually restarting the server.
Starting the Dev Server
Use the glyph dev command to start a development server with hot reload:
# Start dev server with hot reload (default port 3000)
glyph dev main.glyph
# Specify a custom port
glyph dev main.glyph --port 8080
glyph dev main.glyph -p 8080
# Open browser automatically
glyph dev main.glyph --open
glyph dev main.glyph -o
How Hot Reload Works
When you save a .glyph file, the dev server:
- Detects the file change
- Re-parses and recompiles the code
- Restarts the HTTP server with new routes
- Notifies connected browsers to refresh
$ glyph dev api.glyph
Starting development server on port 3000...
Watching /path/to/api.glyph for changes...
Dev server listening on http://localhost:3000 (compiled mode)
# When you save changes:
File changed: api.glyph
Reloading server...
Dev server listening on http://localhost:3000 (compiled mode)
File Watching
The dev server watches for changes to your Glyph files. File watching is enabled by default:
# Watch is enabled by default
glyph dev main.glyph
# Explicitly enable/disable watching
glyph dev main.glyph --watch=true
glyph dev main.glyph --watch=false
glyph dev main.glyph -w
Live Reload for Browsers
The dev server injects a live reload script into responses. Connected browsers automatically refresh when code changes:
# Your API endpoints work normally
@ GET /api/users {
% db: Database
> db.users.all()
}
# Browser-based testing refreshes automatically
# when you modify and save this file
Dev vs Run Commands
Choose the right command for your workflow:
# Development: hot reload, file watching, live reload
glyph dev main.glyph
# Production: single run, no watching, optimized
glyph run main.glyph
| Feature | glyph dev | glyph run |
|---|---|---|
| Hot reload | Yes | No |
| File watching | Yes | No |
| Live reload script | Injected | No |
| Use case | Development | Production |
Development Workflow
A typical development session with hot reload:
# 1. Start the dev server
glyph dev api.glyph --port 3000
# 2. Open your browser or API client
# http://localhost:3000/api/users
# 3. Edit api.glyph in your editor
# - Add new routes
# - Modify existing handlers
# - Fix bugs
# 4. Save the file
# Server automatically reloads
# 5. Refresh browser or re-send API request
# Changes are immediately available
Hot Reload Features
- Automatic detection: File changes trigger reload instantly
- Full recompilation: Routes and handlers are rebuilt
- Browser refresh: Connected browsers update automatically
- Port configuration: Use
--portor-pflag - Atomic saves: Works with editors that use temp files
Debug Mode
GlyphLang provides several debugging techniques to help you troubleshoot issues during development.
Interpreter Mode
Use the interpreter for step-by-step execution with detailed error messages:
# Run with tree-walking interpreter instead of compiler
glyph run main.glyph --interpret
# Interpreter provides more detailed error traces
# Useful when debugging complex logic
Compilation Errors
The compiler provides detailed error messages with line numbers and context:
$ glyph compile api.glyph
Error: Undefined variable 'users' at line 15
|
15| > users
| ^^^^^
|
Hint: Did you mean 'user'? (defined at line 12)
Validation Command
Use the validate command to check for errors without running:
# Validate a single file
glyph validate main.glyph
# Validate with AI-friendly JSON output
glyph validate main.glyph --ai
# Validate entire directory
glyph validate src/ --ai
Debug Logging in Routes
Add temporary logging to trace execution:
@ POST /api/orders {
+ auth(jwt)
% db: Database
# Debug: log input data
$ debugInput = {
received: input,
user_id: auth.user.id,
timestamp: now()
}
# Your logic here
$ order = db.orders.create({
user_id: auth.user.id,
items: input.items
})
# Debug: include debug info in response
> {
success: true,
order: order,
_debug: debugInput # Remove in production
}
}
Error Response Debugging
Return detailed error information during development:
@ GET /api/users/:id {
% db: Database
$ user = db.users.get(id)
if user == null {
> {
success: false,
error: {
code: "NOT_FOUND",
message: "User not found",
# Debug info (remove in production)
_debug: {
requested_id: id,
query: "SELECT * FROM users WHERE id = ?",
timestamp: now()
}
}
}
}
> {success: true, data: user}
}
Bytecode Inspection
Compile and decompile to inspect the generated bytecode:
# Compile to bytecode
glyph compile main.glyph
# Creates main.glybc
# Decompile to inspect bytecode
glyph decompile main.glybc
# Output shows opcodes and operations:
# LOAD_CONST 0 "Hello"
# LOAD_VAR name
# BINARY_OP ADD
# RETURN
LSP Diagnostics
Use the LSP server with your IDE for real-time error detection:
# Start LSP server with debug logging
glyph lsp --log debug.log
# VS Code extension provides:
# - Syntax highlighting
# - Error underlines
# - Hover information
# - Go to definition
Common Debugging Patterns
Effective techniques for troubleshooting:
# 1. Isolate the problem - create minimal test route
@ GET /debug/test {
$ x = 10
$ y = 20
$ result = x + y
> {result: result} # Verify basic operations work
}
# 2. Check variable values at each step
@ POST /debug/trace {
$ step1 = input.data
$ step1Debug = {step: 1, value: step1}
$ step2 = step1 + " processed"
$ step2Debug = {step: 2, value: step2}
> {
final: step2,
trace: [step1Debug, step2Debug]
}
}
# 3. Verify database queries
@ GET /debug/db {
% db: Database
$ allUsers = db.users.all()
$ userCount = allUsers.length()
> {
count: userCount,
sample: allUsers, # Inspect actual data
tables: ["users", "orders", "products"]
}
}
Environment-Based Debugging
Conditionally include debug information:
@ GET /api/status {
$ response = {
status: "ok",
version: "1.0.0",
timestamp: now()
}
# Add debug info in development
if config.environment == "development" {
$ response._debug = {
uptime: server.uptime(),
memory: server.memoryUsage(),
requests: server.requestCount()
}
}
> response
}
Debugging Tips
- Interpreter mode: Use
--interpretfor detailed error traces - Validate first: Run
glyph validatebefore debugging - Add trace logging: Include debug objects in responses temporarily
- Inspect bytecode: Use
glyph decompilefor low-level issues - Use LSP: IDE integration catches errors as you type
- Isolate issues: Create minimal test routes to reproduce bugs
If/Else Statements
Conditional execution using standard if/else syntax.
Basic If Statement
if user.age >= 18 {
$ status = "adult"
}
If/Else
if user.age >= 18 {
$ status = "adult"
} else {
$ status = "minor"
}
If/Else If/Else
if score >= 90 {
$ grade = "A"
} else if score >= 80 {
$ grade = "B"
} else if score >= 70 {
$ grade = "C"
} else if score >= 60 {
$ grade = "D"
} else {
$ grade = "F"
}
Conditional Returns
@ GET /api/users/:id {
% db: Database
$ user = db.users.get(id)
if user == null {
> {error: "User not found", status: 404}
}
if !user.active {
> {error: "User is inactive", status: 403}
}
> {user: user}
}
Complex Conditions
if user.role == "admin" || user.role == "moderator" {
$ can_delete = true
}
if age >= 18 && country == "US" && !is_banned {
$ can_vote = true
}
While Loops
Execute a block of code repeatedly while a condition is true.
Basic While Loop
$ counter = 0
while counter < 10 {
counter = counter + 1 # Reassignment without $
}
# counter is now 10
While with Accumulator
$ sum = 0
$ i = 1
while i <= 100 {
sum = sum + i # Reassignment
i = i + 1 # Reassignment
}
# sum = 5050 (sum of 1 to 100)
Processing Until Condition
$ items = db.queue.pending()
$ processed = 0
while length(items) > 0 && processed < 100 {
$ item = items[0]
$ result = process(item)
processed = processed + 1 # Reassignment
items = db.queue.pending() # Reassignment
}
For Loops
Iterate over arrays or objects with for loops.
Array Iteration
$ numbers = [1, 2, 3, 4, 5]
$ total = 0
for num in numbers {
total = total + num # Reassignment without $
}
# total = 15
Array Iteration with Index
$ fruits = ["apple", "banana", "cherry"]
$ result = []
for index, fruit in fruits {
result = result + [{ # Reassignment without $
position: index,
name: fruit
}]
}
Object Iteration
$ config = {
timeout: 30,
retries: 3,
debug: true
}
$ pairs = []
for key, value in config {
pairs = pairs + [{k: key, v: value}] # Reassignment
}
Nested Loops
$ matrix = [[1, 2], [3, 4], [5, 6]]
$ flat = []
for row in matrix {
for cell in row {
flat = flat + [cell] # Reassignment without $
}
}
# flat = [1, 2, 3, 4, 5, 6]
Processing Database Results
@ GET /api/users/summary {
% db: Database
$ users = db.users.all()
$ summaries = []
for user in users {
$ summary = {
id: user.id,
name: user.name,
post_count: length(user.posts)
}
summaries = summaries + [summary]
}
> {users: summaries}
}
Switch Statements
Multi-way branching based on a value.
Basic Switch
switch status {
case "pending" {
$ message = "Order is pending"
}
case "processing" {
$ message = "Order is being processed"
}
case "shipped" {
$ message = "Order has been shipped"
}
case "delivered" {
$ message = "Order delivered"
}
default {
$ message = "Unknown status"
}
}
Switch with Returns
@ GET /api/status/:code
switch code {
case "200" {
> {status: "OK", message: "Success"}
}
case "404" {
> {status: "Not Found", message: "Resource not found"}
}
case "500" {
> {status: "Error", message: "Internal server error"}
}
default {
> {status: "Unknown", message: "Unknown status code"}
}
}
Switch on Computed Values
$ day = get_day_of_week()
switch day {
case "Saturday" {
$ type = "weekend"
}
case "Sunday" {
$ type = "weekend"
}
default {
$ type = "weekday"
}
}
Routes
Routes are the primary building blocks in GlyphLang, defining HTTP endpoints for your API.
Note: The route keyword can optionally be used after @ for explicit syntax (e.g., @ route /api/users [GET]), but the shorthand @ METHOD /path is preferred.
Basic Route Syntax
# GET route
@ GET /api/hello {
> {message: "Hello, World!"}
}
# GET route with database
@ GET /api/users {
% db: Database
$ users = db.users.all()
> {users: users}
}
Route with Return Type
# Single return type
@ GET /api/users/:id -> User {
% db: Database
$ user = db.users.get(id)
> user
}
# Union return type (success or error)
@ GET /api/users/:id -> User | Error {
% db: Database
$ user = db.users.get(id)
if user == null {
> {error: "Not found", code: 404}
}
> user
}
Route Structure
@ POST /api/example -> Response {
# 1. Middleware (optional)
+ auth(jwt)
+ ratelimit(100/min)
# 2. Dependencies (optional)
% db: Database
% cache: Cache
# 3. Validation (optional)
? validate_email(input.email)
# 4. Logic
$ result = db.users.create(input)
# 5. Return
> {success: true, data: result}
}
HTTP Methods
GlyphLang supports all standard HTTP methods.
Supported Methods
# GET - Retrieve resources
@ GET /api/users {
> db.users.all()
}
# POST - Create resources
@ POST /api/users {
$ user = db.users.create(input)
> {user: user}
}
# PUT - Replace resources
@ PUT /api/users/:id {
$ user = db.users.update(id, input)
> {user: user}
}
# PATCH - Partial update
@ PATCH /api/users/:id {
$ user = db.users.update(id, input)
> {user: user}
}
# DELETE - Remove resources
@ DELETE /api/users/:id {
$ result = db.users.delete(id)
> {deleted: true}
}
RESTful API Example
# Complete CRUD for a resource
: Post {
id: int!
title: str!
content: str!
author_id: int!
created_at: timestamp
}
# List all posts
@ GET /api/posts {
% db: Database
$ posts = db.posts.all()
> {posts: posts}
}
# Get single post
@ GET /api/posts/:id {
% db: Database
$ post = db.posts.get(id)
> {post: post}
}
# Create post
@ POST /api/posts {
+ auth(jwt)
% db: Database
$ post = db.posts.create({
title: input.title,
content: input.content,
author_id: input.user_id,
created_at: now()
})
> {post: post}
}
# Update post
@ PUT /api/posts/:id {
+ auth(jwt)
% db: Database
$ post = db.posts.update(id, input)
> {post: post}
}
# Delete post
@ DELETE /api/posts/:id {
+ auth(jwt)
% db: Database
$ result = db.posts.delete(id)
> {deleted: true}
}
Path Parameters
Capture dynamic segments from URL paths using : prefix.
Single Parameter
# :id becomes available as 'id' variable
@ GET /api/users/:id {
% db: Database
$ user = db.users.get(id)
> {user: user}
}
Multiple Parameters
# Both :userId and :postId are captured
@ GET /api/users/:userId/posts/:postId {
% db: Database
$ post = db.posts.findOne("id", postId)
if post.author_id != userId {
> {error: "Post not found for user"}
}
> {post: post}
}
Parameter Naming
# Use descriptive names
@ GET /api/categories/:categorySlug/products/:productId {
% db: Database
$ category = db.categories.findOne("slug", categorySlug)
$ product = db.products.get(productId)
> {category: category, product: product}
}
Request Body (input)
# POST/PUT/PATCH requests have input object
@ POST /api/users {
% db: Database
# Access request body fields
$ name = input.name
$ email = input.email
$ age = input.age
$ user = db.users.create({
name: name,
email: email,
age: age
})
> {user: user}
}
Authentication
Built-in authentication middleware using the + auth() modifier.
JWT Authentication
# Require valid JWT token
@ GET /api/profile {
+ auth(jwt)
% db: Database
$ user = db.users.get(input.user_id)
> {user: user}
}
Role-Based Access Control
# Require specific role
@ GET /api/admin/users {
+ auth(jwt, role: admin)
% db: Database
$ users = db.users.all()
> {users: users}
}
# Multiple roles
@ POST /api/moderate {
+ auth(jwt, role: moderator)
> {access: "granted"}
}
Protected Route Example
# Protected route - requires valid auth token
@ GET /api/user/profile {
+ auth(jwt)
% db: Database
$ user = db.users.get(input.user_id)
if user == null {
> {error: "User not found", code: 404}
}
> {
}
user: {
id: user.id,
name: user.name,
email: user.email,
role: user.role
}
}
API Key Authentication
# Require valid API key
@ GET /api/data {
+ auth(api_key)
% db: Database
$ data = db.records.all()
> {data: data, count: length(data)}
}
Rate Limiting
Protect your API from abuse with built-in rate limiting.
Basic Rate Limiting
# 100 requests per minute
@ GET /api/data {
+ ratelimit(100/min)
> {data: "example"}
}
Rate Limit Units
# Per second
@ GET /api/fast {
+ ratelimit(10/sec)
> {type: "fast"}
}
# Per minute
@ GET /api/normal {
+ ratelimit(100/min)
> {type: "normal"}
}
# Per hour
@ GET /api/slow {
+ ratelimit(1000/hour)
> {type: "slow"}
}
Combining with Auth
# Auth + rate limiting
@ POST /api/protected {
+ auth(jwt)
+ ratelimit(50/min)
% db: Database
$ result = db.actions.create(input)
> {result: result}
}
Different Limits per Endpoint
# Login: strict rate limit (prevent brute force)
@ POST /api/login {
+ ratelimit(5/min)
# ... login logic
}
# Read operations: generous limit
@ GET /api/posts {
+ ratelimit(200/min)
# ... read logic
}
# Write operations: moderate limit
@ POST /api/posts {
+ auth(jwt)
+ ratelimit(30/min)
# ... create logic
}
Validation
Validate input data using the ? validation operator.
Built-in Validators
@ POST /api/users
# Email validation
? validate_email(input.email)
# Length validation (min, max)
? validate_length(input.name, 1, 100)
? validate_length(input.password, 8, 128)
# Range validation (min, max)
? validate_range(input.age, 0, 150)
? validate_range(input.quantity, 1, 1000)
# ... rest of route
Multiple Validations
@ POST /api/products {
+ auth(jwt)
% db: Database
# Validate all input fields
? validate_length(input.name, 1, 200)
? validate_length(input.description, 0, 5000)
? validate_range(input.price, 0, 1000000)
? validate_range(input.stock, 0, 999999)
$ product = db.products.create({
name: input.name,
description: input.description,
price: input.price,
stock: input.stock,
created_at: now()
})
> {product: product}
}
Conditional Validation
@ POST /api/orders {
+ auth(jwt)
% db: Database
# Always validate quantity
? validate_range(input.quantity, 1, 100)
# Validate shipping address if not pickup
if input.delivery_type != "pickup" {
? validate_length(input.address, 10, 500)
? validate_length(input.city, 2, 100)
? validate_length(input.zip, 5, 10)
}
$ order = db.orders.create(input)
> {order: order}
}
WebSocket Routes
First-class WebSocket support for real-time applications.
Basic WebSocket Route
@ ws /chat {
on connect {
ws.send({type: "welcome", message: "Connected!"})
}
on message {
ws.broadcast({
type: "message",
text: input.text,
timestamp: now()
})
}
on disconnect {
ws.broadcast({type: "user_left"})
}
}
WebSocket with Rooms
@ ws /chat/:room {
on connect {
ws.join(room)
ws.broadcast_to_room(room, {
type: "user_joined",
room: room
})
}
on message {
ws.broadcast_to_room(room, {
type: "message",
text: input.text,
user: input.user,
room: room,
timestamp: now()
})
}
on disconnect {
ws.broadcast_to_room(room, {
type: "user_left",
room: room
})
ws.leave(room)
}
}
WebSocket Event Handlers
Handle different WebSocket lifecycle events.
on connect
Triggered when a client connects.
on connect {
# Initialize connection
ws.send({type: "connected", id: connection_id})
# Join default room
ws.join("lobby")
}
on message
Triggered when a message is received. Message data is in input.
on message {
# Process based on message type
if input.type == "chat" {
ws.broadcast({
type: "chat",
text: input.text,
user: input.user
})
}
if input.type == "join_room" {
ws.join(input.room)
ws.broadcast_to_room(input.room, {
type: "user_joined",
user: input.user
})
}
}
on disconnect
Triggered when a client disconnects.
on disconnect {
# Notify others
ws.broadcast({
type: "user_disconnected",
timestamp: now()
})
# Cleanup (automatic room leave)
}
WebSocket Operations
Built-in functions for WebSocket communication.
Sending Messages
| Function | Description |
|---|---|
ws.send(message) |
Send to current connection only |
ws.broadcast(message) |
Send to all connected clients |
ws.broadcast_to_room(room, message) |
Send to all clients in a specific room |
Room Management
| Function | Description |
|---|---|
ws.join(room) |
Join a room |
ws.leave(room) |
Leave a room |
Connection Control
| Function | Description |
|---|---|
ws.close(reason) |
Close connection with optional reason |
Broadcasting
# Broadcast to all connected clients
@ ws /notifications {
on message {
if input.type == "broadcast" {
ws.broadcast({
type: "notification",
message: input.message,
timestamp: now()
})
}
}
}
WebSocket Rooms
Organize connections into rooms for targeted messaging.
Room-Based Chat Example
@ ws /chat {
on connect {
ws.send({type: "connected"})
}
on message {
switch input.action {
case "join" {
ws.join(input.room)
ws.broadcast_to_room(input.room, {
type: "user_joined",
user: input.user,
room: input.room
})
}
case "leave" {
ws.broadcast_to_room(input.room, {
type: "user_left",
user: input.user,
room: input.room
})
ws.leave(input.room)
}
case "message" {
ws.broadcast_to_room(input.room, {
type: "message",
text: input.text,
user: input.user,
room: input.room,
timestamp: now()
})
}
default {
ws.send({type: "error", message: "Unknown action"})
}
}
}
on disconnect {
# Rooms are automatically cleaned up
}
}
Multi-Room Support
# Users can be in multiple rooms simultaneously
on message {
if input.action == "join_rooms" {
for room in input.rooms {
ws.join(room)
}
ws.send({type: "joined", rooms: input.rooms})
}
}
CLI Commands
Define command-line commands using the ! symbol.
Basic Command
# Database migration command
! migrate "Run database migrations" {
% db: Database
$ result = db.migrate()
> {status: "complete", migrations: result}
}
# Seed data command
! seed "Seed the database with test data" {
% db: Database
$ users = db.users.createMany([
{name: "Admin", email: "admin@example.com", role: "admin"},
{name: "User", email: "user@example.com", role: "user"}
])
> {created: length(users)}
}
Running Commands
# Run a command
glyph exec migrate
# Run with arguments
glyph exec seed --env=development
Cron Tasks
Schedule recurring tasks using the * symbol with cron expressions.
Basic Cron Task
# Run daily at midnight
* "0 0 * * *" cleanup {
% db: Database
$ deleted = db.sessions.deleteExpired()
> {deleted: deleted}
}
# Run every hour
* "0 * * * *" health_check {
$ status = checkServices()
if status != "ok" {
sendAlert("Service degraded")
}
}
Cron Expression Reference
| Expression | Description |
|---|---|
0 0 * * * |
Daily at midnight |
0 * * * * |
Every hour |
*/15 * * * * |
Every 15 minutes |
0 9 * * 1-5 |
Weekdays at 9 AM |
0 0 1 * * |
First day of each month |
Event Handlers
React to application events using the ~ symbol.
Basic Event Handler
# Handle user creation events
~ "user.created" on_user_created {
% email: EmailService
$ user = input.user
email.send({
to: user.email,
subject: "Welcome!",
template: "welcome"
})
}
# Handle order completion
~ "order.completed" on_order_complete {
% db: Database
% notifications: NotificationService
$ order = input.order
notifications.send(order.user_id, {
type: "order_shipped",
order_id: order.id
})
}
Event Patterns
# Wildcard event matching
~ "user.*" on_any_user_event {
% db: Database
$ event_type = input.event
db.event_logs.create({type: event_type, timestamp: now()})
}
# Specific event types
~ "payment.failed" on_payment_failed {
% db: Database
$ order = input.order
db.failed_payments.create({order_id: order.id, timestamp: now()})
}
Queue Workers
Process background jobs using the & symbol.
Basic Queue Worker
# Email queue worker
& "emails" email_worker {
% email: EmailService
$ job = input.job
email.send({
to: job.to,
subject: job.subject,
body: job.body
})
> {processed: true}
}
# Image processing queue
& "images" image_processor {
% storage: StorageService
$ job = input.job
$ processed = processImage(job.image, job.options)
storage.save(job.output_path, processed)
> {path: job.output_path}
}
Queue Operations
# Enqueue a job from a route
@ POST /api/send-email {
% queue: Queue
$ job = queue.push("emails", {
to: input.to,
subject: input.subject,
body: input.body
})
> {queued: true, job_id: job.id}
}
Database Injection
Inject database services into routes using the % symbol.
Basic Injection
@ GET /api/users {
% db: Database
$ users = db.users.all()
> {users: users}
}
Multiple Services
@ GET /api/data {
% db: Database
% cache: Cache
# Try cache first
$ cached = cache.get("users")
if cached != null {
> {users: cached, source: "cache"}
}
# Fall back to database
$ users = db.users.all()
> {users: users, source: "database"}
}
Database Collections
@ GET /api/dashboard {
% db: Database
# Access different collections
$ users = db.users.all()
$ posts = db.posts.all()
$ comments = db.comments.all()
> {
}
user_count: length(users),
post_count: length(posts),
comment_count: length(comments)
}
CRUD Operations
Complete Create, Read, Update, Delete operations.
Create
# Create a new record
$ user = db.users.create({
name: input.name,
email: input.email,
created_at: now()
})
# Returns the created record with generated ID
> {user: user}
Read
# Get by ID
$ user = db.users.get(id)
# Get all records
$ users = db.users.all()
# Find one by field
$ user = db.users.findOne("email", "alice@example.com")
# Count records
$ total = db.users.count()
Update
# Update by ID
$ updated = db.users.update(id, {
name: input.name,
updated_at: now()
})
# Returns the updated record
> {user: updated}
Delete
# Delete by ID
$ result = db.users.delete(id)
# Delete by condition
$ count = db.users.deleteWhere("status", "inactive")
> {deleted: true}
Complete CRUD Example
: Product {
id: int!
name: str!
price: float!
stock: int!
created_at: timestamp
}
# CREATE
@ POST /api/products {
+ auth(jwt)
% db: Database
? validate_length(input.name, 1, 200)
? validate_range(input.price, 0, 1000000)
$ product = db.products.create({
name: input.name,
price: input.price,
stock: input.stock,
created_at: now()
})
> {product: product}
}
# READ (all)
@ GET /api/products {
% db: Database
$ products = db.products.all()
> {products: products}
}
# READ (one)
@ GET /api/products/:id {
% db: Database
$ product = db.products.get(id)
> {product: product}
}
# UPDATE
@ PUT /api/products/:id {
+ auth(jwt)
% db: Database
$ product = db.products.update(id, input)
> {product: product}
}
# DELETE
@ DELETE /api/products/:id {
+ auth(jwt)
% db: Database
$ result = db.products.delete(id)
> {deleted: true}
}
Querying
Advanced database query operations.
Filter by Field
# Filter by exact match
$ active_users = db.users.filter("status", "active")
# Filter by multiple conditions (chain)
$ admins = db.users.filter("role", "admin")
Filter Array Contains
# Find records where array field contains value
$ tagged = db.posts.filterArray("tags", "golang")
Sorting
# Sort ascending
$ oldest = db.users.sortBy("created_at", "asc")
# Sort descending
$ newest = db.users.sortBy("created_at", "desc")
# Sort by views
$ popular = db.posts.sortBy("views", "desc")
Combined Queries
@ GET /api/posts/popular {
% db: Database
# Get active posts sorted by views
$ posts = db.posts.filter("status", "published")
$ sorted = posts.sortBy("views", "desc")
$ top10 = sorted.paginate(0, 10)
> {posts: top10}
}
Pagination
Paginate large result sets efficiently.
Basic Pagination
# paginate(offset, limit)
$ page1 = db.users.paginate(0, 10) # First 10
$ page2 = db.users.paginate(10, 10) # Next 10
$ page3 = db.users.paginate(20, 10) # Next 10
Paginated API Endpoint
@ GET /api/users {
% db: Database
# Get pagination params from query string
$ page = input.page
$ per_page = input.per_page
# Default values
if page == null {
$ page = 1
}
if per_page == null {
$ per_page = 20
}
# Calculate offset
$ offset = (page - 1) * per_page
# Get total count and paginated data
$ total = db.users.count()
$ users = db.users.paginate(offset, per_page)
# Calculate pagination metadata
$ total_pages = (total + per_page - 1) / per_page
$ has_more = page < total_pages
> {
}
users: users,
pagination: {
page: page,
per_page: per_page,
total: total,
total_pages: total_pages,
has_more: has_more
}
}
Cursor-Based Pagination
@ GET /api/posts {
% db: Database
$ cursor = input.cursor
$ limit = 20
if cursor != null {
# Get posts after cursor
$ posts = db.posts.filter("id", ">", cursor).paginate(0, limit)
} else {
# Get first page
$ posts = db.posts.sortBy("id", "asc").paginate(0, limit)
}
# Get next cursor
$ next_cursor = null
if length(posts) == limit {
$ last = posts[length(posts) - 1]
$ next_cursor = last.id
}
> {
}
posts: posts,
next_cursor: next_cursor
}
Transactions
GlyphLang supports database transactions for executing multiple operations atomically. If any operation fails, all changes are rolled back to maintain data consistency.
Basic Transaction
Wrap multiple database operations in a transaction block:
@ POST /api/transfer {
+ auth(jwt)
% db: Database
$ fromAccount = db.accounts.get(input.from_id)
$ toAccount = db.accounts.get(input.to_id)
if fromAccount == null || toAccount == null {
> {error: "Account not found", code: 404}
}
if fromAccount.balance < input.amount {
> {error: "Insufficient funds", code: 400}
}
# Execute transfer in a transaction
$ result = db.transaction({
# Debit from source account
$ fromAccount.balance = fromAccount.balance - input.amount
$ db.accounts.update(input.from_id, fromAccount)
# Credit to destination account
$ toAccount.balance = toAccount.balance + input.amount
$ db.accounts.update(input.to_id, toAccount)
# Record the transfer
$ transfer = db.transfers.create({
from_id: input.from_id,
to_id: input.to_id,
amount: input.amount,
created_at: now()
})
> transfer
})
> {success: true, transfer: result}
}
Automatic Rollback
If any operation in a transaction fails, all changes are automatically rolled back:
@ POST /api/orders {
+ auth(jwt)
% db: Database
$ result = db.transaction({
# Create the order
$ order = db.orders.create({
user_id: auth.user.id,
status: "pending",
created_at: now()
})
# Add order items and update inventory
for item in input.items {
$ product = db.products.get(item.product_id)
# This will fail and rollback if inventory is insufficient
if product.inventory < item.quantity {
> {error: "Insufficient inventory for " + product.name}
}
# Deduct from inventory
$ product.inventory = product.inventory - item.quantity
$ db.products.update(item.product_id, product)
# Create order item
$ db.order_items.create({
order_id: order.id,
product_id: item.product_id,
quantity: item.quantity,
price: product.price
})
}
> order
})
if result.error != null {
> {success: false, error: result.error}
}
> {success: true, order: result}
}
Transaction with Error Handling
Handle transaction errors gracefully:
@ POST /api/users/:id/upgrade {
+ auth(jwt, role: admin)
% db: Database
$ result = db.transaction({
# Get user
$ user = db.users.get(id)
if user == null {
> {error: "User not found", rollback: true}
}
# Update user plan
$ user.plan = "premium"
$ user.upgraded_at = now()
$ db.users.update(id, user)
# Create subscription record
$ subscription = db.subscriptions.create({
user_id: id,
plan: "premium",
starts_at: now(),
expires_at: now() + (365 * 24 * 60 * 60) # 1 year
})
# Send welcome email (external service)
$ emailSent = email.send(user.email, "Welcome to Premium!")
if emailSent == false {
# Log but don't rollback - email can be retried
$ db.email_queue.create({
to: user.email,
template: "premium_welcome",
status: "pending"
})
}
> {user: user, subscription: subscription}
})
> result
}
Nested Operations
Transactions ensure all nested operations succeed or fail together:
@ DELETE /api/users/:id {
+ auth(jwt, role: admin)
% db: Database
$ result = db.transaction({
# Delete user's related data first
$ db.orders.deleteWhere("user_id", id)
$ db.sessions.deleteWhere("user_id", id)
$ db.preferences.deleteWhere("user_id", id)
$ db.notifications.deleteWhere("user_id", id)
# Finally delete the user
$ user = db.users.get(id)
$ db.users.delete(id)
> {deleted_user: user}
})
> {success: true, result: result}
}
Transaction Isolation
Control how transactions interact with concurrent operations:
# Read committed (default) - see committed changes from other transactions
$ result = db.transaction({
# operations
})
# Serializable - strictest isolation, prevents all anomalies
$ result = db.transaction({
isolation: "serializable"
}, {
# operations that require strict consistency
})
# Repeatable read - consistent reads within transaction
$ result = db.transaction({
isolation: "repeatable_read"
}, {
# operations
})
Transaction Features
- Atomicity: All operations succeed or all are rolled back
- Automatic rollback: Errors trigger automatic rollback
- Nested operations: All nested DB calls are part of the transaction
- Error handling: Return error objects to trigger rollback
- Isolation levels: Control visibility of concurrent changes
- Performance: Batch operations for better performance
Migrations
GlyphLang provides schema management functions for database migrations. Create, modify, and manage your database schema programmatically.
Creating Tables
Define table schemas with column types and constraints:
# Define schema for users table
$ userSchema = {
id: "SERIAL PRIMARY KEY",
username: "VARCHAR(100) NOT NULL UNIQUE",
email: "VARCHAR(255) NOT NULL UNIQUE",
password_hash: "VARCHAR(255) NOT NULL",
role: "VARCHAR(50) DEFAULT 'user'",
active: "BOOLEAN DEFAULT true",
created_at: "TIMESTAMP DEFAULT CURRENT_TIMESTAMP",
updated_at: "TIMESTAMP"
}
# Create the table
$ db.createTable("users", userSchema)
# Define schema for posts table with foreign key
$ postSchema = {
id: "SERIAL PRIMARY KEY",
user_id: "INTEGER REFERENCES users(id) ON DELETE CASCADE",
title: "VARCHAR(255) NOT NULL",
content: "TEXT",
published: "BOOLEAN DEFAULT false",
published_at: "TIMESTAMP",
created_at: "TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
}
$ db.createTable("posts", postSchema)
Migration Files
Organize migrations in numbered files for version control:
# migrations/001_create_users.glyph
! migrate_up(db: Database) {
$ schema = {
id: "SERIAL PRIMARY KEY",
username: "VARCHAR(100) NOT NULL UNIQUE",
email: "VARCHAR(255) NOT NULL UNIQUE",
password_hash: "VARCHAR(255) NOT NULL",
created_at: "TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
}
$ db.createTable("users", schema)
}
! migrate_down(db: Database) {
$ db.dropTable("users")
}
# migrations/002_create_posts.glyph
! migrate_up(db: Database) {
$ schema = {
id: "SERIAL PRIMARY KEY",
user_id: "INTEGER REFERENCES users(id)",
title: "VARCHAR(255) NOT NULL",
content: "TEXT",
created_at: "TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
}
$ db.createTable("posts", schema)
}
! migrate_down(db: Database) {
$ db.dropTable("posts")
}
Checking Table Existence
Check if tables exist before creating or modifying:
! ensureSchema(db: Database) {
# Check and create users table
$ usersExists = db.tableExists("users")
if usersExists == false {
$ db.createTable("users", {
id: "SERIAL PRIMARY KEY",
username: "VARCHAR(100) NOT NULL",
email: "VARCHAR(255) NOT NULL"
})
}
# Check and create posts table
$ postsExists = db.tableExists("posts")
if postsExists == false {
$ db.createTable("posts", {
id: "SERIAL PRIMARY KEY",
user_id: "INTEGER REFERENCES users(id)",
title: "VARCHAR(255) NOT NULL"
})
}
}
Adding Columns
Add new columns to existing tables:
# Add a single column
$ db.addColumn("users", "avatar_url", "VARCHAR(500)")
# Add column with default value
$ db.addColumn("users", "verified", "BOOLEAN DEFAULT false")
# Add column with NOT NULL (requires default for existing rows)
$ db.addColumn("posts", "slug", "VARCHAR(255) NOT NULL DEFAULT ''")
# Multiple columns in a migration
! migrate_003_add_user_profile(db: Database) {
$ db.addColumn("users", "bio", "TEXT")
$ db.addColumn("users", "website", "VARCHAR(255)")
$ db.addColumn("users", "location", "VARCHAR(100)")
$ db.addColumn("users", "avatar_url", "VARCHAR(500)")
}
Creating Indexes
Add indexes for query performance:
# Create index on single column
$ db.createIndex("users", "idx_users_email", ["email"])
# Create composite index
$ db.createIndex("posts", "idx_posts_user_published", ["user_id", "published"])
# Create unique index
$ db.createIndex("users", "idx_users_username", ["username"], {unique: true})
# Migration with indexes
! migrate_004_add_indexes(db: Database) {
$ db.createIndex("posts", "idx_posts_created_at", ["created_at"])
$ db.createIndex("posts", "idx_posts_user_id", ["user_id"])
$ db.createIndex("orders", "idx_orders_status", ["status"])
}
Dropping Tables and Columns
Remove tables and columns when needed:
# Drop a table (use with caution!)
$ db.dropTable("old_table")
# Drop table if exists
$ exists = db.tableExists("temp_table")
if exists {
$ db.dropTable("temp_table")
}
# Drop a column
$ db.dropColumn("users", "deprecated_field")
# Rollback migration
! migrate_down_005(db: Database) {
$ db.dropColumn("users", "avatar_url")
$ db.dropColumn("users", "bio")
}
Migration Runner
Track and run migrations in order:
# Create migrations tracking table
! initMigrations(db: Database) {
$ exists = db.tableExists("migrations")
if exists == false {
$ db.createTable("migrations", {
id: "SERIAL PRIMARY KEY",
name: "VARCHAR(255) NOT NULL UNIQUE",
executed_at: "TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
})
}
}
# Check if migration has been run
! migrationExists(db: Database, name: string): bool {
$ result = db.migrations.findOne("name", name)
> result != null
}
# Run a migration
! runMigration(db: Database, name: string, fn: function) {
$ exists = migrationExists(db, name)
if exists == false {
$ fn(db)
$ db.migrations.create({name: name})
}
}
# Usage
@ GET /api/admin/migrate {
+ auth(jwt, role: admin)
% db: Database
$ initMigrations(db)
$ runMigration(db, "001_create_users", migrate_001_up)
$ runMigration(db, "002_create_posts", migrate_002_up)
$ runMigration(db, "003_add_indexes", migrate_003_up)
> {success: true, message: "Migrations complete"}
}
Safe Migration Patterns
Best practices for production migrations:
# Always check before modifying
! safeMigration(db: Database) {
# Wrap in transaction for atomicity
$ db.transaction({
# Check preconditions
$ usersExists = db.tableExists("users")
if usersExists == false {
> {error: "users table must exist first"}
}
# Add new column with safe default
$ db.addColumn("users", "new_field", "VARCHAR(100) DEFAULT ''")
# Backfill data if needed
$ db.query("UPDATE users SET new_field = 'default' WHERE new_field = ''")
> {success: true}
})
}
# Reversible migration
! migrate_005(db: Database, direction: string) {
if direction == "up" {
$ db.addColumn("users", "preferences", "JSONB DEFAULT '{}'")
$ db.createIndex("users", "idx_users_preferences", ["preferences"])
} else {
$ db.dropIndex("users", "idx_users_preferences")
$ db.dropColumn("users", "preferences")
}
}
Migration Best Practices
- Version control: Number migrations sequentially (001, 002, etc.)
- Reversibility: Always write both up and down migrations
- Idempotency: Check if changes exist before applying
- Transactions: Wrap schema changes in transactions
- Safe defaults: Use DEFAULT values for new NOT NULL columns
- Tracking: Record executed migrations in a tracking table
Core Built-in Functions
Time Functions
| Function | Description | Example |
|---|---|---|
now() |
Get current Unix timestamp | $ ts = now() |
time.now() |
Alias for now() | $ ts = time.now() |
Collection Functions
| Function | Description | Example |
|---|---|---|
length(value) |
Get length of array or string | $ len = length(items) |
Usage Examples
@ GET /api/example {
% db: Database
# Get current timestamp
$ created_at = now()
# Get array length
$ users = db.users.all()
$ count = length(users)
# Get string length
$ name = "Hello World"
$ char_count = length(name)
> {
}
timestamp: created_at,
user_count: count,
name_length: char_count
}
String Functions
Comprehensive string manipulation and utility functions.
String Functions Reference
| Function | Description | Example |
|---|---|---|
length(str) |
Returns string length | $ len = length("hello") returns 5 |
startsWith(str, prefix) |
Check if string starts with prefix | $ ok = startsWith("hello", "he") returns true |
endsWith(str, suffix) |
Check if string ends with suffix | $ ok = endsWith("hello", "lo") returns true |
indexOf(str, substr) |
Find position of substring (-1 if not found) | $ pos = indexOf("hello", "ll") returns 2 |
charAt(str, index) |
Get character at position | $ ch = charAt("hello", 1) returns "e" |
String Examples
# String length
$ text = "Hello, World!"
$ len = length(text) # 13
# String concatenation using + operator
$ greeting = "Hello, " + name + "!"
$ message = "User " + user.name + " created at " + user.created_at
# String prefix/suffix checking
$ email = "user@example.com"
if endsWith(email, "@example.com") {
$ domain = "example"
}
# Finding substrings
$ path = "/api/users/123"
$ pos = indexOf(path, "users") # 5
# Character access
$ first = charAt(path, 0) # "/"
Type Conversion Functions
| Function | Description | Example |
|---|---|---|
parseInt(str) |
Parse string to integer | $ num = parseInt("42") returns 42 |
parseFloat(str) |
Parse string to float | $ num = parseFloat("3.14") returns 3.14 |
toString(value) |
Convert any value to string | $ str = toString(123) returns "123" |
Math Functions
| Function | Description | Example |
|---|---|---|
abs(num) |
Absolute value | $ val = abs(-5) returns 5 |
min(a, b) |
Minimum of two values | $ val = min(10, 3) returns 3 |
max(a, b) |
Maximum of two values | $ val = max(10, 3) returns 10 |
Conversion and Math Examples
# Parsing user input
@ POST /api/calculate {
$ amount = parseInt(input.amount)
$ rate = parseFloat(input.rate)
$ result = amount * rate
> {result: result}
}
# Using math functions
$ balance = -100
$ abs_balance = abs(balance) # 100
$ limit = min(input.quantity, 100) # Cap at 100
$ bonus = max(score - 50, 0) # At least 0
# String conversion
$ order_id = 12345
$ message = "Order #" + toString(order_id) + " confirmed"
String in Conditions
if user.role == "admin" {
$ access = "full"
}
if status != "active" {
> {error: "Account inactive"}
}
SQL Injection Prevention
GlyphLang automatically scans for and prevents SQL injection vulnerabilities.
Built-in Protection
Automatic Security
All database operations are automatically parameterized. Direct string concatenation in queries is detected and blocked at compile time.
Safe Query Patterns
# SAFE: Using parameterized methods
$ user = db.users.get(id)
$ users = db.users.filter("status", status)
$ result = db.users.findOne("email", email)
# SAFE: Input is automatically sanitized
@ GET /api/search {
% db: Database
$ results = db.products.filter("name", input.query)
> {results: results}
}
Detected Patterns
# These patterns are DETECTED and BLOCKED:
# - SELECT * FROM users WHERE id = ' + id
# - DROP TABLE; --
# - ' OR '1'='1
# - UNION SELECT
# The compiler will reject code with SQL injection patterns
XSS Prevention
Cross-Site Scripting (XSS) attacks are automatically detected.
Automatic Detection
XSS Scanning
GlyphLang scans for common XSS patterns in string literals and user input handling. Dangerous patterns are flagged at compile time.
Detected Patterns
# These patterns are DETECTED:
# - <script>...</script>
# - javascript:...
# - on*= event handlers (onclick, onerror, etc.)
# - <img src=x onerror=...>
# User input containing these patterns triggers warnings
Safe Output
# JSON responses are automatically escaped
@ GET /api/users/:id {
% db: Database
$ user = db.users.get(id)
> {user: user} # All string values are safely encoded
}
Input Validation
Comprehensive input validation framework.
Validation Functions
| Function | Description | Example |
|---|---|---|
validate_email(value) |
Validate email format | ? validate_email(input.email) |
validate_length(value, min, max) |
Validate string length | ? validate_length(input.name, 1, 100) |
validate_range(value, min, max) |
Validate numeric range | ? validate_range(input.age, 0, 150) |
Complete Validation Example
@ POST /api/users {
% db: Database
# Validate all input fields
? validate_email(input.email)
? validate_length(input.name, 2, 100)
? validate_length(input.bio, 0, 500)
? validate_range(input.age, 13, 120)
# Check for existing user
$ existing = db.users.findOne("email", input.email)
if existing != null {
> {error: "Email already registered", code: 400}
}
# Create user with validated input
$ user = db.users.create({
name: input.name,
email: input.email,
bio: input.bio,
age: input.age,
created_at: now()
})
> {success: true, user: {id: user.id, name: user.name}}
}
Observability
GlyphLang provides built-in observability features including structured logging, Prometheus metrics, and OpenTelemetry distributed tracing to monitor and debug your applications in production.
Logging
Use the logging middleware to record request details with configurable log levels:
# Logging is automatically applied in dev mode
# Logs include: method, path, status code, duration
# Example log output:
# [REQUEST] GET /api/users
# [RESPONSE] GET /api/users - 200 (2.5ms)
Log Levels
GlyphLang supports five log levels:
# Log levels from least to most severe:
# DEBUG - Detailed debugging information
# INFO - General operational messages
# WARN - Warning conditions
# ERROR - Error conditions
# FATAL - Critical errors requiring shutdown
Structured Logging
Logs can be output in JSON format for log aggregation tools:
{
"timestamp": "2024-01-15T10:30:45.123Z",
"level": "INFO",
"message": "Request processed",
"request_id": "abc-123-def",
"fields": {
"method": "GET",
"path": "/api/users",
"status": 200,
"duration_ms": 2.5
}
}
Request Logging in Routes
Add custom logging within your route handlers:
@ POST /api/orders {
+ auth(jwt)
% db: Database
% logger: Logger
# Log incoming request
$ logger.info("Creating order", {
user_id: auth.user.id,
items_count: input.items.length()
})
$ order = db.orders.create({
user_id: auth.user.id,
items: input.items
})
# Log success
$ logger.info("Order created", {
order_id: order.id,
total: order.total
})
> {success: true, order: order}
}
Prometheus Metrics
GlyphLang exposes Prometheus-compatible metrics for monitoring request rates, latencies, and errors:
Built-in Metrics
# Request metrics (automatically collected)
glyphlang_http_requests_total{method="GET",path="/api/users",status="200"}
glyphlang_http_request_duration_seconds{method="GET",path="/api/users",status="200"}
glyphlang_http_request_errors_total{method="POST",path="/api/orders",status="500"}
# Resource metrics
glyphlang_goroutines
glyphlang_memory_alloc_bytes
glyphlang_memory_total_bytes
glyphlang_gc_pause_ns
Exposing Metrics Endpoint
# Metrics are exposed at /metrics by default
# Configure in your application:
@ GET /metrics {
# Returns Prometheus-formatted metrics
> metrics.export()
}
# Or use the built-in metrics endpoint:
# glyph run main.glyph --metrics-port 9090
Custom Business Metrics
@ POST /api/orders {
+ auth(jwt)
% db: Database
% metrics: MetricsService
$ order = db.orders.create(input)
# Increment custom counter
$ metrics.increment("orders_created_total", {
category: order.category,
payment_method: order.payment_method
})
# Record custom histogram (order value)
$ metrics.observe("order_value_dollars", order.total, {
category: order.category
})
# Set gauge for active orders
$ activeCount = db.orders.count("status", "pending")
$ metrics.set("active_orders", activeCount)
> {success: true, order: order}
}
Distributed Tracing
GlyphLang supports OpenTelemetry for distributed tracing across services:
Trace Configuration
# Tracing is configured via environment variables or config
# Environment variables:
# OTEL_SERVICE_NAME=my-api
# OTEL_EXPORTER_OTLP_ENDPOINT=localhost:4317
# OTEL_TRACES_SAMPLER_ARG=0.1 (10% sampling)
# Or configure in code:
$ tracingConfig = {
service_name: "my-api",
service_version: "1.0.0",
environment: "production",
exporter: "otlp", # or "stdout" for dev
endpoint: "localhost:4317",
sampling_rate: 0.1 # Sample 10% of requests
}
Automatic Request Tracing
# Each request automatically creates a trace span
# Spans include: method, path, status, duration
@ GET /api/users/:id {
% db: Database
# Database queries are automatically traced
$ user = db.users.get(id)
# External API calls are traced
$ enriched = api.enrich(user)
> enriched
}
# Trace output includes:
# - HTTP request span (parent)
# - Database query span (child)
# - External API span (child)
Custom Spans
@ POST /api/process {
% tracer: Tracer
# Create a custom span for complex operations
$ tracer.startSpan("process_data", {
attributes: {
data_size: input.data.length(),
format: input.format
}
})
# Processing logic
$ result = processData(input)
# End span with status
$ tracer.endSpan({status: "ok"})
> {result: result}
}
Health Checks
Implement health check endpoints for load balancers and orchestrators:
# Liveness probe - is the service running?
@ GET /health/live {
> {status: "ok", timestamp: now()}
}
# Readiness probe - is the service ready to accept traffic?
@ GET /health/ready {
% db: Database
% cache: Cache
$ dbHealthy = db.ping()
$ cacheHealthy = cache.ping()
if dbHealthy && cacheHealthy {
> {
status: "ready",
checks: {
database: "ok",
cache: "ok"
}
}
} else {
> {
status: "not_ready",
checks: {
database: dbHealthy,
cache: cacheHealthy
}
}
}
}
# Detailed health with metrics
@ GET /health {
> {
status: "ok",
version: "1.0.0",
uptime: server.uptime(),
memory: {
used: server.memoryUsed(),
total: server.memoryTotal()
},
requests: {
total: metrics.get("requests_total"),
errors: metrics.get("request_errors_total")
}
}
}
Observability Features
- Logging: Structured logs with levels (DEBUG, INFO, WARN, ERROR, FATAL)
- JSON format: Machine-readable logs for aggregation tools
- Prometheus metrics: Request rates, latencies, errors, custom metrics
- OpenTelemetry: Distributed tracing with automatic span creation
- Health checks: Liveness and readiness probes for orchestration
- Request IDs: Correlation IDs for tracking requests across services
CORS
Cross-Origin Resource Sharing (CORS) controls which domains can access your API from browsers. GlyphLang provides built-in CORS middleware with secure defaults.
Basic CORS Configuration
Enable CORS with the cors middleware:
# Allow specific origins
@ GET /api/data {
+ cors(["https://example.com", "https://app.example.com"])
> {data: "accessible from allowed origins"}
}
# Allow all origins (development only)
@ GET /api/public {
+ cors(["*"])
> {data: "accessible from any origin"}
}
CORS Headers
The CORS middleware automatically sets these headers:
# Response headers set by CORS middleware:
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
Preflight Requests
The middleware automatically handles OPTIONS preflight requests:
# Browser sends OPTIONS request before actual request
# for non-simple requests (custom headers, PUT/DELETE, etc.)
# GlyphLang automatically responds with:
# - 204 No Content status
# - CORS headers
# - No body
# Your route only handles the actual request
@ PUT /api/users/:id {
+ cors(["https://app.example.com"])
+ auth(jwt)
# This only runs for PUT requests, not OPTIONS
$ updated = db.users.update(id, input)
> {success: true, user: updated}
}
Credentials and Cookies
Configure CORS for requests with credentials:
# When using specific origins, credentials are allowed
@ POST /api/auth/login {
+ cors(["https://app.example.com"])
# Browser can send cookies with this request
# Response includes: Access-Control-Allow-Credentials: true
$ token = authenticate(input)
> {token: token}
}
# SECURITY: Wildcard origin disables credentials
@ GET /api/public {
+ cors(["*"])
# Credentials are automatically disabled
# Response includes: Access-Control-Allow-Credentials: false
> {data: "public data"}
}
Multiple Origins
Allow access from multiple specific origins:
# Production configuration with multiple allowed origins
@ GET /api/data {
+ cors([
"https://example.com",
"https://www.example.com",
"https://app.example.com",
"https://admin.example.com"
])
> db.data.all()
}
# Development and staging
@ GET /api/data {
+ cors([
"http://localhost:3000",
"http://localhost:8080",
"https://staging.example.com"
])
> db.data.all()
}
Security Headers
GlyphLang includes additional security headers middleware:
# Security headers are added automatically:
# X-Content-Type-Options: nosniff
# X-Frame-Options: DENY
# X-XSS-Protection: 1; mode=block
# Referrer-Policy: strict-origin-when-cross-origin
@ GET /api/secure {
+ cors(["https://app.example.com"])
+ securityHeaders()
> {data: "protected"}
}
Environment-Based CORS
Configure different CORS settings per environment:
# Define allowed origins based on environment
$ allowedOrigins = match config.environment {
"development" => [
"http://localhost:3000",
"http://localhost:8080"
]
"staging" => [
"https://staging.example.com",
"https://staging-app.example.com"
]
"production" => [
"https://example.com",
"https://app.example.com"
]
_ => []
}
@ GET /api/data {
+ cors(allowedOrigins)
> db.data.all()
}
CORS Security Best Practices
# DO: Use specific origins in production
+ cors(["https://app.example.com"])
# DON'T: Use wildcard in production with sensitive data
+ cors(["*"]) # Only for truly public APIs
# DO: Restrict methods if not all are needed
+ cors(["https://app.example.com"], {
methods: ["GET", "POST"]
})
# DO: Limit exposed headers
+ cors(["https://app.example.com"], {
expose_headers: ["X-Request-Id"]
})
CORS Features
- Origin validation: Only allow specified domains
- Preflight handling: Automatic OPTIONS request responses
- Credentials: Enabled for specific origins, disabled for wildcards
- Security headers: X-Frame-Options, X-Content-Type-Options, etc.
- Cache control: Max-Age header for preflight caching (24 hours)
- Multiple origins: Support for lists of allowed domains
Glyph CLI Tool
The glyph command-line tool provides everything you need to develop, compile, and run Glyph applications.
Available Commands
glyph dev- Development server with hot reloadglyph run- Run in production modeglyph compile- Compile to bytecodeglyph decompile- Decompile bytecodeglyph expand- Convert compact symbols to human-readable keywordsglyph compact- Convert keywords back to compact symbolsglyph context- Generate AI-optimized project contextglyph validate- Validate source with structured errorsglyph codegen- Generate server code for a target languageglyph openapi- Generate OpenAPI 3.0 specificationglyph docs- Generate API documentationglyph client- Generate typed API client codeglyph test- Run tests defined in a Glyph fileglyph repl- Start interactive REPL sessionglyph exec- Execute CLI commandsglyph commands- List available commandsglyph ir- Export service IR as JSON or catalogglyph init- Initialize new projectglyph lsp- Start language server
dev / run
Start your Glyph application in development or production mode.
Development Mode
# Start with hot reload
glyph dev main.glyph
# Custom port
glyph dev main.glyph -p 8080
# Open browser automatically
glyph dev main.glyph --open
Development mode features:
- Automatic hot reload on file changes
- Live browser refresh via Server-Sent Events
- Pretty colored output for requests
- Graceful shutdown with Ctrl+C
Production Mode
# Run source file
glyph run main.glyph
# Run pre-compiled bytecode
glyph run app.glybc --bytecode
# Use interpreter mode
glyph run main.glyph --interpret
compile / decompile
Compile Glyph source to bytecode or decompile bytecode back to readable format.
Compile to Bytecode
# Basic compilation
glyph compile main.glyph
# Custom output file
glyph compile main.glyph -o build/app.glybc
# Maximum optimization
glyph compile main.glyph -O 3
Decompile Bytecode
# Decompile to .glyph
glyph decompile app.glybc
# Show disassembly only
glyph decompile app.glybc --disasm
expand / compact
Convert between compact symbol syntax (.glyph) and human-readable keyword syntax (.glyphx). Both formats produce identical behavior.
Expand (Symbols to Keywords)
# Expand a single file
glyph expand main.glyph
# Write to specific output file
glyph expand main.glyph -o main.glyphx
# Expand all .glyph files in a directory
glyph expand ./src
# Watch for changes and auto-expand
glyph expand main.glyph --watch
Compact (Keywords to Symbols)
# Compact a single file
glyph compact main.glyphx
# Write to specific output file
glyph compact main.glyphx -o main.glyph
# Compact all .glyphx files in a directory
glyph compact ./src
# Watch for changes and auto-compact
glyph compact main.glyphx --watch
Symbol to Keyword Mapping
| Symbol | Keyword | Meaning |
|---|---|---|
@ | route | HTTP route |
$ | let | Variable binding |
> | return | Response return |
: | type | Type definition |
% | inject | Dependency injection |
+ | use | Middleware |
? | validate | Validation |
~ | on | Event handler |
* | cron | Scheduled task |
& | queue | Queue worker |
context (AI-First)
Generate AI-optimized project context for AI agents working with Glyph codebases. This command produces compact, cacheable representations that minimize token usage.
Designed for AI Agents
The context command generates structured output that AI models can efficiently parse, enabling smarter code generation and understanding of your project.
Basic Usage
# Generate context for current directory
glyph context
# Generate for specific directory
glyph context ./src
# Generate for a single file
glyph context --file main.glyph
Output Formats
# JSON format (default) - full structured context
glyph context --format json
# Compact format - minimal text for token efficiency
glyph context --format compact
# Stubs format - type stub file format
glyph context --format stubs
Incremental Updates
# Save context for future diffing
glyph context --save
# Show only changes since last save
glyph context --changed
Task-Targeted Context
# Focus on routes only
glyph context --for route
# Focus on types only
glyph context --for type
# Focus on functions only
glyph context --for function
# Focus on CLI commands only
glyph context --for command
Example Output (JSON)
{
"version": "1.0",
"project_hash": "a1b2c3d4...",
"types": {
"User": {
"fields": ["id: int!", "name: str!", "email: str?"],
"hash": "e5f6g7h8..."
}
},
"routes": [
{
"method": "GET",
"path": "/api/users/:id",
"params": ["id"],
"returns": "User | Error",
"auth": "jwt"
}
],
"patterns": ["crud", "auth_usage", "database_routes"]
}
validate (AI-First)
Validate Glyph source files with structured, AI-friendly error output. Returns detailed error information that AI agents can parse and act upon.
Structured Errors for AI
The --ai flag outputs JSON with error types, precise locations, and fix hints that AI models can use to automatically correct issues.
Basic Usage
# Human-readable output
glyph validate main.glyph
# Structured JSON for AI agents
glyph validate main.glyph --ai
# Validate all files in directory
glyph validate src/ --ai
# Treat warnings as errors
glyph validate main.glyph --strict
Error Types
| Error Type | Description |
|---|---|
syntax_error |
Parser syntax errors |
lexer_error |
Tokenization errors |
undefined_reference |
Reference to undefined type/variable |
type_mismatch |
Type incompatibility |
duplicate_definition |
Duplicate type/route definition |
invalid_route |
Invalid route configuration |
missing_required |
Missing required field or parameter |
Example Output (AI Mode)
{
"valid": false,
"file_path": "main.glyph",
"errors": [
{
"type": "undefined_reference",
"message": "undefined type: UserProfile",
"location": {
"file": "main.glyph",
"line": 15,
"column": 12
},
"fix_hint": "define type 'UserProfile' or import it",
"context": " $ user: UserProfile = ...",
"severity": "error"
}
],
"stats": {
"types": 3,
"routes": 5,
"functions": 2,
"lines": 87
}
}
exec / commands
Execute and list CLI commands defined in your Glyph source files.
List Available Commands
glyph commands main.glyph
Execute Commands
# Simple command
glyph exec main.glyph hello --name="Alice"
# Command with multiple arguments
glyph exec main.glyph add --a=5 --b=3
# Command with optional flags
glyph exec main.glyph greet --name="Bob" --formal
codegen
Generate a complete server application from Glyph source code in a target language. Parses the .glyph file, transforms it through the Semantic IR, and produces working server code.
Basic Usage
# Output Python/FastAPI code to stdout
glyph codegen main.glyph
# Generate Python project in a directory
glyph codegen main.glyph --lang python -o ./my-api
# Generate TypeScript/Express project
glyph codegen main.glyph --lang typescript -o ./my-api
Output Structure
When using --output, the command generates a project directory. The structure depends on the target language:
# Python (--lang python)
my-api/
main.py # FastAPI application with routes, models, providers
requirements.txt # Python dependencies
# TypeScript (--lang typescript)
my-api/
src/
app.ts # Express application with routes, interfaces, providers
package.json # npm dependencies
tsconfig.json # TypeScript configuration
Supported Languages
| Language | Flag | Framework |
|---|---|---|
| Python | --lang python | FastAPI + Pydantic |
| TypeScript | --lang typescript | Express + TypeScript |
Polyglot Code Generation
GlyphLang's code generation pipeline transforms a single .glyph source file into complete, working server applications in multiple target languages. The same API definition produces identical behavior whether deployed as Python/FastAPI or TypeScript/Express.
How It Works
The pipeline has three stages:
.glyph source → AST → Semantic IR → Target Language
├── Python / FastAPI
└── TypeScript / Express
The Semantic IR is a language-neutral intermediate representation that captures types, routes, providers, cron jobs, events, and queue workers. Each code generator reads the same IR and produces idiomatic output for its target.
Example: One Source, Two Servers
Given this Glyph source:
: Todo {
id: int!
title: str!
done: bool!
}
@ GET /api/todos {
% db: Database
$ todos = db.todos.Find()
> todos
}
@ POST /api/todos {
< input: Todo
% db: Database
$ created = db.todos.Create(input)
> created
}
Running glyph codegen app.glyph --lang python produces:
from fastapi import FastAPI, Depends
from pydantic import BaseModel
class Todo(BaseModel):
id: int
title: str
done: bool
app = FastAPI()
@app.get("/api/todos")
async def get_api_todos(db=Depends(get_db)):
todos = db.todos.Find()
return todos
@app.post("/api/todos")
async def post_api_todos(input: Todo, db=Depends(get_db)):
created = db.todos.Create(input)
return created
Running glyph codegen app.glyph --lang typescript produces:
import express, { Request, Response } from 'express';
interface Todo {
id: number;
title: string;
done: boolean;
}
const app = express();
app.use(express.json());
app.get('/api/todos', async (req: Request, res: Response) => {
const db = getDb();
const todos = db.todos.Find();
return res.json(todos);
});
app.post('/api/todos', async (req: Request, res: Response) => {
const db = getDb();
const input: Todo = req.body;
const created = db.todos.Create(input);
return res.json(created);
});
What Gets Generated
Both targets generate complete, runnable projects including:
- Type definitions — Pydantic models (Python) or TypeScript interfaces
- Route handlers — with path parameters, input body typing, and JSON responses
- Provider stubs — Database, Redis, MongoDB, LLM, and custom providers
- Cron jobs — APScheduler (Python) or node-cron (TypeScript)
- Event handlers — EventEmitter-based async event processing
- Queue workers — Task queue worker stubs
- Project files —
requirements.txt/package.json+tsconfig.json
Custom Providers
In addition to built-in providers (Database, Redis, MongoDB, LLM), you can define custom provider contracts with typed method signatures. Custom providers are supported in both the runtime and code generation targets.
Defining a Provider
Use the provider keyword to declare a provider contract:
provider EmailService {
send(to: str!, subject: str!, body: str!) -> EmailStatus
status(message_id: str!) -> EmailStatus
}
provider PaymentGateway {
charge(amount: int!, currency: str!, token: str!) -> ChargeResult
refund(charge_id: str!) -> RefundResult
}
Injecting Custom Providers
Inject custom providers into routes with the same % syntax as built-in providers:
@ POST /api/send-email {
< input: EmailMessage
% email: EmailService
$ result = email.send(input.to, input.subject, input.body)
> result
}
@ POST /api/charge {
< input: ChargeRequest
+ auth(jwt)
% payments: PaymentGateway
$ result = payments.charge(input.amount, input.currency, input.token)
> result
}
Code Generation
When generating code, custom providers produce stub classes that you implement with your actual service logic:
Depends() injectionTypeScript: Provider class + getter function for dependency injection
openapi / docs
Generate API specifications and documentation from your Glyph source code.
OpenAPI Specification
# Output YAML to stdout
glyph openapi main.glyph
# Write to file
glyph openapi main.glyph -o openapi.yaml
# Output as JSON
glyph openapi main.glyph --format json
# Custom API title and version
glyph openapi main.glyph --title "My API" --api-version 2.0.0
API Documentation
# Generate HTML documentation
glyph docs main.glyph -o docs.html
# Generate Markdown documentation
glyph docs main.glyph --format markdown -o API.md
# Custom title
glyph docs main.glyph --title "My API" -o docs.html
client
Generate a fully typed API client from your Glyph source code. Reads route and type definitions to produce client methods for each endpoint.
Basic Usage
# Output TypeScript client to stdout
glyph client main.glyph
# Write to file
glyph client main.glyph -o client.ts
# Set base URL
glyph client main.glyph --base-url https://api.example.com
Supported Languages
| Language | Flag |
|---|---|
| TypeScript | --lang typescript (default) |
test
Run test blocks defined in your Glyph source files.
Basic Usage
# Run all tests in a file
glyph test math_test.glyph
# Verbose output
glyph test math_test.glyph --verbose
# Filter tests by name pattern
glyph test math_test.glyph --filter "add*"
# Stop on first failure
glyph test math_test.glyph --fail-fast
Writing Tests
test "should add numbers" {
assert(1 + 1 == 2)
}
test "should concatenate strings" {
$ result = "hello" + " " + "world"
assert(result == "hello world")
}
repl
Start an interactive Read-Eval-Print Loop for exploring Glyph syntax and testing expressions.
Basic Usage
glyph repl
REPL Commands
| Command | Description |
|---|---|
:help | Show available commands |
:quit | Exit the REPL |
:clear | Clear the environment |
:vars | Show all defined variables |
ir (Export)
Export a GlyphLang service as a machine-readable Intermediate Representation — useful for tooling, agent orchestration, and external system integration.
Formats
# Full ServiceIR as JSON
glyph ir main.glyph
# Flattened operation catalog (agent/orchestration-friendly)
glyph ir main.glyph --format catalog
# Human-readable notation summary
glyph ir main.glyph --format compact
# Write to file
glyph ir main.glyph --format catalog -o catalog.json
Catalog Format
The catalog format produces a flat list of named operations — each with an id, kind, notation, params, return type, auth, and providers — designed for ingestion by external agent and orchestration systems.
{
"service": "api",
"version": "1.0",
"operations": [
{
"id": "GET /users/:id",
"kind": "route",
"notation": "@ GET /users/:id",
"method": "GET",
"path": "/users/:id",
"params": [
{ "name": "id", "type": "string", "required": true, "kind": "path" }
],
"returns": "User",
"auth": { "type": "jwt", "required": true },
"providers": ["Database"]
}
]
}
Example: REST API
A complete REST API example with CRUD operations.
# Type definitions
: Todo {
id: int!
title: str!
completed: bool!
created_at: timestamp
}
# List all todos
@ GET /api/todos {
% db: Database
$ todos = db.todos.all()
> {todos: todos}
}
# Get single todo
@ GET /api/todos/:id {
% db: Database
$ todo = db.todos.get(id)
if todo == null {
> {error: "Todo not found", code: 404}
}
> {todo: todo}
}
# Create todo
@ POST /api/todos {
% db: Database
? validate_length(input.title, 1, 500)
$ todo = db.todos.create({
title: input.title,
completed: false,
created_at: now()
})
> {todo: todo}
}
# Update todo
@ PUT /api/todos/:id {
% db: Database
$ existing = db.todos.get(id)
if existing == null {
> {error: "Todo not found", code: 404}
}
$ todo = db.todos.update(id, {
title: input.title,
completed: input.completed
})
> {todo: todo}
}
# Delete todo
@ DELETE /api/todos/:id {
% db: Database
$ existing = db.todos.get(id)
if existing == null {
> {error: "Todo not found", code: 404}
}
$ result = db.todos.delete(id)
> {deleted: true}
}
# Toggle completion
@ POST /api/todos/:id/toggle {
% db: Database
$ todo = db.todos.get(id)
if todo == null {
> {error: "Todo not found", code: 404}
}
$ updated = db.todos.update(id, {
completed: !todo.completed
})
> {todo: updated}
}
Example: Protected API
API with authentication middleware and role-based access control.
# User type
: User {
id: int!
email: str!
name: str!
role: str!
created_at: timestamp
}
# Get current user profile (requires auth)
@ GET /api/me {
+ auth(jwt)
% db: Database
$ user = db.users.get(input.user_id)
if user == null {
> {error: "User not found", code: 404}
}
> {
}
user: {
id: user.id,
name: user.name,
email: user.email,
role: user.role,
created_at: user.created_at
}
}
# Update profile (requires auth)
@ PUT /api/profile {
+ auth(jwt)
% db: Database
? validate_length(input.name, 2, 100)
$ user = db.users.update(input.user_id, {
name: input.name,
updated_at: now()
})
> {
}
success: true,
user: {
id: user.id,
name: user.name,
email: user.email
}
}
# Admin-only: list all users
@ GET /api/admin/users {
+ auth(jwt, role: admin)
% db: Database
$ users = db.users.all()
> {users: users, count: length(users)}
}
# Admin-only: delete user
@ DELETE /api/admin/users/:id {
+ auth(jwt, role: admin)
% db: Database
$ user = db.users.get(id)
if user == null {
> {error: "User not found", code: 404}
}
if user.role == "admin" {
> {error: "Cannot delete admin users", code: 403}
}
$ result = db.users.delete(id)
> {deleted: true, user_id: id}
}
# Moderator or admin: update user role
@ PUT /api/admin/users/:id/role {
+ auth(jwt, role: moderator)
% db: Database
$ user = db.users.get(id)
if user == null {
> {error: "User not found", code: 404}
}
$ updated = db.users.update(id, {
role: input.role,
updated_at: now()
})
> {success: true, user: updated}
}
# API key authentication example
@ GET /api/external/data {
+ auth(api_key)
% db: Database
$ records = db.records.all()
> {data: records}
}
Example: Real-time Chat
WebSocket-based chat application with rooms.
# Message type
: ChatMessage {
id: int!
room: str!
user: str!
text: str!
timestamp: timestamp
}
# WebSocket chat endpoint
@ ws /chat {
on connect {
ws.send({
type: "connected",
message: "Welcome to the chat!"
})
}
on message {
switch input.action {
case "join" {
# Join a room
ws.join(input.room)
ws.send({
type: "joined",
room: input.room
})
ws.broadcast_to_room(input.room, {
type: "user_joined",
user: input.user,
room: input.room,
timestamp: now()
})
}
case "leave" {
# Leave a room
ws.broadcast_to_room(input.room, {
type: "user_left",
user: input.user,
room: input.room,
timestamp: now()
})
ws.leave(input.room)
ws.send({
type: "left",
room: input.room
})
}
case "message" {
# Send message to room
ws.broadcast_to_room(input.room, {
type: "message",
user: input.user,
text: input.text,
room: input.room,
timestamp: now()
})
}
case "typing" {
# Broadcast typing indicator
ws.broadcast_to_room(input.room, {
type: "typing",
user: input.user,
room: input.room
})
}
default {
ws.send({
type: "error",
message: "Unknown action: " + input.action
})
}
}
}
on disconnect {
# Automatic cleanup - rooms are handled by the server
}
}
# Get available rooms
@ GET /api/chat/rooms {
> {
}
rooms: ["general", "random", "tech", "gaming"]
}
Example: Blog API
Full-featured blog API with posts, comments, and pagination.
# Type definitions
: Author {
id: int!
name: str!
email: str!
bio: str?
}
: Post {
id: int!
title: str!
slug: str!
content: str!
excerpt: str?
author_id: int!
tags: List[str]
status: str!
views: int!
created_at: timestamp
updated_at: timestamp?
}
: Comment {
id: int!
post_id: int!
author_name: str!
author_email: str!
content: str!
approved: bool!
created_at: timestamp
}
# ===================
# Posts
# ===================
# List published posts with pagination
@ GET /api/posts {
% db: Database
$ page = input.page
$ per_page = input.per_page
if page == null {
$ page = 1
}
if per_page == null {
$ per_page = 10
}
$ offset = (page - 1) * per_page
$ posts = db.posts.filter("status", "published")
$ sorted = posts.sortBy("created_at", "desc")
$ paginated = sorted.paginate(offset, per_page)
$ total = db.posts.filter("status", "published").count()
> {
}
posts: paginated,
pagination: {
page: page,
per_page: per_page,
total: total,
has_more: page * per_page < total
}
}
# Get single post by slug
@ GET /api/posts/:slug {
% db: Database
$ post = db.posts.findOne("slug", slug)
if post == null {
> {error: "Post not found", code: 404}
}
# Increment view count
$ updated = db.posts.update(post.id, {
views: post.views + 1
})
# Get author
$ author = db.authors.get(post.author_id)
# Get approved comments
$ comments = db.comments.filter("post_id", post.id)
$ approved = comments.filter("approved", true)
> {
}
post: updated,
author: author,
comments: approved
}
# Create post (auth required)
@ POST /api/posts {
+ auth(jwt)
% db: Database
? validate_length(input.title, 1, 200)
? validate_length(input.content, 1, 50000)
$ slug = input.slug
if slug == null {
$ slug = input.title
}
$ post = db.posts.create({
title: input.title,
slug: slug,
content: input.content,
excerpt: input.excerpt,
author_id: input.user_id,
tags: input.tags,
status: "draft",
views: 0,
created_at: now()
})
> {post: post}
}
# Update post
@ PUT /api/posts/:id {
+ auth(jwt)
% db: Database
$ existing = db.posts.get(id)
if existing == null {
> {error: "Post not found", code: 404}
}
if existing.author_id != input.user_id {
> {error: "Not authorized", code: 403}
}
$ post = db.posts.update(id, {
title: input.title,
content: input.content,
excerpt: input.excerpt,
tags: input.tags,
updated_at: now()
})
> {post: post}
}
# Publish post
@ POST /api/posts/:id/publish {
+ auth(jwt)
% db: Database
$ post = db.posts.get(id)
if post.author_id != input.user_id {
> {error: "Not authorized", code: 403}
}
$ updated = db.posts.update(id, {
status: "published",
updated_at: now()
})
> {post: updated}
}
# Delete post
@ DELETE /api/posts/:id {
+ auth(jwt)
% db: Database
$ post = db.posts.get(id)
if post.author_id != input.user_id {
> {error: "Not authorized", code: 403}
}
$ result = db.posts.delete(id)
$ comments = db.comments.deleteWhere("post_id", id)
> {deleted: true}
}
# Get posts by tag
@ GET /api/posts/tag/:tag {
% db: Database
$ posts = db.posts.filterArray("tags", tag)
$ published = posts.filter("status", "published")
> {posts: published, tag: tag}
}
# ===================
# Comments
# ===================
# Add comment to post
@ POST /api/posts/:postId/comments {
% db: Database
? validate_length(input.content, 1, 2000)
? validate_email(input.email)
$ post = db.posts.get(postId)
if post == null {
> {error: "Post not found", code: 404}
}
$ comment = db.comments.create({
post_id: postId,
author_name: input.name,
author_email: input.email,
content: input.content,
approved: false,
created_at: now()
})
> {comment: comment, message: "Comment submitted for moderation"}
}
# Approve comment (admin only)
@ POST /api/comments/:id/approve {
+ auth(jwt, role: admin)
% db: Database
$ comment = db.comments.update(id, {
approved: true
})
> {comment: comment}
}
# Delete comment (admin only)
@ DELETE /api/comments/:id {
+ auth(jwt, role: admin)
% db: Database
$ result = db.comments.delete(id)
> {deleted: true}
}
# ===================
# Search
# ===================
@ GET /api/search {
% db: Database
? validate_length(input.q, 1, 100)
$ posts = db.posts.filter("status", "published")
$ results = []
for post in posts {
# Simple text search in title
if post.title == input.q {
results = results + [post]
}
}
> {
}
query: input.q,
results: results,
count: length(results)
}
Comments
GlyphLang supports two styles of single-line comments. Use comments to document your code, explain complex logic, and leave notes for other developers.
Hash Comments
Use
#for comments (the preferred style):Double-Slash Comments
C-style
//comments are also supported:Section Headers
Use comments to organize code into logical sections:
Inline Documentation
Add inline comments to explain complex logic:
TODO Comments
Mark incomplete or future work with TODO comments:
Comment Guidelines
#(preferred) or//for comments