Skip to content

Syntax Quick Reference#

This page is a lookup reference, not a learning guide. For hands-on learning, start with the AI Day Planner tutorial which teaches these concepts progressively.

Try it: Functions | Objects | Walkers & Graphs | AI Integration | Full Reference

# ============================================================
# Learn Jac in Y Minutes
# ============================================================
# Jac is a superset of Python with graph-native programming,
# object-spatial walkers, AI-native constructs, and full-stack
# codespaces -- all with brace-delimited blocks.
# Run a file with: jac <filename>

# ============================================================
# Comments & Docstrings
# ============================================================

# Single-line comment

#*
    Multi-line
    comment
*#

# Module-level docstring (no semicolon needed)
"""This module does something useful."""

# Docstrings go BEFORE the declaration they document
"""Object-level docstring."""
obj Documented {

    """Method docstring."""
    def method() {
    }
}


# ============================================================
# Entry Point
# ============================================================

# Every Jac program starts from a `with entry` block.
# You can have multiple; they run in order.

with entry {
    print("Hello, world!");
}

# Use :__main__ to run only when this is the main module
with entry:__main__ {
    print("Only when run directly");
}


# ============================================================
# Variables & Types
# ============================================================

with entry {
    x: int = 42;                 # Typed variable
    name = "Jac";                # Type inferred
    pi: float = 3.14;
    flag: bool = True;
    nothing: None = None;

    # Jac has the same built-in types as Python:
    # int, float, str, bool, list, tuple, set, dict, bytes, any

    # Union types
    maybe: str | None = None;

    # F-strings
    msg = f"Value: {x}, Pi: {pi:.2f}";
}


# ============================================================
# Imports
# ============================================================

# Simple import
import os;
import sys, json;

# Import with alias
import datetime as dt;

# Import specific items from a module
import from math { sqrt, pi, log as logarithm }

# Relative imports
import from .sibling { helper_func }
import from ..parent.mod { SomeClass }

# Include merges a module's namespace into the current scope
include random;

# Cross-codespace imports (see Full-Stack section below)
# sv import from ...main { MyWalker }       # Server import in client
# cl import from "@jac/runtime" { Link }    # npm runtime import


# ============================================================
# Functions (def)
# ============================================================

# Functions use `def`, braces for body, and semicolons
def greet(name: str) -> str {
    return f"Hello, {name}!";
}

# Default parameters and multiple return values
def divmod_example(a: int, b: int = 2) -> tuple[int, int] {
    return (a // b, a % b);
}

# No-arg functions still need parentheses
def say_hi() {
    print("Hi!");
}

# Abstract function (declaration only, no body)
def area() -> float abs;

# Function with all param types
def kitchen_sink(
    pos_only: int,
    /,
    regular: str = "default",
    *args: int,
    kw_only: bool = True,
    **kwargs: any
) -> str {
    return "ok";
}

# Public function (becomes API endpoint with `jac start`)
def:pub get_items() -> list {
    return [];
}

# Private function
def:priv internal_helper() -> None { }


# ============================================================
# Control Flow
# ============================================================

with entry {
    x = 9;

    # --- if / elif / else (no parens needed, braces required) ---
    if x < 5 {
        print("low");
    } elif x < 10 {
        print("medium");
    } else {
        print("high");
    }

    # --- for-in loop ---
    for item in ["a", "b", "c"] {
        print(item);
    }

    # --- for-to-by loop (C-style iteration) ---
    # Syntax: for VAR = START to CONDITION by STEP { ... }
    for i = 0 to i < 10 by i += 2 {
        print(i);   # 0, 2, 4, 6, 8
    }

    # --- while loop (with optional else) ---
    n = 5;
    while n > 0 {
        n -= 1;
    } else {
        print("Loop completed normally");
    }

    # --- break, continue, skip ---
    for i in range(10) {
        if i == 3 { continue; }
        if i == 7 { break; }
        print(i);
    }

    # --- ternary expression ---
    label = "high" if x > 5 else "low";
}


# ============================================================
# Match (Python-style pattern matching)
# ============================================================

with entry {
    value = 10;
    match value {
        case 1:
            print("one");
        case 2 | 3:
            print("two or three");
        case x if x > 5:
            print(f"big: {x}");
        case _:
            print("other");
    }
}


# ============================================================
# Switch (C-style, with fall-through)
# ============================================================

def check_fruit(fruit: str) {
    switch fruit {
        case "apple":
            print("It's an apple");
            break;
        case "banana":
        case "orange":
            print("banana or orange (fall-through)");
        default:
            print("unknown fruit");
    }
}


# ============================================================
# Collections
# ============================================================

with entry {
    # Lists
    fruits = ["apple", "banana", "cherry"];
    print(fruits[0]);       # apple
    print(fruits[1:3]);     # ["banana", "cherry"]
    print(fruits[-1]);      # cherry

    # Dictionaries
    person = {"name": "Alice", "age": 25};
    print(person["name"]);

    # Tuples (immutable)
    point = (10, 20);
    (x, y) = point;         # Tuple unpacking

    # Sets
    colors = {"red", "green", "blue"};

    # Comprehensions
    squares = [i ** 2 for i in range(5)];
    evens = [i for i in range(10) if i % 2 == 0];
    name_map = {name: len(name) for name in ["alice", "bob"]};
    unique_lens = {len(s) for s in ["hi", "hey", "hi"]};

    # Generator expression
    total = sum(x ** 2 for x in range(1000));

    # Star unpacking
    (first, *rest) = [1, 2, 3, 4];
    print(first);   # 1
    print(rest);    # [2, 3, 4]
}


# ============================================================
# Objects (obj) vs Classes (class)
# ============================================================

# `obj` is like a Python dataclass -- fields are per-instance,
# auto-generates __init__, __eq__, __repr__, etc.
obj Dog {
    has name: str = "Unnamed",
        age: int = 0;

    def bark() {
        print(f"{self.name} says Woof!");
    }

    # Class method -- Self refers to the class
    class def create(name: str) -> Self {
        return Self(name=name);
    }

    # Static method -- no self or Self
    static def species() -> str {
        return "Canis familiaris";
    }
}

# `class` follows standard Python class behavior
class Cat {
    has name: str = "Unnamed";

    def meow(self) {
        print(f"{self.name} says Meow!");
    }
}

# Inheritance
obj Puppy(Dog) {
    has parent_name: str = "Unknown";

    override def bark() {
        print(f"Puppy of {self.parent_name} yips!");
    }
}

# Generic types with type parameters
obj Result[T, E = Exception] {
    has value: T | None = None,
        error: E | None = None;

    def is_ok() -> bool {
        return self.error is None;
    }
}

# Forward declaration (define body later or in another file)
obj UserProfile;


# ============================================================
# Has Declarations (fields)
# ============================================================

obj Example {
    # Basic typed fields with defaults
    has name: str,
        count: int = 0;

    # Static (class-level) field
    static has instances: int = 0;

    # Deferred initialization (set in postinit)
    has computed: int by postinit;

    def postinit() {
        self.computed = self.count * 2;
    }
}


# ============================================================
# Access Modifiers
# ============================================================

# Access modifiers work on obj, class, node, edge, walker,
# def, has -- controlling visibility and API exposure

obj:pub Person {
    has:pub name: str;          # Public (default)
    has:priv ssn: str;          # Private
    has:protect age: int;       # Protected
}

# Public walker becomes REST endpoint with `jac start`
walker:pub GetUsers {
    can get with Root entry {
        report [-->];
    }
}

# Private walker enforces per-user auth
walker:priv MyData {
    can get with Root entry {
        report [-->];
    }
}


# ============================================================
# Enums
# ============================================================

enum Color {
    RED = "red",
    GREEN = "green",
    BLUE = "blue"
}

# Auto-valued enum members
enum Status { PENDING, ACTIVE, DONE }

with entry {
    print(Color.RED.value);      # "red"
    print(Status.ACTIVE.value);  # 2
}


# ============================================================
# Type Aliases
# ============================================================

type JsonPrimitive = str | int | float | bool | None;
type Json = JsonPrimitive | list[Json] | dict[str, Json];

# Generic type alias
type NumberList = list[int | float];

# Self type -- refers to the enclosing archetype
obj TreeNode {
    has value: int = 0,
        next: Self | None = None;  # Self = TreeNode here
}


# ============================================================
# Global Variables (glob)
# ============================================================

glob MAX_SIZE: int = 100;
glob greeting: str = "Hello";

def use_global() {
    global greeting;          # Reference module-level glob
    greeting = "Hola";
}


# ============================================================
# Impl Blocks (separate declaration from definition)
# ============================================================

obj Calculator {
    has value: int = 0;

    # Declare methods (no body)
    def add(n: int) -> int;
    def multiply(n: int) -> int;
}

# Define methods separately (can be in a .impl.jac file)
impl Calculator.add(n: int) -> int {
    self.value += n;
    return self.value;
}

impl Calculator.multiply(n: int) -> int {
    self.value *= n;
    return self.value;
}


# ============================================================
# Lambdas
# ============================================================

with entry {
    # Simple lambda (untyped params, colon body)
    add = lambda x, y: x + y;
    print(add(3, 4));

    # Typed lambda with return type
    mul = lambda (x: int, y: int) -> int : x * y;
    print(mul(3, 4));

    # Multi-statement lambda (brace body)
    classify = lambda (score: int) -> str {
        if score >= 90 { return "A"; }
        elif score >= 80 { return "B"; }
        else { return "F"; }
    };
    print(classify(85));

    # No-arg lambda
    get_42 = lambda : 42;

    # Void lambda (common in JSX event handlers)
    handler = lambda -> None { print("clicked"); };
}


# ============================================================
# Pipe Operators
# ============================================================

with entry {
    # Forward pipe: value |> function
    "hello" |> print;
    5 |> str |> print;

    # Backward pipe: function <| value
    print <| "world";

    # Chained pipes
    [3, 1, 2] |> sorted |> list |> print;
}


# ============================================================
# Decorators
# ============================================================

# Prefer `class def` for classmethods in obj (see Objects section above)
# @classmethod decorator is supported for Python `class` compatibility
@classmethod
def my_class_method(cls: type) -> str {
    return cls.__name__;
}


# ============================================================
# Try / Except / Finally
# ============================================================

with entry {
    try {
        result = 10 // 0;
    } except ZeroDivisionError as e {
        print(f"Caught: {e}");
    } except Exception {
        print("Some other error");
    } else {
        print("No error occurred");
    } finally {
        print("Always runs");
    }
}


# ============================================================
# With Statement (context managers)
# ============================================================

with entry {
    with open("file.txt") as f {
        data = f.read();
    }

    # Multiple context managers
    with open("a.txt") as a, open("b.txt") as b {
        print(a.read(), b.read());
    }
}


# ============================================================
# Assert
# ============================================================

with entry {
    x = 42;
    assert x == 42;
    assert x > 0, "x must be positive";
}


# ============================================================
# Walrus Operator (:=)
# ============================================================

with entry {
    # Assignment inside expressions
    if (n := len("hello")) > 3 {
        print(f"Long string: {n} chars");
    }
}


# ============================================================
# Test Blocks
# ============================================================

def fib(n: int) -> int {
    if n <= 1 { return n; }
    return fib(n - 1) + fib(n - 2);
}

test "fibonacci base cases" {
    assert fib(0) == 0;
    assert fib(1) == 1;
}

test "fibonacci recursive" {
    for i in range(2, 10) {
        assert fib(i) == fib(i - 1) + fib(i - 2);
    }
}

# Tests can spawn walkers and check reports
test "walker test" {
    root ++> Person(name="Alice", age=30);
    result = root spawn Greeter();
    assert len(result.reports) > 0;
}


# ============================================================
# Async / Await
# ============================================================

import asyncio;

async def fetch_data() -> str {
    await asyncio.sleep(1);
    return "data";
}

async def main() {
    result = await fetch_data();
    print(result);
}


# ============================================================
# Flow / Wait (concurrent tasks)
# ============================================================

import from time { sleep }

def slow_task(n: int) -> int {
    sleep(1);
    return n * 2;
}

with entry {
    # `flow` launches a concurrent task, `wait` collects results
    task1 = flow slow_task(1);
    task2 = flow slow_task(2);
    task3 = flow slow_task(3);

    r1 = wait task1;
    r2 = wait task2;
    r3 = wait task3;
    print(r1, r2, r3);   # 2 4 6
}


# ============================================================
# Null-Safe Access (?. and ?[])
# ============================================================

with entry {
    x: list | None = None;
    print(x?.append);      # None (no crash)
    print(x?[0]);           # None (no crash)

    y = [1, 2, 3];
    print(y?[1]);           # 2
    print(y?[99]);          # None (out of bounds returns None)
}


# ============================================================
# Inline Python (::py::)
# ============================================================

with entry {
    result: int = 0;
    ::py::
import sys
result = sys.maxsize
    ::py::
    print(f"Max int: {result}");
}

# Also works inside objects/enums for Python-specific methods
enum Priority {
    LOW = 1,
    HIGH = 2

    ::py::
    def is_urgent(self):
        return self.value >= 2
    ::py::
}


# ============================================================
# OBJECT SPATIAL PROGRAMMING (OSP)
# ============================================================
# Jac extends the type system with graph-native constructs:
# nodes, edges, walkers, and spatial abilities.


# ============================================================
# Nodes and Edges
# ============================================================

# Nodes are objects that can exist in a graph
node Person {
    has name: str,
        age: int;
}

# Edges connect nodes and can carry data
edge Friendship {
    has since: int = 0;
}

# Nodes with abilities (triggered by walkers)
node SecureRoom {
    has name: str,
        clearance: int = 0;

    can on_enter with Visitor entry {
        print(f"Welcome to {self.name}");
    }

    can on_exit with Visitor exit {
        print(f"Leaving {self.name}");
    }
}

# Node inheritance
node Employee(Person) {
    has department: str;
}

# Edge with methods
edge Weighted {
    has weight: float = 1.0;

    def normalize(max_w: float) -> float {
        return self.weight / max_w;
    }
}


# ============================================================
# Connection Operators
# ============================================================

with entry {
    a = Person(name="Alice", age=25);
    b = Person(name="Bob", age=30);
    c = Person(name="Charlie", age=28);

    # --- Untyped connections ---
    root ++> a;             # Connect root -> a
    a ++> b;                # Connect a -> b
    c <++ a;                # Connect a -> c (backward syntax)
    a <++> b;               # Bidirectional a <-> b

    # --- Typed connections (with edge data) ---
    a +>: Friendship(since=2020) :+> b;
    a +>: Friendship(since=1995) :+> c;

    # --- Typed connection with field assignment ---
    a +>: Friendship : since=2018 :+> b;

    # --- Chained connections ---
    root ++> a ++> b ++> c;

    # --- Delete edge ---
    a del --> b;

    # --- Delete node ---
    del c;
}


# ============================================================
# Edge Traversal & Filters
# ============================================================

with entry {
    # Traverse outgoing edges from root
    print([root -->]);                      # All nodes via outgoing edges
    print([root <--]);                      # All nodes via incoming edges
    print([root <-->]);                     # All nodes via any edges

    # Filter by edge type
    print([root ->:Friendship:->]);          # Nodes connected by Friendship edges

    # Filter by edge field values
    print([root ->:Friendship:since > 2018:->]);

    # Filter by node type
    print([root -->](?:Person));             # Only Person nodes

    # Filter by node attribute
    print([root -->](?age >= 18));           # Nodes with age >= 18

    # Combined: type + attribute
    print([root -->](?:Person, age > 25));

    # Get edge objects themselves (not target nodes)
    print([edge root -->]);                  # All edge objects
    print([edge root ->:Friendship:->]);     # Friendship edge objects

    # Chained traversal (multi-hop)
    fof = [root ->:Friendship:-> ->:Friendship:->];
}


# ============================================================
# Assign Comprehensions (spatial update)
# ============================================================

with entry {
    # Filter nodes by attribute
    adults = [root -->](?age >= 18);

    # Assign: update matching nodes in-place
    [root -->](?age >= 18)(=verified=True);
}


# ============================================================
# Walkers
# ============================================================
# Walkers are objects that traverse graphs.
# They have abilities that trigger on entry/exit of nodes.

walker Greeter {
    has greeting: str = "Hello";

    # Runs when walker enters the root node
    can greet_root with Root entry {
        print(f"{self.greeting} from root!");
        visit [-->];        # Move to connected nodes
    }

    # Runs when walker visits any Person node
    can greet_person with Person entry {
        # `here` = current node, `self` = the walker
        print(f"{self.greeting}, {here.name}!");
        report here.name;   # Collect a value (returned as list)
        visit [-->];         # Continue traversal
    }
}

with entry {
    root ++> Person(name="Alice", age=25);
    root ++> Person(name="Bob", age=30);

    # Spawn a walker at root and collect results
    result = root spawn Greeter();
    print(result.reports);   # ["Alice", "Bob"]
}


# ============================================================
# Walker Control Flow
# ============================================================

walker SearchWalker {
    has target: str;

    can search with Person entry {
        if here.name == self.target {
            print(f"Found {self.target}!");
            disengage;       # Stop traversal immediately
        }
        report here.name;

        # visit...else runs fallback when no outgoing nodes
        visit [-->] else {
            print("Reached a dead end");
        }
    }
}


# ============================================================
# Visit Statement Variants
# ============================================================

walker VisitDemo {
    can demo with Person entry {
        visit [-->];                    # All outgoing nodes
        visit [<--];                    # All incoming nodes
        visit [<-->];                   # Both directions
        visit [-->](?:Person);          # Type-filtered
        visit [->:Friendship:->];       # Via edge type
        visit [->:Friendship:since > 2020:->];  # Edge condition

        visit [-->] else {              # Fallback if nowhere to go
            print("Dead end");
        }

        visit : 0 : [-->];             # First outgoing node only
        visit : -1 : [-->];            # Last outgoing node only

        visit here;                     # Re-visit current node
    }
}


# ============================================================
# Node & Edge Abilities
# ============================================================
# Nodes and edges can have abilities that trigger
# when specific walker types visit them.

node Gateway {
    has name: str;

    # Triggers for any walker
    can on_any with entry {
        print(f"Someone entered {self.name}");
    }

    # Triggers only for specific walker type
    can on_inspector with Inspector entry {
        if visitor.clearance < 5 {
            print("Access denied");
            disengage;
        }
    }

    # Multiple walker types (union)
    can on_multi with Admin | Inspector entry {
        print("Authorized personnel");
    }

    # Exit ability
    can on_leave with Inspector exit {
        print("Inspector leaving");
    }
}

walker Inspector {
    has clearance: int = 0;

    can visit_gateway with Gateway entry {
        # `here` = current Gateway node
        # `self` = the walker
        print(f"Inspecting: {here.name}");
        visit [-->];
    }
}


# ============================================================
# Typed Context Blocks
# ============================================================
# Handle different subtypes with specialized code paths

node Animal { has name: str; }
node Dog(Animal) { has breed: str; }
node Cat(Animal) { has indoor: bool; }

walker AnimalVisitor {
    can visit_animal with Animal entry {
        ->Dog{print(f"{here.name} is a {here.breed} dog");}
        ->Cat{print(f"{here.name} says meow");}
        ->_{print(f"{here.name} is some animal");}
    }
}


# ============================================================
# Spawn Syntax Variants
# ============================================================

with entry {
    w = Greeter(greeting="Hi");

    # Binary spawn: node spawn walker
    root spawn w;

    # Spawn with params
    root spawn Greeter(greeting="Hey");

    # Spawn returns result object
    result = root spawn Greeter();
    print(result.reports);   # List of reported values

    # Reverse: walker spawn node
    w spawn root;
}


# ============================================================
# Walkers as REST APIs
# ============================================================
# Public walkers become HTTP endpoints with `jac start`

walker:pub add_todo {
    has title: str;          # Becomes request body field

    can create with Root entry {
        new_todo = here ++> Todo(title=self.title);
        report new_todo;     # Becomes response body
    }
}

# Endpoint: POST /walker/add_todo
# Body: {"title": "Learn Jac"}

# Public functions also become endpoints
def:pub health_check() -> dict {
    return {"status": "ok"};
}

# @restspec customizes HTTP method and path
import from http { HTTPMethod }

@restspec(method=HTTPMethod.GET, path="/items/{item_id}")
walker:pub get_item {
    has item_id: str;
    can fetch with Root entry {
        report {"id": self.item_id};
    }
}


# ============================================================
# Async Walkers
# ============================================================

async walker AsyncCrawler {
    has depth: int = 0;

    async can crawl with Root entry {
        print(f"Crawling at depth {self.depth}");
        visit [-->];
    }
}


# ============================================================
# Anonymous Abilities
# ============================================================
# Abilities without names (auto-named by compiler)

node AutoNode {
    has val: int = 0;

    can with entry {
        print(f"Entered node with val={self.val}");
    }
}

walker AutoWalker {
    can with Root entry {
        visit [-->];
    }

    can with AutoNode entry {
        print(f"Visiting: {here.val}");
    }
}


# ============================================================
# Graph Built-in Functions
# ============================================================

with entry {
    p = Person(name="Alice", age=30);
    root ++> p;

    jid(p);              # Unique Jac ID of object
    save(p);             # Persist node to storage
    commit();            # Commit pending changes
    printgraph(root);    # Print graph for debugging
}


# ============================================================
# AI INTEGRATION (by llm)
# ============================================================
# Jac's Meaning Typed Programming lets the compiler
# extract semantics from your code to construct LLM prompts.


# ============================================================
# by llm() -- Delegate Function to LLM
# ============================================================

# The function signature IS the specification.
# Name, param names, types, and return type become the prompt.

def classify_sentiment(text: str) -> str by llm;

# Enums constrain LLM output to valid values
enum Category { WORK, PERSONAL, SHOPPING, HEALTH, OTHER }
def categorize(title: str) -> Category by llm();

# Structured output -- every field must be filled
obj Ingredient {
    has name: str,
        cost: float,
        carby: bool;
}
def plan_shopping(recipe: str) -> list[Ingredient] by llm();

# Model configuration
def summarize(text: str) -> str by llm(
    model_name="gpt-4",
    temperature=0.7,
    max_tokens=2000
);

# Streaming response (returns generator)
def stream_story(prompt: str) -> str by llm(stream=True);

# Inline LLM expression
with entry {
    result = "Explain quantum computing simply" by llm;
}


# ============================================================
# sem -- Semantic Descriptions for AI
# ============================================================
# `sem` attaches descriptions to bindings that the compiler
# includes in the LLM prompt. It's not a comment -- it
# changes what the LLM sees at runtime.

sem Ingredient.cost = "Estimated cost in USD";
sem Ingredient.carby = "True if high in carbohydrates";

sem plan_shopping = "Generate a shopping list for the given recipe.";

# Parameter-level semantics
sem summarize.text = "The article or document to summarize";
sem summarize.return = "A 2-3 sentence summary";

# Enum value semantics
enum Priority { LOW, MEDIUM, HIGH, CRITICAL }
sem Priority.CRITICAL = "Requires immediate attention within 1 hour";


# ============================================================
# Tool Calling (Agentic AI)
# ============================================================
# Give the LLM access to functions it can call (ReAct loop)

def get_weather(city: str) -> str {
    return f"Weather data for {city}";
}

def search_web(query: str) -> list[str] {
    return [f"Result for {query}"];
}

# The LLM decides which tools to call and in what order
def answer_question(question: str) -> str by llm(
    tools=[get_weather, search_web]
);

# Additional context injection
glob company_info = "TechCorp, products: CloudDB, SecureAuth";

def support_agent(question: str) -> str by llm(
    incl_info={"company": company_info}
);
sem support_agent = "Answer customer questions about our products.";


# ============================================================
# Multimodal AI
# ============================================================

import from byllm.lib { Image }

def describe_image(image: Image) -> str by llm;

with entry {
    desc = describe_image(Image("photo.jpg"));
    desc = describe_image(Image("https://example.com/img.png"));
}


# ============================================================
# FULL-STACK DEVELOPMENT (Codespaces)
# ============================================================
# Jac code can target different execution environments:
#   sv { } = server (Python/PyPI)
#   cl { } = client (JavaScript/npm)
#   na { } = native (C ABI)


# ============================================================
# Codespace Blocks
# ============================================================

# Server code (default -- code outside any block is server)
node Todo {
    has title: str, done: bool = False;
}

def:pub get_todos() -> list {
    return [{"title": t.title} for t in [root -->](?:Todo)];
}

# Client code (compiles to JavaScript/React)
cl {
    def:pub app() -> JsxElement {
        has items: list = [];

        async can with entry {
            items = await get_todos();
        }

        return <div>
            {[<p key={i.title}>{i.title}</p> for i in items]}
        </div>;
    }
}

# Explicit server block
sv {
    node Secret { has value: str; }
}

# Single-statement form (no braces)
sv import from .database { connect_db }
cl import from react { useState }


# ============================================================
# File Extension Conventions
# ============================================================
# .jac           Default (server codespace)
# .sv.jac        Server-only variant
# .cl.jac        Client-only variant (auto client codespace)
# .na.jac        Native variant
# .impl.jac      Implementation annex (method bodies)
# .test.jac      Test annex


# ============================================================
# Client Components (JSX)
# ============================================================

cl {
    def:pub Counter() -> JsxElement {
        # `has` in client components becomes React useState
        has count: int = 0;

        return <div>
            <p>Count: {count}</p>
            <button onClick={lambda -> None { count = count + 1; }}>
                Increment
            </button>
        </div>;
    }
}

# JSX syntax reference:
# <div>text</div>               HTML elements
# <Component prop="val" />      Component with props
# {expression}                  JavaScript expression
# {condition and <p>Show</p>}   Conditional render
# {[<li>...</li> for x in xs]}  List rendering
# <div {...props}>               Spread props
# <div className="cls">         Class name (not "class")
# <div style={{"color": "red"}} Inline styles


# ============================================================
# Client State & Lifecycle
# ============================================================

cl {
    def:pub DataView() -> JsxElement {
        has data: list = [];
        has loading: bool = True;

        # Mount effect (runs once on component mount)
        async can with entry {
            data = await fetch("/api/data").then(
                lambda r: any -> any { return r.json(); }
            );
            loading = False;
        }

        # Dependency effect (runs when userId changes)
        # async can with [userId] entry { ... }

        # Multiple dependencies
        # can with (a, b) entry { ... }

        # Cleanup on unmount
        # can with exit { unsubscribe(); }

        if loading { return <p>Loading...</p>; }
        return <div>{data}</div>;
    }
}


# ============================================================
# Server-Client Communication
# ============================================================

# Import server walkers in client code
sv import from ...main { AddTodo, GetTodos }

cl {
    def:pub TodoApp() -> JsxElement {
        has todos: list = [];

        async can with entry {
            result = root spawn GetTodos();
            if result.reports {
                todos = result.reports[0];
            }
        }

        async def add_todo(text: str) -> None {
            result = root spawn AddTodo(title=text);
            if result.reports {
                todos = todos + [result.reports[0]];
            }
        }

        return <div>...</div>;
    }
}


# ============================================================
# Routing (File-Based)
# ============================================================
# pages/index.jac          -> /
# pages/about.jac          -> /about
# pages/users/[id].jac     -> /users/:id  (dynamic param)
# pages/[...notFound].jac  -> *            (catch-all)
# pages/(auth)/layout.jac  -> route group  (no URL segment)
# pages/layout.jac         -> root layout

# Page files export a `page` function:
# cl { def:pub page() -> JsxElement { ... } }

# Layout files use <Outlet /> for child routes:
# cl import from "@jac/runtime" { Outlet }
# cl { def:pub layout() -> JsxElement {
#     return <><nav>...</nav><Outlet /></>;
# } }


# ============================================================
# Authentication (Client)
# ============================================================

# cl import from "@jac/runtime" {
#     jacLogin,       # (email, pass) -> bool
#     jacSignup,      # (email, pass) -> dict
#     jacLogout,      # () -> void
#     jacIsLoggedIn   # () -> bool
# }


# ============================================================
# Special Variables Reference
# ============================================================
# self     -- the current object/walker/node
# here     -- the current node (in walker abilities)
# visitor  -- the visiting walker (in node/edge abilities)
# root     -- the root node of the graph


# ============================================================
# Keywords Reference
# ============================================================
# Types:    str, int, float, bool, list, tuple, set, dict, bytes, any, type
# Decl:     obj, class, node, edge, walker, enum, has, can, def, impl,
#           glob, test, type
# Modifiers: pub, priv, protect, static, override, abs, async
# Control:  if, elif, else, for, to, by, while, match, switch, case, default
# Flow:     return, yield, break, continue, raise, del, assert, skip
# OSP:      visit, spawn, entry, exit, disengage, report, here, visitor, root
# AI:       by, llm, sem
# Async:    async, await, flow, wait
# Logic:    and, or, not, in, is
# Codespace: sv, cl, na
# Other:    import, include, from, as, try, except, finally, with, lambda,
#           global, nonlocal, self, super, init, postinit