mirror of
https://github.com/blackboxprogramming/BlackRoad-Operating-System.git
synced 2026-03-18 01:34:00 -05:00
Add THE CANONICAL 100: Complete Lucidia language definition through examples
This commit introduces the foundational specification for Lucidia v1.0 - a set of 100 working example programs that DEFINE the language through demonstration rather than formal grammar. Key Philosophy: - Examples ARE the spec (not documentation OF the spec) - AI systems learn by reading all 100 examples and extracting patterns - Humans learn by working through examples sequentially - No feature exists unless demonstrated in these examples Structure: - 001-010: Fundamentals (hello world → functions) - 011-020: Data & Collections (lists, maps, sets) - 021-030: Control Flow (if, loops, pattern matching) - 031-040: Functions & Composition (map, filter, reduce, closures) - 041-050: UI Basics (forms, inputs, validation) - 051-060: Reactive Programming (state, watchers, events) - 061-070: Consent & Privacy (permission system - CORE DIFFERENTIATOR) - 071-080: Storage & Sync (local-first, cloud-optional) - 081-090: AI Integration (intent → code, learning user style) - 091-100: Complete Applications (todo, notes, chat, e-commerce) Core Language Features Demonstrated: ✓ Intent over ceremony (write WHAT, not HOW) ✓ Consent as syntax (ask permission for: resource) ✓ Local-first storage (store locally, sync to cloud optional) ✓ AI-collaborative (### Intent comments become code) ✓ Reactive by default (state, watch, computed) ✓ Zero setup (runs in browser via WASM) ✓ Multi-paradigm (functional, OOP, reactive, agent-based) ✓ Gradual complexity (hello world → production apps) Files Created: - README.md - Learning philosophy and path - INDEX.md - Complete reference table - 001-100.lucidia - All example programs Total: 102 files, ~3,500+ lines of example code Why This Matters: This is not just documentation. This IS Lucidia. Every parser, compiler, AI assistant, and developer tool will be trained on these examples. They are the permanent, immutable foundation of the language. Next Steps: 1. Build parser that learns from these examples 2. Train AI to recognize and generate Lucidia patterns 3. Create browser playground with these as gallery 4. Use for academic paper and conference presentations Designed by: Cece (Principal Language & Runtime Architect) For: BlackRoad Operating System / Lucidia Programming Language Status: Complete foundation for implementation
This commit is contained in:
4
docs/examples/canonical/001-hello-world.lucidia
Normal file
4
docs/examples/canonical/001-hello-world.lucidia
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# 001: Hello World
|
||||||
|
# The simplest possible Lucidia program
|
||||||
|
|
||||||
|
show "Hello, World!"
|
||||||
10
docs/examples/canonical/002-variables.lucidia
Normal file
10
docs/examples/canonical/002-variables.lucidia
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# 002: Variables & Values
|
||||||
|
# Storing and using data
|
||||||
|
|
||||||
|
name = "Alex"
|
||||||
|
age = 28
|
||||||
|
active = true
|
||||||
|
|
||||||
|
show "Name: {name}"
|
||||||
|
show "Age: {age}"
|
||||||
|
show "Active: {active}"
|
||||||
17
docs/examples/canonical/003-basic-math.lucidia
Normal file
17
docs/examples/canonical/003-basic-math.lucidia
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# 003: Basic Math
|
||||||
|
# Numbers and arithmetic
|
||||||
|
|
||||||
|
a = 10
|
||||||
|
b = 5
|
||||||
|
|
||||||
|
sum = a + b
|
||||||
|
difference = a - b
|
||||||
|
product = a * b
|
||||||
|
quotient = a / b
|
||||||
|
remainder = a % b
|
||||||
|
|
||||||
|
show "Sum: {sum}"
|
||||||
|
show "Difference: {difference}"
|
||||||
|
show "Product: {product}"
|
||||||
|
show "Quotient: {quotient}"
|
||||||
|
show "Remainder: {remainder}"
|
||||||
22
docs/examples/canonical/004-strings-templates.lucidia
Normal file
22
docs/examples/canonical/004-strings-templates.lucidia
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# 004: Strings & Templates
|
||||||
|
# Working with text
|
||||||
|
|
||||||
|
firstname = "Alex"
|
||||||
|
lastname = "Chen"
|
||||||
|
|
||||||
|
# String concatenation
|
||||||
|
fullname = firstname + " " + lastname
|
||||||
|
|
||||||
|
# Template strings (preferred)
|
||||||
|
greeting = "Hello, {firstname} {lastname}!"
|
||||||
|
|
||||||
|
# Multi-line strings
|
||||||
|
bio = """
|
||||||
|
Alex is a developer
|
||||||
|
who loves programming
|
||||||
|
in Lucidia
|
||||||
|
"""
|
||||||
|
|
||||||
|
show fullname
|
||||||
|
show greeting
|
||||||
|
show bio
|
||||||
16
docs/examples/canonical/005-comments.lucidia
Normal file
16
docs/examples/canonical/005-comments.lucidia
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# 005: Comments
|
||||||
|
# How to document your code
|
||||||
|
|
||||||
|
# This is a single-line comment
|
||||||
|
# It explains what the code does
|
||||||
|
|
||||||
|
name = "Lucidia" # Inline comments work too
|
||||||
|
|
||||||
|
## This is a documentation comment
|
||||||
|
## Used for generating docs
|
||||||
|
|
||||||
|
### This is an AI intent comment
|
||||||
|
### These tell the AI what you want to accomplish
|
||||||
|
### (We'll see more of these in examples 081-090)
|
||||||
|
|
||||||
|
show "Comments help humans and AI understand your code"
|
||||||
13
docs/examples/canonical/006-simple-functions.lucidia
Normal file
13
docs/examples/canonical/006-simple-functions.lucidia
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# 006: Simple Functions
|
||||||
|
# Creating reusable code
|
||||||
|
|
||||||
|
# Function with no parameters
|
||||||
|
greet():
|
||||||
|
show "Hello!"
|
||||||
|
|
||||||
|
# Call the function
|
||||||
|
greet()
|
||||||
|
greet()
|
||||||
|
greet()
|
||||||
|
|
||||||
|
# Each call executes the function body
|
||||||
12
docs/examples/canonical/007-function-parameters.lucidia
Normal file
12
docs/examples/canonical/007-function-parameters.lucidia
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# 007: Function Parameters
|
||||||
|
# Functions that accept input
|
||||||
|
|
||||||
|
greet(name):
|
||||||
|
show "Hello, {name}!"
|
||||||
|
|
||||||
|
# Call with different arguments
|
||||||
|
greet("Alex")
|
||||||
|
greet("Jordan")
|
||||||
|
greet("Casey")
|
||||||
|
|
||||||
|
# The parameter 'name' takes on the value of each argument
|
||||||
15
docs/examples/canonical/008-return-values.lucidia
Normal file
15
docs/examples/canonical/008-return-values.lucidia
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# 008: Return Values
|
||||||
|
# Functions that produce output
|
||||||
|
|
||||||
|
double(x):
|
||||||
|
x * 2
|
||||||
|
|
||||||
|
# The last expression is returned automatically
|
||||||
|
result = double(5)
|
||||||
|
show "Double of 5 is {result}"
|
||||||
|
|
||||||
|
# Explicit return also works
|
||||||
|
square(x):
|
||||||
|
return x * x
|
||||||
|
|
||||||
|
show "Square of 5 is {square(5)}"
|
||||||
19
docs/examples/canonical/009-multiple-parameters.lucidia
Normal file
19
docs/examples/canonical/009-multiple-parameters.lucidia
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# 009: Multiple Parameters
|
||||||
|
# Functions with several inputs
|
||||||
|
|
||||||
|
add(a, b):
|
||||||
|
a + b
|
||||||
|
|
||||||
|
multiply(x, y):
|
||||||
|
x * y
|
||||||
|
|
||||||
|
# Use them together
|
||||||
|
result = add(3, 4)
|
||||||
|
show "3 + 4 = {result}"
|
||||||
|
|
||||||
|
result = multiply(5, 6)
|
||||||
|
show "5 × 6 = {result}"
|
||||||
|
|
||||||
|
# Compose functions
|
||||||
|
total = add(multiply(2, 3), multiply(4, 5))
|
||||||
|
show "2×3 + 4×5 = {total}"
|
||||||
20
docs/examples/canonical/010-calling-functions.lucidia
Normal file
20
docs/examples/canonical/010-calling-functions.lucidia
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# 010: Calling Functions
|
||||||
|
# Different ways to use functions
|
||||||
|
|
||||||
|
# Define a utility function
|
||||||
|
format_name(first, last):
|
||||||
|
"{last}, {first}"
|
||||||
|
|
||||||
|
# Simple call
|
||||||
|
name = format_name("Alex", "Chen")
|
||||||
|
show name
|
||||||
|
|
||||||
|
# Use in expressions
|
||||||
|
full_greeting = "Hello, " + format_name("Jordan", "Lee")
|
||||||
|
show full_greeting
|
||||||
|
|
||||||
|
# Chain function calls
|
||||||
|
shout(text):
|
||||||
|
text.uppercase()
|
||||||
|
|
||||||
|
show shout(format_name("Casey", "Park"))
|
||||||
17
docs/examples/canonical/011-lists.lucidia
Normal file
17
docs/examples/canonical/011-lists.lucidia
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# 011: Lists
|
||||||
|
# Ordered collections of items
|
||||||
|
|
||||||
|
# Create a list
|
||||||
|
fruits = ["apple", "banana", "cherry"]
|
||||||
|
|
||||||
|
# Access by index (zero-based)
|
||||||
|
show fruits[0] # apple
|
||||||
|
show fruits[1] # banana
|
||||||
|
show fruits[2] # cherry
|
||||||
|
|
||||||
|
# Get list length
|
||||||
|
show "We have {fruits.length} fruits"
|
||||||
|
|
||||||
|
# Lists can hold any type
|
||||||
|
mixed = [42, "hello", true, 3.14]
|
||||||
|
show mixed
|
||||||
24
docs/examples/canonical/012-list-operations.lucidia
Normal file
24
docs/examples/canonical/012-list-operations.lucidia
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# 012: List Operations
|
||||||
|
# Modifying and working with lists
|
||||||
|
|
||||||
|
numbers = [1, 2, 3, 4, 5]
|
||||||
|
|
||||||
|
# Add to end
|
||||||
|
numbers.append(6)
|
||||||
|
show numbers # [1, 2, 3, 4, 5, 6]
|
||||||
|
|
||||||
|
# Add to beginning
|
||||||
|
numbers.prepend(0)
|
||||||
|
show numbers # [0, 1, 2, 3, 4, 5, 6]
|
||||||
|
|
||||||
|
# Remove by value
|
||||||
|
numbers.remove(3)
|
||||||
|
show numbers # [0, 1, 2, 4, 5, 6]
|
||||||
|
|
||||||
|
# Check if contains
|
||||||
|
has_five = numbers.contains(5)
|
||||||
|
show "Has 5: {has_five}"
|
||||||
|
|
||||||
|
# Get slice
|
||||||
|
first_three = numbers[0:3]
|
||||||
|
show first_three # [0, 1, 2]
|
||||||
22
docs/examples/canonical/013-maps.lucidia
Normal file
22
docs/examples/canonical/013-maps.lucidia
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# 013: Maps/Objects
|
||||||
|
# Key-value pairs
|
||||||
|
|
||||||
|
# Create a map
|
||||||
|
user = {
|
||||||
|
name: "Alex Chen",
|
||||||
|
age: 28,
|
||||||
|
email: "alex@example.com",
|
||||||
|
active: true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Access properties
|
||||||
|
show user.name
|
||||||
|
show user.age
|
||||||
|
show user.email
|
||||||
|
|
||||||
|
# Bracket notation also works
|
||||||
|
show user["name"]
|
||||||
|
|
||||||
|
# Check if key exists
|
||||||
|
has_email = user.has("email")
|
||||||
|
show "Has email: {has_email}"
|
||||||
27
docs/examples/canonical/014-map-operations.lucidia
Normal file
27
docs/examples/canonical/014-map-operations.lucidia
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# 014: Map Operations
|
||||||
|
# Modifying maps
|
||||||
|
|
||||||
|
person = {
|
||||||
|
name: "Jordan",
|
||||||
|
age: 25
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add new property
|
||||||
|
person.email = "jordan@example.com"
|
||||||
|
show person
|
||||||
|
|
||||||
|
# Update existing property
|
||||||
|
person.age = 26
|
||||||
|
show "New age: {person.age}"
|
||||||
|
|
||||||
|
# Remove property
|
||||||
|
person.remove("email")
|
||||||
|
show person
|
||||||
|
|
||||||
|
# Get all keys
|
||||||
|
keys = person.keys()
|
||||||
|
show "Keys: {keys}"
|
||||||
|
|
||||||
|
# Get all values
|
||||||
|
values = person.values()
|
||||||
|
show "Values: {values}"
|
||||||
22
docs/examples/canonical/015-nested-structures.lucidia
Normal file
22
docs/examples/canonical/015-nested-structures.lucidia
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# 015: Nested Structures
|
||||||
|
# Complex data organization
|
||||||
|
|
||||||
|
# Lists inside maps
|
||||||
|
user = {
|
||||||
|
name: "Casey",
|
||||||
|
hobbies: ["reading", "hiking", "coding"],
|
||||||
|
settings: {
|
||||||
|
theme: "dark",
|
||||||
|
notifications: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Maps inside lists
|
||||||
|
users = [
|
||||||
|
{ name: "Alex", age: 28 },
|
||||||
|
{ name: "Jordan", age: 25 },
|
||||||
|
{ name: "Casey", age: 30 }
|
||||||
|
]
|
||||||
|
|
||||||
|
show user
|
||||||
|
show users
|
||||||
24
docs/examples/canonical/016-accessing-nested-data.lucidia
Normal file
24
docs/examples/canonical/016-accessing-nested-data.lucidia
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# 016: Accessing Nested Data
|
||||||
|
# Navigate deep structures
|
||||||
|
|
||||||
|
user = {
|
||||||
|
name: "Alex",
|
||||||
|
address: {
|
||||||
|
street: "123 Main St",
|
||||||
|
city: "Portland",
|
||||||
|
country: "USA"
|
||||||
|
},
|
||||||
|
contacts: ["alex@email.com", "+1-555-0100"]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Access nested map
|
||||||
|
city = user.address.city
|
||||||
|
show "City: {city}"
|
||||||
|
|
||||||
|
# Access nested list
|
||||||
|
email = user.contacts[0]
|
||||||
|
show "Email: {email}"
|
||||||
|
|
||||||
|
# Chain accessors
|
||||||
|
country = user.address.country
|
||||||
|
show "Lives in {country}"
|
||||||
22
docs/examples/canonical/017-sets.lucidia
Normal file
22
docs/examples/canonical/017-sets.lucidia
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# 017: Sets
|
||||||
|
# Unique collections (no duplicates)
|
||||||
|
|
||||||
|
# Create a set
|
||||||
|
tags = {"javascript", "python", "rust"}
|
||||||
|
|
||||||
|
# Add items
|
||||||
|
tags.add("go")
|
||||||
|
tags.add("python") # Already exists, won't duplicate
|
||||||
|
|
||||||
|
show tags # {"javascript", "python", "rust", "go"}
|
||||||
|
|
||||||
|
# Check membership
|
||||||
|
has_rust = tags.contains("rust")
|
||||||
|
show "Knows Rust: {has_rust}"
|
||||||
|
|
||||||
|
# Remove item
|
||||||
|
tags.remove("javascript")
|
||||||
|
show tags
|
||||||
|
|
||||||
|
# Set size
|
||||||
|
show "Number of languages: {tags.size}"
|
||||||
18
docs/examples/canonical/018-tuples.lucidia
Normal file
18
docs/examples/canonical/018-tuples.lucidia
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# 018: Tuples
|
||||||
|
# Fixed-size collections with mixed types
|
||||||
|
|
||||||
|
# Create a tuple (immutable)
|
||||||
|
point = (10, 20)
|
||||||
|
show "X: {point.0}, Y: {point.1}"
|
||||||
|
|
||||||
|
# Named tuple
|
||||||
|
person = (name: "Alex", age: 28)
|
||||||
|
show "Name: {person.name}"
|
||||||
|
show "Age: {person.age}"
|
||||||
|
|
||||||
|
# Function returning multiple values (tuple)
|
||||||
|
divide_with_remainder(a, b):
|
||||||
|
(quotient: a / b, remainder: a % b)
|
||||||
|
|
||||||
|
result = divide_with_remainder(17, 5)
|
||||||
|
show "17 ÷ 5 = {result.quotient} remainder {result.remainder}"
|
||||||
27
docs/examples/canonical/019-optional-values.lucidia
Normal file
27
docs/examples/canonical/019-optional-values.lucidia
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# 019: Optional Values
|
||||||
|
# Handling values that might not exist
|
||||||
|
|
||||||
|
# Function that might not return a value
|
||||||
|
find_user(id):
|
||||||
|
users = [
|
||||||
|
{ id: 1, name: "Alex" },
|
||||||
|
{ id: 2, name: "Jordan" }
|
||||||
|
]
|
||||||
|
|
||||||
|
for user in users:
|
||||||
|
if user.id == id:
|
||||||
|
return user
|
||||||
|
|
||||||
|
return null
|
||||||
|
|
||||||
|
# Try to find existing user
|
||||||
|
user = find_user(1)
|
||||||
|
if user != null:
|
||||||
|
show "Found: {user.name}"
|
||||||
|
else:
|
||||||
|
show "User not found"
|
||||||
|
|
||||||
|
# Try to find non-existent user
|
||||||
|
missing = find_user(99)
|
||||||
|
if missing == null:
|
||||||
|
show "User 99 doesn't exist"
|
||||||
22
docs/examples/canonical/020-working-with-nulls.lucidia
Normal file
22
docs/examples/canonical/020-working-with-nulls.lucidia
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# 020: Working with Nulls
|
||||||
|
# Safe handling of missing data
|
||||||
|
|
||||||
|
# Optional chaining - safe access
|
||||||
|
user = { name: "Alex" } # no email property
|
||||||
|
|
||||||
|
# This would error:
|
||||||
|
# email = user.email.lowercase()
|
||||||
|
|
||||||
|
# Safe version:
|
||||||
|
email = user.email?.lowercase() or "no email"
|
||||||
|
show email # "no email"
|
||||||
|
|
||||||
|
# Default values
|
||||||
|
user_with_email = { name: "Jordan", email: "j@example.com" }
|
||||||
|
user_without_email = { name: "Casey" }
|
||||||
|
|
||||||
|
get_email(user):
|
||||||
|
user.email or "unknown@example.com"
|
||||||
|
|
||||||
|
show get_email(user_with_email) # "j@example.com"
|
||||||
|
show get_email(user_without_email) # "unknown@example.com"
|
||||||
18
docs/examples/canonical/021-if-statements.lucidia
Normal file
18
docs/examples/canonical/021-if-statements.lucidia
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# 021: If Statements
|
||||||
|
# Conditional execution
|
||||||
|
|
||||||
|
age = 20
|
||||||
|
|
||||||
|
if age >= 18:
|
||||||
|
show "You are an adult"
|
||||||
|
|
||||||
|
# Only runs if condition is true
|
||||||
|
score = 85
|
||||||
|
|
||||||
|
if score >= 90:
|
||||||
|
show "Grade: A"
|
||||||
|
|
||||||
|
if score >= 80:
|
||||||
|
show "Grade: B"
|
||||||
|
|
||||||
|
# Both conditions can be true
|
||||||
17
docs/examples/canonical/022-if-else.lucidia
Normal file
17
docs/examples/canonical/022-if-else.lucidia
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# 022: If-Else
|
||||||
|
# Choose between two paths
|
||||||
|
|
||||||
|
age = 16
|
||||||
|
|
||||||
|
if age >= 18:
|
||||||
|
show "You can vote"
|
||||||
|
else:
|
||||||
|
show "Too young to vote"
|
||||||
|
|
||||||
|
# Check password strength
|
||||||
|
password = "abc"
|
||||||
|
|
||||||
|
if password.length >= 8:
|
||||||
|
show "Strong password"
|
||||||
|
else:
|
||||||
|
show "Password too short"
|
||||||
27
docs/examples/canonical/023-else-if-chains.lucidia
Normal file
27
docs/examples/canonical/023-else-if-chains.lucidia
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# 023: Else-If Chains
|
||||||
|
# Multiple conditions
|
||||||
|
|
||||||
|
score = 85
|
||||||
|
|
||||||
|
if score >= 90:
|
||||||
|
show "Grade: A"
|
||||||
|
else if score >= 80:
|
||||||
|
show "Grade: B"
|
||||||
|
else if score >= 70:
|
||||||
|
show "Grade: C"
|
||||||
|
else if score >= 60:
|
||||||
|
show "Grade: D"
|
||||||
|
else:
|
||||||
|
show "Grade: F"
|
||||||
|
|
||||||
|
# Temperature categorization
|
||||||
|
temp = 72
|
||||||
|
|
||||||
|
if temp > 80:
|
||||||
|
show "Hot"
|
||||||
|
else if temp > 60:
|
||||||
|
show "Comfortable"
|
||||||
|
else if temp > 40:
|
||||||
|
show "Cool"
|
||||||
|
else:
|
||||||
|
show "Cold"
|
||||||
29
docs/examples/canonical/024-comparison-operators.lucidia
Normal file
29
docs/examples/canonical/024-comparison-operators.lucidia
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# 024: Comparison Operators
|
||||||
|
# Testing relationships between values
|
||||||
|
|
||||||
|
a = 10
|
||||||
|
b = 20
|
||||||
|
|
||||||
|
# Equal
|
||||||
|
if a == 10:
|
||||||
|
show "a is 10"
|
||||||
|
|
||||||
|
# Not equal
|
||||||
|
if a != b:
|
||||||
|
show "a and b are different"
|
||||||
|
|
||||||
|
# Greater than
|
||||||
|
if b > a:
|
||||||
|
show "b is greater than a"
|
||||||
|
|
||||||
|
# Less than
|
||||||
|
if a < b:
|
||||||
|
show "a is less than b"
|
||||||
|
|
||||||
|
# Greater than or equal
|
||||||
|
if a >= 10:
|
||||||
|
show "a is at least 10"
|
||||||
|
|
||||||
|
# Less than or equal
|
||||||
|
if b <= 20:
|
||||||
|
show "b is at most 20"
|
||||||
29
docs/examples/canonical/025-logical-operators.lucidia
Normal file
29
docs/examples/canonical/025-logical-operators.lucidia
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# 025: Logical Operators
|
||||||
|
# Combining conditions
|
||||||
|
|
||||||
|
age = 25
|
||||||
|
has_license = true
|
||||||
|
|
||||||
|
# AND - both must be true
|
||||||
|
if age >= 18 and has_license:
|
||||||
|
show "Can drive"
|
||||||
|
|
||||||
|
# OR - at least one must be true
|
||||||
|
is_weekend = true
|
||||||
|
is_holiday = false
|
||||||
|
|
||||||
|
if is_weekend or is_holiday:
|
||||||
|
show "Day off!"
|
||||||
|
|
||||||
|
# NOT - invert a boolean
|
||||||
|
is_raining = false
|
||||||
|
|
||||||
|
if not is_raining:
|
||||||
|
show "Good day for a walk"
|
||||||
|
|
||||||
|
# Complex combinations
|
||||||
|
temperature = 75
|
||||||
|
is_sunny = true
|
||||||
|
|
||||||
|
if temperature > 70 and is_sunny and not is_raining:
|
||||||
|
show "Perfect beach day!"
|
||||||
23
docs/examples/canonical/026-for-loops.lucidia
Normal file
23
docs/examples/canonical/026-for-loops.lucidia
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# 026: For Loops
|
||||||
|
# Iterate over collections
|
||||||
|
|
||||||
|
# Loop over list
|
||||||
|
fruits = ["apple", "banana", "cherry"]
|
||||||
|
|
||||||
|
for fruit in fruits:
|
||||||
|
show "I like {fruit}"
|
||||||
|
|
||||||
|
# Loop over range
|
||||||
|
for i in 1..5:
|
||||||
|
show "Count: {i}"
|
||||||
|
|
||||||
|
# Loop over map
|
||||||
|
user = { name: "Alex", age: 28, city: "Portland" }
|
||||||
|
|
||||||
|
for key in user.keys():
|
||||||
|
show "{key}: {user[key]}"
|
||||||
|
|
||||||
|
# Nested loops
|
||||||
|
for x in 1..3:
|
||||||
|
for y in 1..3:
|
||||||
|
show "{x} × {y} = {x * y}"
|
||||||
26
docs/examples/canonical/027-while-loops.lucidia
Normal file
26
docs/examples/canonical/027-while-loops.lucidia
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# 027: While Loops
|
||||||
|
# Repeat while condition is true
|
||||||
|
|
||||||
|
# Count up
|
||||||
|
count = 1
|
||||||
|
|
||||||
|
while count <= 5:
|
||||||
|
show "Count: {count}"
|
||||||
|
count = count + 1
|
||||||
|
|
||||||
|
# Wait for condition
|
||||||
|
password_attempts = 0
|
||||||
|
max_attempts = 3
|
||||||
|
|
||||||
|
while password_attempts < max_attempts:
|
||||||
|
# In real code, get user input here
|
||||||
|
password_attempts = password_attempts + 1
|
||||||
|
show "Attempt {password_attempts}"
|
||||||
|
|
||||||
|
# While with complex condition
|
||||||
|
balance = 100
|
||||||
|
price = 15
|
||||||
|
|
||||||
|
while balance >= price:
|
||||||
|
show "Purchase made, balance: {balance}"
|
||||||
|
balance = balance - price
|
||||||
31
docs/examples/canonical/028-loop-control.lucidia
Normal file
31
docs/examples/canonical/028-loop-control.lucidia
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# 028: Loop Control
|
||||||
|
# Break and continue
|
||||||
|
|
||||||
|
# Break - exit loop early
|
||||||
|
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||||
|
|
||||||
|
for num in numbers:
|
||||||
|
if num > 5:
|
||||||
|
break # Stop here
|
||||||
|
show num
|
||||||
|
|
||||||
|
# Outputs: 1, 2, 3, 4, 5
|
||||||
|
|
||||||
|
# Continue - skip to next iteration
|
||||||
|
for num in numbers:
|
||||||
|
if num % 2 == 0:
|
||||||
|
continue # Skip even numbers
|
||||||
|
show num
|
||||||
|
|
||||||
|
# Outputs: 1, 3, 5, 7, 9
|
||||||
|
|
||||||
|
# Find first match
|
||||||
|
users = ["Alice", "Bob", "Charlie", "David"]
|
||||||
|
found = null
|
||||||
|
|
||||||
|
for user in users:
|
||||||
|
if user[0] == "C":
|
||||||
|
found = user
|
||||||
|
break
|
||||||
|
|
||||||
|
show "Found: {found}" # Charlie
|
||||||
30
docs/examples/canonical/029-pattern-matching.lucidia
Normal file
30
docs/examples/canonical/029-pattern-matching.lucidia
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# 029: Pattern Matching
|
||||||
|
# Match values against patterns
|
||||||
|
|
||||||
|
status = "pending"
|
||||||
|
|
||||||
|
# Basic match
|
||||||
|
status is:
|
||||||
|
"pending": show "Waiting..."
|
||||||
|
"approved": show "Approved!"
|
||||||
|
"rejected": show "Rejected"
|
||||||
|
_: show "Unknown status"
|
||||||
|
|
||||||
|
# Match with values
|
||||||
|
score = 85
|
||||||
|
|
||||||
|
score is:
|
||||||
|
100: show "Perfect!"
|
||||||
|
90..99: show "Excellent"
|
||||||
|
80..89: show "Good"
|
||||||
|
70..79: show "Fair"
|
||||||
|
_: show "Needs improvement"
|
||||||
|
|
||||||
|
# Match with conditions
|
||||||
|
user_type = "admin"
|
||||||
|
|
||||||
|
user_type is:
|
||||||
|
"admin": show "Full access"
|
||||||
|
"moderator": show "Limited access"
|
||||||
|
"user": show "Basic access"
|
||||||
|
_: show "No access"
|
||||||
33
docs/examples/canonical/030-guards.lucidia
Normal file
33
docs/examples/canonical/030-guards.lucidia
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# 030: Guards
|
||||||
|
# Early returns and validation
|
||||||
|
|
||||||
|
# Guard clause pattern
|
||||||
|
process_payment(amount, balance):
|
||||||
|
# Guard: insufficient funds
|
||||||
|
if amount > balance:
|
||||||
|
return "Insufficient funds"
|
||||||
|
|
||||||
|
# Guard: invalid amount
|
||||||
|
if amount <= 0:
|
||||||
|
return "Invalid amount"
|
||||||
|
|
||||||
|
# Main logic only runs if guards pass
|
||||||
|
new_balance = balance - amount
|
||||||
|
return "Payment successful, new balance: {new_balance}"
|
||||||
|
|
||||||
|
show process_payment(50, 100) # Success
|
||||||
|
show process_payment(150, 100) # Insufficient funds
|
||||||
|
show process_payment(-10, 100) # Invalid amount
|
||||||
|
|
||||||
|
# Guards make code clearer than nested ifs
|
||||||
|
validate_user(user):
|
||||||
|
if user == null:
|
||||||
|
return "User is required"
|
||||||
|
|
||||||
|
if not user.has("email"):
|
||||||
|
return "Email is required"
|
||||||
|
|
||||||
|
if user.email.length < 5:
|
||||||
|
return "Invalid email"
|
||||||
|
|
||||||
|
return "Valid user"
|
||||||
26
docs/examples/canonical/031-anonymous-functions.lucidia
Normal file
26
docs/examples/canonical/031-anonymous-functions.lucidia
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# 031: Anonymous Functions
|
||||||
|
# Functions without names (lambdas)
|
||||||
|
|
||||||
|
# Store in variable
|
||||||
|
double = (x) => x * 2
|
||||||
|
|
||||||
|
show double(5) # 10
|
||||||
|
|
||||||
|
# Pass as argument
|
||||||
|
apply_twice(f, x):
|
||||||
|
f(f(x))
|
||||||
|
|
||||||
|
result = apply_twice((n) => n + 1, 10)
|
||||||
|
show result # 12
|
||||||
|
|
||||||
|
# Multiple parameters
|
||||||
|
add = (a, b) => a + b
|
||||||
|
show add(3, 4) # 7
|
||||||
|
|
||||||
|
# Multi-line anonymous function
|
||||||
|
greet = (name) => {
|
||||||
|
message = "Hello, {name}!"
|
||||||
|
message.uppercase()
|
||||||
|
}
|
||||||
|
|
||||||
|
show greet("alex") # HELLO, ALEX!
|
||||||
23
docs/examples/canonical/032-higher-order-functions.lucidia
Normal file
23
docs/examples/canonical/032-higher-order-functions.lucidia
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# 032: Higher-Order Functions
|
||||||
|
# Functions that take or return functions
|
||||||
|
|
||||||
|
# Function that returns a function
|
||||||
|
make_multiplier(factor):
|
||||||
|
return (x) => x * factor
|
||||||
|
|
||||||
|
# Create specialized functions
|
||||||
|
double = make_multiplier(2)
|
||||||
|
triple = make_multiplier(3)
|
||||||
|
|
||||||
|
show double(5) # 10
|
||||||
|
show triple(5) # 15
|
||||||
|
|
||||||
|
# Function that takes a function
|
||||||
|
apply_operation(a, b, operation):
|
||||||
|
operation(a, b)
|
||||||
|
|
||||||
|
result = apply_operation(10, 5, (x, y) => x + y)
|
||||||
|
show result # 15
|
||||||
|
|
||||||
|
result = apply_operation(10, 5, (x, y) => x * y)
|
||||||
|
show result # 50
|
||||||
27
docs/examples/canonical/033-map.lucidia
Normal file
27
docs/examples/canonical/033-map.lucidia
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# 033: Map
|
||||||
|
# Transform each element in a collection
|
||||||
|
|
||||||
|
numbers = [1, 2, 3, 4, 5]
|
||||||
|
|
||||||
|
# Double each number
|
||||||
|
doubled = numbers.map((x) => x * 2)
|
||||||
|
show doubled # [2, 4, 6, 8, 10]
|
||||||
|
|
||||||
|
# Square each number
|
||||||
|
squared = numbers.map((x) => x * x)
|
||||||
|
show squared # [1, 4, 9, 16, 25]
|
||||||
|
|
||||||
|
# Transform strings
|
||||||
|
names = ["alex", "jordan", "casey"]
|
||||||
|
uppercase_names = names.map((name) => name.uppercase())
|
||||||
|
show uppercase_names # ["ALEX", "JORDAN", "CASEY"]
|
||||||
|
|
||||||
|
# Extract property from objects
|
||||||
|
users = [
|
||||||
|
{ name: "Alex", age: 28 },
|
||||||
|
{ name: "Jordan", age: 25 },
|
||||||
|
{ name: "Casey", age: 30 }
|
||||||
|
]
|
||||||
|
|
||||||
|
names = users.map((user) => user.name)
|
||||||
|
show names # ["Alex", "Jordan", "Casey"]
|
||||||
28
docs/examples/canonical/034-filter.lucidia
Normal file
28
docs/examples/canonical/034-filter.lucidia
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# 034: Filter
|
||||||
|
# Keep only elements that match a condition
|
||||||
|
|
||||||
|
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||||
|
|
||||||
|
# Keep even numbers
|
||||||
|
evens = numbers.filter((x) => x % 2 == 0)
|
||||||
|
show evens # [2, 4, 6, 8, 10]
|
||||||
|
|
||||||
|
# Keep numbers greater than 5
|
||||||
|
large = numbers.filter((x) => x > 5)
|
||||||
|
show large # [6, 7, 8, 9, 10]
|
||||||
|
|
||||||
|
# Filter objects
|
||||||
|
users = [
|
||||||
|
{ name: "Alex", age: 28, active: true },
|
||||||
|
{ name: "Jordan", age: 17, active: true },
|
||||||
|
{ name: "Casey", age: 30, active: false }
|
||||||
|
]
|
||||||
|
|
||||||
|
# Keep active adults
|
||||||
|
active_adults = users.filter((user) => user.age >= 18 and user.active)
|
||||||
|
show active_adults # [{ name: "Alex", age: 28, active: true }]
|
||||||
|
|
||||||
|
# Filter strings
|
||||||
|
words = ["hello", "hi", "goodbye", "bye"]
|
||||||
|
short_words = words.filter((word) => word.length <= 3)
|
||||||
|
show short_words # ["hi", "bye"]
|
||||||
29
docs/examples/canonical/035-reduce.lucidia
Normal file
29
docs/examples/canonical/035-reduce.lucidia
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# 035: Reduce
|
||||||
|
# Combine all elements into a single value
|
||||||
|
|
||||||
|
numbers = [1, 2, 3, 4, 5]
|
||||||
|
|
||||||
|
# Sum all numbers
|
||||||
|
total = numbers.reduce((sum, num) => sum + num, 0)
|
||||||
|
show total # 15
|
||||||
|
|
||||||
|
# Product of all numbers
|
||||||
|
product = numbers.reduce((prod, num) => prod * num, 1)
|
||||||
|
show product # 120
|
||||||
|
|
||||||
|
# Find maximum
|
||||||
|
max = numbers.reduce((max_so_far, num) => {
|
||||||
|
if num > max_so_far:
|
||||||
|
num
|
||||||
|
else:
|
||||||
|
max_so_far
|
||||||
|
}, numbers[0])
|
||||||
|
show max # 5
|
||||||
|
|
||||||
|
# Build object from list
|
||||||
|
items = ["apple", "banana", "cherry"]
|
||||||
|
counts = items.reduce((obj, item) => {
|
||||||
|
obj[item] = item.length
|
||||||
|
obj
|
||||||
|
}, {})
|
||||||
|
show counts # { apple: 5, banana: 6, cherry: 6 }
|
||||||
29
docs/examples/canonical/036-function-composition.lucidia
Normal file
29
docs/examples/canonical/036-function-composition.lucidia
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# 036: Function Composition
|
||||||
|
# Combine functions to create new ones
|
||||||
|
|
||||||
|
# Simple functions
|
||||||
|
add_one = (x) => x + 1
|
||||||
|
double = (x) => x * 2
|
||||||
|
square = (x) => x * x
|
||||||
|
|
||||||
|
# Manual composition
|
||||||
|
result = square(double(add_one(3)))
|
||||||
|
show result # (3+1)*2 = 8, 8*8 = 64
|
||||||
|
|
||||||
|
# Compose helper
|
||||||
|
compose(f, g):
|
||||||
|
return (x) => f(g(x))
|
||||||
|
|
||||||
|
# Create composed function
|
||||||
|
double_then_square = compose(square, double)
|
||||||
|
show double_then_square(5) # (5*2)^2 = 100
|
||||||
|
|
||||||
|
# Chain multiple operations
|
||||||
|
numbers = [1, 2, 3, 4, 5]
|
||||||
|
|
||||||
|
result = numbers
|
||||||
|
.map((x) => x * 2) # double each
|
||||||
|
.filter((x) => x > 5) # keep if > 5
|
||||||
|
.reduce((sum, x) => sum + x, 0) # sum all
|
||||||
|
|
||||||
|
show result # 6 + 8 + 10 = 24
|
||||||
45
docs/examples/canonical/037-closures.lucidia
Normal file
45
docs/examples/canonical/037-closures.lucidia
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# 037: Closures
|
||||||
|
# Functions that remember their environment
|
||||||
|
|
||||||
|
# Counter closure
|
||||||
|
make_counter():
|
||||||
|
count = 0
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
count = count + 1
|
||||||
|
count
|
||||||
|
}
|
||||||
|
|
||||||
|
# Each counter has its own state
|
||||||
|
counter1 = make_counter()
|
||||||
|
counter2 = make_counter()
|
||||||
|
|
||||||
|
show counter1() # 1
|
||||||
|
show counter1() # 2
|
||||||
|
show counter1() # 3
|
||||||
|
|
||||||
|
show counter2() # 1 (separate counter)
|
||||||
|
show counter2() # 2
|
||||||
|
|
||||||
|
# Private variables
|
||||||
|
make_account(initial_balance):
|
||||||
|
balance = initial_balance
|
||||||
|
|
||||||
|
return {
|
||||||
|
deposit: (amount) => {
|
||||||
|
balance = balance + amount
|
||||||
|
balance
|
||||||
|
},
|
||||||
|
withdraw: (amount) => {
|
||||||
|
if amount > balance:
|
||||||
|
return "Insufficient funds"
|
||||||
|
balance = balance - amount
|
||||||
|
balance
|
||||||
|
},
|
||||||
|
get_balance: () => balance
|
||||||
|
}
|
||||||
|
|
||||||
|
account = make_account(100)
|
||||||
|
show account.deposit(50) # 150
|
||||||
|
show account.withdraw(30) # 120
|
||||||
|
show account.get_balance() # 120
|
||||||
33
docs/examples/canonical/038-partial-application.lucidia
Normal file
33
docs/examples/canonical/038-partial-application.lucidia
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# 038: Partial Application
|
||||||
|
# Pre-fill some arguments
|
||||||
|
|
||||||
|
# Generic function
|
||||||
|
multiply(a, b):
|
||||||
|
a * b
|
||||||
|
|
||||||
|
# Partially apply
|
||||||
|
double = (x) => multiply(2, x)
|
||||||
|
triple = (x) => multiply(3, x)
|
||||||
|
|
||||||
|
show double(5) # 10
|
||||||
|
show triple(5) # 15
|
||||||
|
|
||||||
|
# Partial application helper
|
||||||
|
partial(fn, ...fixed_args):
|
||||||
|
return (...remaining_args) => fn(...fixed_args, ...remaining_args)
|
||||||
|
|
||||||
|
# Create specialized versions
|
||||||
|
greet(greeting, name):
|
||||||
|
"{greeting}, {name}!"
|
||||||
|
|
||||||
|
say_hello = partial(greet, "Hello")
|
||||||
|
say_goodbye = partial(greet, "Goodbye")
|
||||||
|
|
||||||
|
show say_hello("Alex") # Hello, Alex!
|
||||||
|
show say_goodbye("Alex") # Goodbye, Alex!
|
||||||
|
|
||||||
|
# Useful for map/filter
|
||||||
|
numbers = [1, 2, 3, 4, 5]
|
||||||
|
add_ten = partial((a, b) => a + b, 10)
|
||||||
|
result = numbers.map(add_ten)
|
||||||
|
show result # [11, 12, 13, 14, 15]
|
||||||
37
docs/examples/canonical/039-recursion.lucidia
Normal file
37
docs/examples/canonical/039-recursion.lucidia
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# 039: Recursion
|
||||||
|
# Functions that call themselves
|
||||||
|
|
||||||
|
# Factorial
|
||||||
|
factorial(n):
|
||||||
|
if n <= 1:
|
||||||
|
return 1
|
||||||
|
return n * factorial(n - 1)
|
||||||
|
|
||||||
|
show factorial(5) # 120 (5 * 4 * 3 * 2 * 1)
|
||||||
|
|
||||||
|
# Fibonacci
|
||||||
|
fibonacci(n):
|
||||||
|
if n <= 1:
|
||||||
|
return n
|
||||||
|
return fibonacci(n - 1) + fibonacci(n - 2)
|
||||||
|
|
||||||
|
show fibonacci(7) # 13
|
||||||
|
|
||||||
|
# Sum of list
|
||||||
|
sum_list(numbers):
|
||||||
|
if numbers.length == 0:
|
||||||
|
return 0
|
||||||
|
return numbers[0] + sum_list(numbers[1:])
|
||||||
|
|
||||||
|
show sum_list([1, 2, 3, 4, 5]) # 15
|
||||||
|
|
||||||
|
# Countdown
|
||||||
|
countdown(n):
|
||||||
|
if n <= 0:
|
||||||
|
show "Blast off!"
|
||||||
|
return
|
||||||
|
|
||||||
|
show n
|
||||||
|
countdown(n - 1)
|
||||||
|
|
||||||
|
countdown(5)
|
||||||
33
docs/examples/canonical/040-tail-recursion.lucidia
Normal file
33
docs/examples/canonical/040-tail-recursion.lucidia
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# 040: Tail Recursion
|
||||||
|
# Efficient recursion (last call is recursive call)
|
||||||
|
|
||||||
|
# Regular recursion (not tail-recursive)
|
||||||
|
factorial_regular(n):
|
||||||
|
if n <= 1:
|
||||||
|
return 1
|
||||||
|
return n * factorial_regular(n - 1) # Multiplication happens AFTER recursive call
|
||||||
|
|
||||||
|
# Tail-recursive version (with accumulator)
|
||||||
|
factorial_tail(n, accumulator = 1):
|
||||||
|
if n <= 1:
|
||||||
|
return accumulator
|
||||||
|
return factorial_tail(n - 1, n * accumulator) # Recursive call is LAST thing
|
||||||
|
|
||||||
|
show factorial_tail(5) # 120
|
||||||
|
|
||||||
|
# Sum with tail recursion
|
||||||
|
sum_tail(numbers, accumulator = 0):
|
||||||
|
if numbers.length == 0:
|
||||||
|
return accumulator
|
||||||
|
return sum_tail(numbers[1:], accumulator + numbers[0])
|
||||||
|
|
||||||
|
show sum_tail([1, 2, 3, 4, 5]) # 15
|
||||||
|
|
||||||
|
# Range generator
|
||||||
|
make_range(start, end, result = []):
|
||||||
|
if start > end:
|
||||||
|
return result
|
||||||
|
result.append(start)
|
||||||
|
return make_range(start + 1, end, result)
|
||||||
|
|
||||||
|
show make_range(1, 10) # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||||
27
docs/examples/canonical/041-showing-output.lucidia
Normal file
27
docs/examples/canonical/041-showing-output.lucidia
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# 041: Showing Output
|
||||||
|
# Display information to the user
|
||||||
|
|
||||||
|
# Simple text
|
||||||
|
show "Hello!"
|
||||||
|
|
||||||
|
# Variables
|
||||||
|
name = "Alex"
|
||||||
|
show name
|
||||||
|
|
||||||
|
# Template strings
|
||||||
|
age = 28
|
||||||
|
show "I am {age} years old"
|
||||||
|
|
||||||
|
# Multiple values
|
||||||
|
show "Name: {name}, Age: {age}"
|
||||||
|
|
||||||
|
# Show results of expressions
|
||||||
|
show "5 + 3 = {5 + 3}"
|
||||||
|
|
||||||
|
# Show objects
|
||||||
|
user = { name: "Alex", age: 28 }
|
||||||
|
show user
|
||||||
|
|
||||||
|
# Show lists
|
||||||
|
items = ["apple", "banana", "cherry"]
|
||||||
|
show items
|
||||||
19
docs/examples/canonical/042-user-input.lucidia
Normal file
19
docs/examples/canonical/042-user-input.lucidia
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# 042: User Input
|
||||||
|
# Get data from the user
|
||||||
|
|
||||||
|
# Simple text input
|
||||||
|
ask "What's your name?" -> name
|
||||||
|
show "Hello, {name}!"
|
||||||
|
|
||||||
|
# Number input
|
||||||
|
ask "How old are you?" -> age
|
||||||
|
show "You are {age} years old"
|
||||||
|
|
||||||
|
# Multiple inputs
|
||||||
|
ask "What's your email?" -> email
|
||||||
|
ask "Choose a password:" -> password
|
||||||
|
|
||||||
|
show "Account created for {email}"
|
||||||
|
|
||||||
|
# Input with validation happens automatically
|
||||||
|
# (AI adds validation based on variable name patterns)
|
||||||
16
docs/examples/canonical/043-simple-form.lucidia
Normal file
16
docs/examples/canonical/043-simple-form.lucidia
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# 043: Simple Form
|
||||||
|
# Structured input collection
|
||||||
|
|
||||||
|
form contact:
|
||||||
|
input name -> user.name
|
||||||
|
input email -> user.email
|
||||||
|
input message -> user.message
|
||||||
|
button "Send" -> send_message(user)
|
||||||
|
|
||||||
|
send_message(data):
|
||||||
|
show "Message from {data.name}"
|
||||||
|
show "Email: {data.email}"
|
||||||
|
show "Message: {data.message}"
|
||||||
|
|
||||||
|
# Form automatically renders in the UI
|
||||||
|
# Button triggers the handler function
|
||||||
27
docs/examples/canonical/044-input-validation.lucidia
Normal file
27
docs/examples/canonical/044-input-validation.lucidia
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# 044: Input Validation
|
||||||
|
# Ensure data is correct
|
||||||
|
|
||||||
|
form signup:
|
||||||
|
input email -> user.email
|
||||||
|
validate: is_email
|
||||||
|
error: "Please enter a valid email"
|
||||||
|
|
||||||
|
input password -> user.password
|
||||||
|
validate: (pw) => pw.length >= 8
|
||||||
|
error: "Password must be at least 8 characters"
|
||||||
|
|
||||||
|
input age -> user.age
|
||||||
|
validate: (a) => a >= 18
|
||||||
|
error: "Must be 18 or older"
|
||||||
|
|
||||||
|
button "Sign Up" -> create_account(user)
|
||||||
|
|
||||||
|
create_account(data):
|
||||||
|
show "Account created for {data.email}"
|
||||||
|
|
||||||
|
# Built-in validators:
|
||||||
|
# - is_email
|
||||||
|
# - is_url
|
||||||
|
# - is_phone
|
||||||
|
# - is_number
|
||||||
|
# Custom validators are just functions that return true/false
|
||||||
32
docs/examples/canonical/045-buttons.lucidia
Normal file
32
docs/examples/canonical/045-buttons.lucidia
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# 045: Buttons
|
||||||
|
# Clickable actions
|
||||||
|
|
||||||
|
# Simple button
|
||||||
|
button "Click Me" -> handle_click()
|
||||||
|
|
||||||
|
handle_click():
|
||||||
|
show "Button was clicked!"
|
||||||
|
|
||||||
|
# Button with data
|
||||||
|
button "Increment" -> increment()
|
||||||
|
button "Decrement" -> decrement()
|
||||||
|
|
||||||
|
counter = 0
|
||||||
|
|
||||||
|
increment():
|
||||||
|
counter = counter + 1
|
||||||
|
show "Count: {counter}"
|
||||||
|
|
||||||
|
decrement():
|
||||||
|
counter = counter - 1
|
||||||
|
show "Count: {counter}"
|
||||||
|
|
||||||
|
# Styled buttons (optional)
|
||||||
|
button "Primary" -> do_something()
|
||||||
|
style: "primary"
|
||||||
|
|
||||||
|
button "Danger" -> do_something_risky()
|
||||||
|
style: "danger"
|
||||||
|
|
||||||
|
button "Disabled" -> nothing()
|
||||||
|
disabled: true
|
||||||
31
docs/examples/canonical/046-multiple-inputs.lucidia
Normal file
31
docs/examples/canonical/046-multiple-inputs.lucidia
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# 046: Multiple Inputs
|
||||||
|
# Complex forms with many fields
|
||||||
|
|
||||||
|
form user_profile:
|
||||||
|
input firstname -> profile.firstname
|
||||||
|
placeholder: "First name"
|
||||||
|
|
||||||
|
input lastname -> profile.lastname
|
||||||
|
placeholder: "Last name"
|
||||||
|
|
||||||
|
input email -> profile.email
|
||||||
|
type: "email"
|
||||||
|
validate: is_email
|
||||||
|
|
||||||
|
input phone -> profile.phone
|
||||||
|
type: "tel"
|
||||||
|
placeholder: "+1-555-0100"
|
||||||
|
|
||||||
|
input bio -> profile.bio
|
||||||
|
type: "textarea"
|
||||||
|
placeholder: "Tell us about yourself"
|
||||||
|
rows: 4
|
||||||
|
|
||||||
|
button "Save Profile" -> save_profile(profile)
|
||||||
|
button "Cancel" -> cancel()
|
||||||
|
|
||||||
|
save_profile(data):
|
||||||
|
show "Profile saved for {data.firstname} {data.lastname}"
|
||||||
|
|
||||||
|
cancel():
|
||||||
|
show "Profile editing cancelled"
|
||||||
29
docs/examples/canonical/047-dropdowns.lucidia
Normal file
29
docs/examples/canonical/047-dropdowns.lucidia
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# 047: Dropdowns
|
||||||
|
# Select from options
|
||||||
|
|
||||||
|
form preferences:
|
||||||
|
select theme -> user.theme
|
||||||
|
options: ["light", "dark", "auto"]
|
||||||
|
default: "auto"
|
||||||
|
|
||||||
|
select language -> user.language
|
||||||
|
options: [
|
||||||
|
{ value: "en", label: "English" },
|
||||||
|
{ value: "es", label: "Español" },
|
||||||
|
{ value: "fr", label: "Français" }
|
||||||
|
]
|
||||||
|
default: "en"
|
||||||
|
|
||||||
|
select country -> user.country
|
||||||
|
options: get_countries()
|
||||||
|
searchable: true
|
||||||
|
|
||||||
|
button "Save" -> save_preferences(user)
|
||||||
|
|
||||||
|
get_countries():
|
||||||
|
return ["USA", "Canada", "Mexico", "UK", "France", "Germany"]
|
||||||
|
|
||||||
|
save_preferences(prefs):
|
||||||
|
show "Theme: {prefs.theme}"
|
||||||
|
show "Language: {prefs.language}"
|
||||||
|
show "Country: {prefs.country}"
|
||||||
31
docs/examples/canonical/048-checkboxes.lucidia
Normal file
31
docs/examples/canonical/048-checkboxes.lucidia
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# 048: Checkboxes
|
||||||
|
# Multiple selections
|
||||||
|
|
||||||
|
form newsletter:
|
||||||
|
checkbox "Daily digest" -> subscriptions.daily
|
||||||
|
checkbox "Weekly roundup" -> subscriptions.weekly
|
||||||
|
checkbox "Product updates" -> subscriptions.products
|
||||||
|
checkbox "Marketing emails" -> subscriptions.marketing
|
||||||
|
|
||||||
|
button "Subscribe" -> save_subscriptions(subscriptions)
|
||||||
|
|
||||||
|
save_subscriptions(subs):
|
||||||
|
show "Subscriptions:"
|
||||||
|
if subs.daily:
|
||||||
|
show " - Daily digest"
|
||||||
|
if subs.weekly:
|
||||||
|
show " - Weekly roundup"
|
||||||
|
if subs.products:
|
||||||
|
show " - Product updates"
|
||||||
|
if subs.marketing:
|
||||||
|
show " - Marketing emails"
|
||||||
|
|
||||||
|
# Checkbox groups
|
||||||
|
form interests:
|
||||||
|
checkbox_group "What interests you?" -> user.interests
|
||||||
|
options: ["Technology", "Sports", "Music", "Art", "Travel"]
|
||||||
|
|
||||||
|
button "Save" -> save_interests(user)
|
||||||
|
|
||||||
|
save_interests(data):
|
||||||
|
show "Interests: {data.interests}"
|
||||||
35
docs/examples/canonical/049-radio-buttons.lucidia
Normal file
35
docs/examples/canonical/049-radio-buttons.lucidia
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# 049: Radio Buttons
|
||||||
|
# Single selection from options
|
||||||
|
|
||||||
|
form survey:
|
||||||
|
radio_group "How satisfied are you?" -> feedback.satisfaction
|
||||||
|
options: [
|
||||||
|
"Very satisfied",
|
||||||
|
"Satisfied",
|
||||||
|
"Neutral",
|
||||||
|
"Dissatisfied",
|
||||||
|
"Very dissatisfied"
|
||||||
|
]
|
||||||
|
|
||||||
|
radio_group "Would you recommend us?" -> feedback.recommend
|
||||||
|
options: ["Yes", "No", "Maybe"]
|
||||||
|
|
||||||
|
button "Submit" -> submit_feedback(feedback)
|
||||||
|
|
||||||
|
submit_feedback(data):
|
||||||
|
show "Satisfaction: {data.satisfaction}"
|
||||||
|
show "Recommend: {data.recommend}"
|
||||||
|
|
||||||
|
# Radio with custom values
|
||||||
|
form payment:
|
||||||
|
radio_group "Payment method" -> order.payment_method
|
||||||
|
options: [
|
||||||
|
{ value: "card", label: "Credit Card" },
|
||||||
|
{ value: "paypal", label: "PayPal" },
|
||||||
|
{ value: "crypto", label: "Cryptocurrency" }
|
||||||
|
]
|
||||||
|
|
||||||
|
button "Continue" -> process_payment(order)
|
||||||
|
|
||||||
|
process_payment(data):
|
||||||
|
show "Payment method: {data.payment_method}"
|
||||||
47
docs/examples/canonical/050-form-submission.lucidia
Normal file
47
docs/examples/canonical/050-form-submission.lucidia
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# 050: Form Submission
|
||||||
|
# Handle complete form workflows
|
||||||
|
|
||||||
|
form login:
|
||||||
|
input email -> credentials.email
|
||||||
|
type: "email"
|
||||||
|
validate: is_email
|
||||||
|
required: true
|
||||||
|
|
||||||
|
input password -> credentials.password
|
||||||
|
type: "password"
|
||||||
|
validate: (pw) => pw.length >= 8
|
||||||
|
required: true
|
||||||
|
|
||||||
|
checkbox "Remember me" -> credentials.remember
|
||||||
|
|
||||||
|
button "Log In" -> attempt_login(credentials)
|
||||||
|
button "Forgot Password?" -> reset_password()
|
||||||
|
|
||||||
|
attempt_login(creds):
|
||||||
|
# Validate all fields are present
|
||||||
|
if not creds.email or not creds.password:
|
||||||
|
show "Please fill in all fields"
|
||||||
|
return
|
||||||
|
|
||||||
|
# In real app, this would call an API
|
||||||
|
show "Logging in as {creds.email}..."
|
||||||
|
|
||||||
|
# Simulate API call
|
||||||
|
success = true
|
||||||
|
|
||||||
|
if success:
|
||||||
|
show "Login successful!"
|
||||||
|
if creds.remember:
|
||||||
|
show "You will be remembered"
|
||||||
|
else:
|
||||||
|
show "Invalid credentials"
|
||||||
|
|
||||||
|
reset_password():
|
||||||
|
ask "Enter your email:" -> email
|
||||||
|
show "Password reset link sent to {email}"
|
||||||
|
|
||||||
|
# Form automatically handles:
|
||||||
|
# - Validation before submission
|
||||||
|
# - Disabled submit button if invalid
|
||||||
|
# - Error messages next to fields
|
||||||
|
# - Loading states during async operations
|
||||||
23
docs/examples/canonical/051-state-declaration.lucidia
Normal file
23
docs/examples/canonical/051-state-declaration.lucidia
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# 051: State Declaration
|
||||||
|
# Reactive variables that trigger UI updates
|
||||||
|
|
||||||
|
# Declare reactive state
|
||||||
|
state count = 0
|
||||||
|
state username = "Guest"
|
||||||
|
state is_logged_in = false
|
||||||
|
|
||||||
|
# When state changes, UI automatically updates
|
||||||
|
button "Increment" -> increment()
|
||||||
|
button "Login" -> login()
|
||||||
|
|
||||||
|
increment():
|
||||||
|
count = count + 1
|
||||||
|
show "Count: {count}" # UI re-renders automatically
|
||||||
|
|
||||||
|
login():
|
||||||
|
username = "Alex"
|
||||||
|
is_logged_in = true
|
||||||
|
show "Welcome, {username}!"
|
||||||
|
|
||||||
|
# Regular variables don't trigger UI updates
|
||||||
|
# State variables do - that's the difference
|
||||||
27
docs/examples/canonical/052-watching-state.lucidia
Normal file
27
docs/examples/canonical/052-watching-state.lucidia
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# 052: Watching State
|
||||||
|
# React to state changes
|
||||||
|
|
||||||
|
state temperature = 72
|
||||||
|
|
||||||
|
# Watch for changes
|
||||||
|
watch temperature:
|
||||||
|
show "Temperature is now {temperature}°F"
|
||||||
|
|
||||||
|
if temperature > 80:
|
||||||
|
show "It's hot!"
|
||||||
|
else if temperature < 60:
|
||||||
|
show "It's cold!"
|
||||||
|
else:
|
||||||
|
show "Temperature is comfortable"
|
||||||
|
|
||||||
|
# Modify state
|
||||||
|
button "Increase" -> increase_temp()
|
||||||
|
button "Decrease" -> decrease_temp()
|
||||||
|
|
||||||
|
increase_temp():
|
||||||
|
temperature = temperature + 5
|
||||||
|
|
||||||
|
decrease_temp():
|
||||||
|
temperature = temperature - 5
|
||||||
|
|
||||||
|
# Watcher runs automatically whenever temperature changes
|
||||||
36
docs/examples/canonical/053-computed-values.lucidia
Normal file
36
docs/examples/canonical/053-computed-values.lucidia
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# 053: Computed Values
|
||||||
|
# Derived state that updates automatically
|
||||||
|
|
||||||
|
state firstname = "Alex"
|
||||||
|
state lastname = "Chen"
|
||||||
|
|
||||||
|
# Computed value - automatically recalculates when dependencies change
|
||||||
|
computed fullname = "{firstname} {lastname}"
|
||||||
|
|
||||||
|
show fullname # "Alex Chen"
|
||||||
|
|
||||||
|
# Update state
|
||||||
|
button "Change Name" -> change_name()
|
||||||
|
|
||||||
|
change_name():
|
||||||
|
firstname = "Jordan"
|
||||||
|
lastname = "Lee"
|
||||||
|
# fullname automatically becomes "Jordan Lee"
|
||||||
|
|
||||||
|
# Shopping cart example
|
||||||
|
state items = [
|
||||||
|
{ name: "Apple", price: 1.50, quantity: 3 },
|
||||||
|
{ name: "Banana", price: 0.75, quantity: 5 }
|
||||||
|
]
|
||||||
|
|
||||||
|
computed total = items.reduce((sum, item) => {
|
||||||
|
sum + (item.price * item.quantity)
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
show "Total: ${total}" # Automatically updates when items change
|
||||||
|
|
||||||
|
button "Add Orange" -> add_item()
|
||||||
|
|
||||||
|
add_item():
|
||||||
|
items.append({ name: "Orange", price: 2.00, quantity: 2 })
|
||||||
|
# total automatically recalculates
|
||||||
38
docs/examples/canonical/054-multiple-watchers.lucidia
Normal file
38
docs/examples/canonical/054-multiple-watchers.lucidia
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# 054: Multiple Watchers
|
||||||
|
# Several reactions to the same state
|
||||||
|
|
||||||
|
state user_count = 0
|
||||||
|
|
||||||
|
# Watcher 1: Update display
|
||||||
|
watch user_count:
|
||||||
|
show "Users online: {user_count}"
|
||||||
|
|
||||||
|
# Watcher 2: Check capacity
|
||||||
|
watch user_count:
|
||||||
|
if user_count > 100:
|
||||||
|
show "⚠️ Server at capacity!"
|
||||||
|
|
||||||
|
# Watcher 3: Log changes
|
||||||
|
watch user_count:
|
||||||
|
log("User count changed to: {user_count}")
|
||||||
|
|
||||||
|
# All three watchers run when state changes
|
||||||
|
button "Add User" -> add_user()
|
||||||
|
button "Remove User" -> remove_user()
|
||||||
|
|
||||||
|
add_user():
|
||||||
|
user_count = user_count + 1
|
||||||
|
|
||||||
|
remove_user():
|
||||||
|
if user_count > 0:
|
||||||
|
user_count = user_count - 1
|
||||||
|
|
||||||
|
# Watch multiple states
|
||||||
|
state email = ""
|
||||||
|
state password = ""
|
||||||
|
|
||||||
|
watch email, password:
|
||||||
|
if email != "" and password != "":
|
||||||
|
show "Form is complete"
|
||||||
|
else:
|
||||||
|
show "Please fill in all fields"
|
||||||
41
docs/examples/canonical/055-reactive-forms.lucidia
Normal file
41
docs/examples/canonical/055-reactive-forms.lucidia
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# 055: Reactive Forms
|
||||||
|
# Forms that respond to user input in real-time
|
||||||
|
|
||||||
|
state email = ""
|
||||||
|
state password = ""
|
||||||
|
state password_confirm = ""
|
||||||
|
|
||||||
|
# Reactive validation
|
||||||
|
computed email_valid = email.length > 0 and email.contains("@")
|
||||||
|
computed password_valid = password.length >= 8
|
||||||
|
computed passwords_match = password == password_confirm
|
||||||
|
|
||||||
|
computed form_valid = email_valid and password_valid and passwords_match
|
||||||
|
|
||||||
|
form signup:
|
||||||
|
input email -> email
|
||||||
|
placeholder: "your@email.com"
|
||||||
|
|
||||||
|
# Show validation feedback in real-time
|
||||||
|
if email != "" and not email_valid:
|
||||||
|
show_error "Invalid email format"
|
||||||
|
|
||||||
|
input password -> password
|
||||||
|
type: "password"
|
||||||
|
|
||||||
|
if password != "" and not password_valid:
|
||||||
|
show_error "Password must be at least 8 characters"
|
||||||
|
|
||||||
|
input password_confirm -> password_confirm
|
||||||
|
type: "password"
|
||||||
|
placeholder: "Confirm password"
|
||||||
|
|
||||||
|
if password_confirm != "" and not passwords_match:
|
||||||
|
show_error "Passwords don't match"
|
||||||
|
|
||||||
|
# Button disabled until form is valid
|
||||||
|
button "Sign Up" -> submit()
|
||||||
|
disabled: not form_valid
|
||||||
|
|
||||||
|
submit():
|
||||||
|
show "Account created for {email}"
|
||||||
48
docs/examples/canonical/056-two-way-binding.lucidia
Normal file
48
docs/examples/canonical/056-two-way-binding.lucidia
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# 056: Two-Way Binding
|
||||||
|
# State and UI stay in sync automatically
|
||||||
|
|
||||||
|
state message = "Hello, World!"
|
||||||
|
|
||||||
|
# Input field binds to state
|
||||||
|
form editor:
|
||||||
|
input message -> message
|
||||||
|
|
||||||
|
# Display automatically updates as you type
|
||||||
|
show "Preview: {message}"
|
||||||
|
show "Length: {message.length} characters"
|
||||||
|
|
||||||
|
# Computed values update too
|
||||||
|
computed word_count = message.split(" ").length
|
||||||
|
show "Words: {word_count}"
|
||||||
|
|
||||||
|
# Slider example
|
||||||
|
state volume = 50
|
||||||
|
|
||||||
|
form volume_control:
|
||||||
|
slider volume -> volume
|
||||||
|
min: 0
|
||||||
|
max: 100
|
||||||
|
step: 1
|
||||||
|
|
||||||
|
show "Volume: {volume}%"
|
||||||
|
|
||||||
|
# Visual indicator
|
||||||
|
if volume > 75:
|
||||||
|
show "🔊 Loud"
|
||||||
|
else if volume > 25:
|
||||||
|
show "🔉 Medium"
|
||||||
|
else:
|
||||||
|
show "🔈 Quiet"
|
||||||
|
|
||||||
|
# Color picker
|
||||||
|
state color = "#3366ff"
|
||||||
|
|
||||||
|
form color_picker:
|
||||||
|
input color -> color
|
||||||
|
type: "color"
|
||||||
|
|
||||||
|
# Preview box updates in real-time
|
||||||
|
show_box:
|
||||||
|
background: color
|
||||||
|
width: 100
|
||||||
|
height: 100
|
||||||
30
docs/examples/canonical/057-event-emitters.lucidia
Normal file
30
docs/examples/canonical/057-event-emitters.lucidia
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# 057: Event Emitters
|
||||||
|
# Send custom events
|
||||||
|
|
||||||
|
# Define events
|
||||||
|
on_user_login(user):
|
||||||
|
emit event "user.logged_in" with { user }
|
||||||
|
|
||||||
|
on_purchase(item, price):
|
||||||
|
emit event "purchase.completed" with { item, price }
|
||||||
|
|
||||||
|
on_error(message):
|
||||||
|
emit event "app.error" with { message }
|
||||||
|
|
||||||
|
# Trigger events
|
||||||
|
button "Login" -> login()
|
||||||
|
button "Buy Item" -> buy()
|
||||||
|
button "Cause Error" -> cause_error()
|
||||||
|
|
||||||
|
login():
|
||||||
|
user = { name: "Alex", id: 123 }
|
||||||
|
on_user_login(user)
|
||||||
|
|
||||||
|
buy():
|
||||||
|
on_purchase("Coffee Mug", 12.99)
|
||||||
|
|
||||||
|
cause_error():
|
||||||
|
on_error("Something went wrong!")
|
||||||
|
|
||||||
|
# Events can be caught by listeners (see next example)
|
||||||
|
# This allows decoupled communication between parts of your app
|
||||||
39
docs/examples/canonical/058-event-listeners.lucidia
Normal file
39
docs/examples/canonical/058-event-listeners.lucidia
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# 058: Event Listeners
|
||||||
|
# Respond to custom events
|
||||||
|
|
||||||
|
# Listen for events
|
||||||
|
on event "user.logged_in":
|
||||||
|
show "Welcome back, {event.data.user.name}!"
|
||||||
|
load_user_dashboard(event.data.user)
|
||||||
|
|
||||||
|
on event "purchase.completed":
|
||||||
|
show "Purchase: {event.data.item} for ${event.data.price}"
|
||||||
|
update_inventory(event.data.item)
|
||||||
|
|
||||||
|
on event "app.error":
|
||||||
|
show "Error: {event.data.message}"
|
||||||
|
log_error(event.data.message)
|
||||||
|
|
||||||
|
# Multiple listeners for same event
|
||||||
|
on event "user.logged_in":
|
||||||
|
track_analytics("user_login")
|
||||||
|
|
||||||
|
on event "user.logged_in":
|
||||||
|
sync_user_data()
|
||||||
|
|
||||||
|
# Listeners are independent - one failing doesn't affect others
|
||||||
|
|
||||||
|
load_user_dashboard(user):
|
||||||
|
show "Loading dashboard for user {user.id}"
|
||||||
|
|
||||||
|
update_inventory(item):
|
||||||
|
show "Updating inventory after purchase of {item}"
|
||||||
|
|
||||||
|
log_error(msg):
|
||||||
|
show "Logged: {msg}"
|
||||||
|
|
||||||
|
track_analytics(action):
|
||||||
|
show "Analytics: {action}"
|
||||||
|
|
||||||
|
sync_user_data():
|
||||||
|
show "Syncing user data"
|
||||||
38
docs/examples/canonical/059-custom-events.lucidia
Normal file
38
docs/examples/canonical/059-custom-events.lucidia
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# 059: Custom Events
|
||||||
|
# Create your own event system
|
||||||
|
|
||||||
|
# Shopping cart events
|
||||||
|
state cart = []
|
||||||
|
|
||||||
|
add_to_cart(item):
|
||||||
|
cart.append(item)
|
||||||
|
emit event "cart.item_added" with { item, cart_size: cart.length }
|
||||||
|
|
||||||
|
remove_from_cart(item_id):
|
||||||
|
cart = cart.filter((item) => item.id != item_id)
|
||||||
|
emit event "cart.item_removed" with { item_id, cart_size: cart.length }
|
||||||
|
|
||||||
|
clear_cart():
|
||||||
|
cart = []
|
||||||
|
emit event "cart.cleared"
|
||||||
|
|
||||||
|
# Cart listeners
|
||||||
|
on event "cart.item_added":
|
||||||
|
show "Added: {event.data.item.name}"
|
||||||
|
show "Cart size: {event.data.cart_size}"
|
||||||
|
|
||||||
|
on event "cart.item_removed":
|
||||||
|
show "Removed item {event.data.item_id}"
|
||||||
|
show "Cart size: {event.data.cart_size}"
|
||||||
|
|
||||||
|
on event "cart.cleared":
|
||||||
|
show "Cart is now empty"
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
button "Add Apple" -> add_to_cart({ id: 1, name: "Apple", price: 1.50 })
|
||||||
|
button "Add Banana" -> add_to_cart({ id: 2, name: "Banana", price: 0.75 })
|
||||||
|
button "Remove Apple" -> remove_from_cart(1)
|
||||||
|
button "Clear Cart" -> clear_cart()
|
||||||
|
|
||||||
|
# Events decouple components - cart logic doesn't need to know
|
||||||
|
# about UI updates, analytics, etc. They just listen for events.
|
||||||
54
docs/examples/canonical/060-event-bus.lucidia
Normal file
54
docs/examples/canonical/060-event-bus.lucidia
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# 060: Event Bus
|
||||||
|
# Central event coordination
|
||||||
|
|
||||||
|
# Create event bus for app-wide communication
|
||||||
|
bus = event_bus()
|
||||||
|
|
||||||
|
# Module 1: User authentication
|
||||||
|
module auth:
|
||||||
|
login(email, password):
|
||||||
|
# Authenticate user
|
||||||
|
user = { email, name: "Alex", id: 123 }
|
||||||
|
bus.emit("auth.login", user)
|
||||||
|
|
||||||
|
logout():
|
||||||
|
bus.emit("auth.logout")
|
||||||
|
|
||||||
|
# Module 2: Analytics
|
||||||
|
module analytics:
|
||||||
|
bus.on("auth.login", (user) => {
|
||||||
|
track("user_login", { user_id: user.id })
|
||||||
|
})
|
||||||
|
|
||||||
|
bus.on("auth.logout", () => {
|
||||||
|
track("user_logout")
|
||||||
|
})
|
||||||
|
|
||||||
|
track(event, data):
|
||||||
|
show "📊 Analytics: {event} {data}"
|
||||||
|
|
||||||
|
# Module 3: UI updates
|
||||||
|
module ui:
|
||||||
|
bus.on("auth.login", (user) => {
|
||||||
|
show "Welcome, {user.name}!"
|
||||||
|
show_dashboard()
|
||||||
|
})
|
||||||
|
|
||||||
|
bus.on("auth.logout", () => {
|
||||||
|
show "Goodbye!"
|
||||||
|
show_login_screen()
|
||||||
|
})
|
||||||
|
|
||||||
|
show_dashboard():
|
||||||
|
show "Dashboard loaded"
|
||||||
|
|
||||||
|
show_login_screen():
|
||||||
|
show "Login screen loaded"
|
||||||
|
|
||||||
|
# Modules don't know about each other
|
||||||
|
# They only know about events
|
||||||
|
# This makes the app easy to extend and maintain
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
button "Login" -> auth.login("alex@example.com", "password")
|
||||||
|
button "Logout" -> auth.logout()
|
||||||
27
docs/examples/canonical/061-asking-permission.lucidia
Normal file
27
docs/examples/canonical/061-asking-permission.lucidia
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# 061: Asking Permission
|
||||||
|
# Consent-first data access
|
||||||
|
|
||||||
|
# Basic permission request
|
||||||
|
ask permission for: location
|
||||||
|
|
||||||
|
# Show modal to user:
|
||||||
|
# "This app wants to access your location"
|
||||||
|
# [Allow] [Deny]
|
||||||
|
|
||||||
|
# Permission with context
|
||||||
|
ask permission for: camera
|
||||||
|
purpose: "Take profile photo"
|
||||||
|
duration: "one-time"
|
||||||
|
|
||||||
|
# Multiple resources
|
||||||
|
ask permission for: [microphone, camera]
|
||||||
|
purpose: "Video call"
|
||||||
|
|
||||||
|
# Custom resource
|
||||||
|
ask permission for: user.contacts
|
||||||
|
purpose: "Invite friends"
|
||||||
|
why: "We'll only read names and emails, never store them"
|
||||||
|
|
||||||
|
# The permission dialog is AUTOMATIC
|
||||||
|
# Users see clear, honest requests
|
||||||
|
# No sneaky background access
|
||||||
25
docs/examples/canonical/062-permission-granted.lucidia
Normal file
25
docs/examples/canonical/062-permission-granted.lucidia
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# 062: Permission Granted Flow
|
||||||
|
# Execute code only if permission is granted
|
||||||
|
|
||||||
|
ask permission for: location
|
||||||
|
|
||||||
|
if granted:
|
||||||
|
# This code only runs if user clicks "Allow"
|
||||||
|
current_location = get_current_location()
|
||||||
|
show "You are at: {current_location.latitude}, {current_location.longitude}"
|
||||||
|
|
||||||
|
# Find nearby restaurants
|
||||||
|
restaurants = find_nearby("restaurants", current_location)
|
||||||
|
show "Found {restaurants.length} nearby restaurants"
|
||||||
|
|
||||||
|
# If permission denied, this block doesn't execute
|
||||||
|
# App continues safely without location data
|
||||||
|
|
||||||
|
# Complex example with multiple permissions
|
||||||
|
ask permission for: [camera, storage]
|
||||||
|
purpose: "Save photos"
|
||||||
|
|
||||||
|
if granted:
|
||||||
|
photo = take_photo()
|
||||||
|
save_to_storage(photo)
|
||||||
|
show "Photo saved!"
|
||||||
34
docs/examples/canonical/063-permission-denied.lucidia
Normal file
34
docs/examples/canonical/063-permission-denied.lucidia
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# 063: Permission Denied Flow
|
||||||
|
# Handle gracefully when user says no
|
||||||
|
|
||||||
|
ask permission for: contacts
|
||||||
|
purpose: "Find friends using the app"
|
||||||
|
|
||||||
|
if granted:
|
||||||
|
contacts = read_contacts()
|
||||||
|
friends = find_matching_users(contacts)
|
||||||
|
show "Found {friends.length} friends!"
|
||||||
|
else:
|
||||||
|
# User denied permission
|
||||||
|
show "You can invite friends manually using their email"
|
||||||
|
show_manual_invite_form()
|
||||||
|
|
||||||
|
# Another example
|
||||||
|
ask permission for: notifications
|
||||||
|
|
||||||
|
if granted:
|
||||||
|
enable_push_notifications()
|
||||||
|
show "You'll receive updates!"
|
||||||
|
else:
|
||||||
|
show "You can still use the app, but won't get notifications"
|
||||||
|
# Degrade gracefully - app still works
|
||||||
|
|
||||||
|
show_manual_invite_form():
|
||||||
|
form invite:
|
||||||
|
input email -> friend_email
|
||||||
|
button "Send Invite" -> send_invite(friend_email)
|
||||||
|
|
||||||
|
send_invite(email):
|
||||||
|
show "Invitation sent to {email}"
|
||||||
|
|
||||||
|
# The key: denied permission = graceful degradation, not broken app
|
||||||
42
docs/examples/canonical/064-multiple-permissions.lucidia
Normal file
42
docs/examples/canonical/064-multiple-permissions.lucidia
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# 064: Multiple Permissions
|
||||||
|
# Handle complex permission scenarios
|
||||||
|
|
||||||
|
# Sequential permissions
|
||||||
|
start_video_call():
|
||||||
|
# Ask for camera first
|
||||||
|
ask permission for: camera
|
||||||
|
purpose: "Video call"
|
||||||
|
|
||||||
|
if granted:
|
||||||
|
# Camera allowed, now ask for microphone
|
||||||
|
ask permission for: microphone
|
||||||
|
purpose: "Voice during video call"
|
||||||
|
|
||||||
|
if granted:
|
||||||
|
# Both granted - start call
|
||||||
|
initiate_call()
|
||||||
|
show "Call started with video and audio"
|
||||||
|
else:
|
||||||
|
# Camera yes, microphone no
|
||||||
|
initiate_call(video_only: true)
|
||||||
|
show "Call started with video only"
|
||||||
|
else:
|
||||||
|
# Camera denied
|
||||||
|
show "Camera is required for video calls"
|
||||||
|
|
||||||
|
# Batch permissions
|
||||||
|
start_photo_app():
|
||||||
|
ask permission for: [camera, storage, location]
|
||||||
|
purpose: "Take and save geotagged photos"
|
||||||
|
|
||||||
|
# Check individual grants
|
||||||
|
if granted.camera and granted.storage:
|
||||||
|
if granted.location:
|
||||||
|
show "Full features enabled"
|
||||||
|
else:
|
||||||
|
show "Photos will be saved without location data"
|
||||||
|
else:
|
||||||
|
show "Camera and storage access required"
|
||||||
|
|
||||||
|
button "Start Video Call" -> start_video_call()
|
||||||
|
button "Open Photo App" -> start_photo_app()
|
||||||
38
docs/examples/canonical/065-consent-recording.lucidia
Normal file
38
docs/examples/canonical/065-consent-recording.lucidia
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# 065: Consent Recording
|
||||||
|
# Audit trail of data access
|
||||||
|
|
||||||
|
ask permission for: user.health_data
|
||||||
|
purpose: "Calculate fitness stats"
|
||||||
|
|
||||||
|
if granted:
|
||||||
|
# Record this access in audit log
|
||||||
|
with consent.record:
|
||||||
|
health_data = read_health_data()
|
||||||
|
stats = calculate_fitness_stats(health_data)
|
||||||
|
show "Your fitness stats: {stats}"
|
||||||
|
|
||||||
|
# Consent log entry created:
|
||||||
|
# {
|
||||||
|
# timestamp: 2025-01-15T10:30:00Z,
|
||||||
|
# resource: "user.health_data",
|
||||||
|
# purpose: "Calculate fitness stats",
|
||||||
|
# action: "read",
|
||||||
|
# user_approved: true
|
||||||
|
# }
|
||||||
|
|
||||||
|
# API call with consent recording
|
||||||
|
ask permission for: api.financial_data
|
||||||
|
purpose: "Show account balance"
|
||||||
|
|
||||||
|
if granted:
|
||||||
|
with consent.record:
|
||||||
|
balance = fetch "/api/account/balance"
|
||||||
|
show "Balance: ${balance}"
|
||||||
|
|
||||||
|
# User can later review: "This app accessed my financial data on [date]"
|
||||||
|
|
||||||
|
# Consent records are:
|
||||||
|
# - Stored locally (encrypted)
|
||||||
|
# - Never sent to cloud without explicit user action
|
||||||
|
# - Viewable in app settings
|
||||||
|
# - Can be exported for GDPR compliance
|
||||||
41
docs/examples/canonical/066-revoking-consent.lucidia
Normal file
41
docs/examples/canonical/066-revoking-consent.lucidia
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# 066: Revoking Consent
|
||||||
|
# Users can take back permission anytime
|
||||||
|
|
||||||
|
# In app settings
|
||||||
|
form privacy_settings:
|
||||||
|
show "Your Permissions:"
|
||||||
|
|
||||||
|
for permission in consent.list_granted():
|
||||||
|
show "{permission.resource} - Granted on {permission.date}"
|
||||||
|
button "Revoke" -> revoke_permission(permission.resource)
|
||||||
|
|
||||||
|
revoke_permission(resource):
|
||||||
|
consent.revoke(resource)
|
||||||
|
show "Permission revoked for {resource}"
|
||||||
|
|
||||||
|
# Attempting to use revoked permission
|
||||||
|
use_location():
|
||||||
|
# Check if still granted
|
||||||
|
if consent.is_granted("location"):
|
||||||
|
location = get_current_location()
|
||||||
|
show "Location: {location}"
|
||||||
|
else:
|
||||||
|
# Permission was revoked
|
||||||
|
show "Location access has been revoked"
|
||||||
|
|
||||||
|
# Optionally re-request
|
||||||
|
ask permission for: location
|
||||||
|
purpose: "Show nearby places"
|
||||||
|
|
||||||
|
if granted:
|
||||||
|
use_location() # Try again
|
||||||
|
else:
|
||||||
|
show "Cannot show nearby places without location"
|
||||||
|
|
||||||
|
# Revoke all permissions
|
||||||
|
button "Clear All Permissions" -> clear_all()
|
||||||
|
|
||||||
|
clear_all():
|
||||||
|
consent.revoke_all()
|
||||||
|
show "All permissions cleared"
|
||||||
|
show "You'll be asked again when needed"
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# 067: Checking Permission Status
|
||||||
|
# Query current permission state
|
||||||
|
|
||||||
|
# Check before requesting
|
||||||
|
show_location_feature():
|
||||||
|
status = consent.check_status("location")
|
||||||
|
|
||||||
|
status is:
|
||||||
|
"granted": {
|
||||||
|
# Already have permission
|
||||||
|
show_location_map()
|
||||||
|
}
|
||||||
|
"denied": {
|
||||||
|
# User previously denied
|
||||||
|
show "You denied location access"
|
||||||
|
show "To enable, grant permission below:"
|
||||||
|
button "Enable Location" -> request_location()
|
||||||
|
}
|
||||||
|
"not_asked": {
|
||||||
|
# Never requested before
|
||||||
|
show "Enable location to see nearby places"
|
||||||
|
button "Enable" -> request_location()
|
||||||
|
}
|
||||||
|
|
||||||
|
request_location():
|
||||||
|
ask permission for: location
|
||||||
|
if granted:
|
||||||
|
show_location_map()
|
||||||
|
|
||||||
|
show_location_map():
|
||||||
|
show "📍 Map view enabled"
|
||||||
|
|
||||||
|
# Check multiple permissions
|
||||||
|
check_video_call_ready():
|
||||||
|
camera_status = consent.check_status("camera")
|
||||||
|
mic_status = consent.check_status("microphone")
|
||||||
|
|
||||||
|
if camera_status == "granted" and mic_status == "granted":
|
||||||
|
show "✅ Ready for video call"
|
||||||
|
button "Start Call" -> start_call()
|
||||||
|
else:
|
||||||
|
show "⚠️ Missing permissions:"
|
||||||
|
if camera_status != "granted":
|
||||||
|
show " - Camera"
|
||||||
|
if mic_status != "granted":
|
||||||
|
show " - Microphone"
|
||||||
|
|
||||||
|
button "Check Video Status" -> check_video_call_ready()
|
||||||
50
docs/examples/canonical/068-scoped-permissions.lucidia
Normal file
50
docs/examples/canonical/068-scoped-permissions.lucidia
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# 068: Scoped Permissions
|
||||||
|
# Fine-grained control over access duration
|
||||||
|
|
||||||
|
# One-time permission
|
||||||
|
ask permission for: camera
|
||||||
|
scope: "one-time"
|
||||||
|
purpose: "Take profile photo"
|
||||||
|
|
||||||
|
if granted:
|
||||||
|
photo = take_photo()
|
||||||
|
upload_profile_photo(photo)
|
||||||
|
# Permission automatically expires after this block
|
||||||
|
|
||||||
|
# Session-based permission
|
||||||
|
ask permission for: location
|
||||||
|
scope: "session"
|
||||||
|
purpose: "Show nearby restaurants"
|
||||||
|
|
||||||
|
if granted:
|
||||||
|
# Permission lasts until app closes
|
||||||
|
update_location_periodically()
|
||||||
|
|
||||||
|
# Persistent permission
|
||||||
|
ask permission for: notifications
|
||||||
|
scope: "persistent"
|
||||||
|
purpose: "Daily reminders"
|
||||||
|
|
||||||
|
if granted:
|
||||||
|
# Permission persists across sessions
|
||||||
|
# User must manually revoke
|
||||||
|
schedule_daily_notifications()
|
||||||
|
|
||||||
|
# Time-limited permission
|
||||||
|
ask permission for: api.user_data
|
||||||
|
scope: "time-limited"
|
||||||
|
duration: "24 hours"
|
||||||
|
purpose: "Generate weekly report"
|
||||||
|
|
||||||
|
if granted:
|
||||||
|
# Permission expires after 24 hours
|
||||||
|
generate_report()
|
||||||
|
|
||||||
|
# Resource-specific scoping
|
||||||
|
ask permission for: user.contacts
|
||||||
|
scope: "read-only" # Cannot modify
|
||||||
|
purpose: "Find friends"
|
||||||
|
|
||||||
|
if granted:
|
||||||
|
contacts = read_contacts()
|
||||||
|
# Cannot write to contacts - scope enforced by runtime
|
||||||
51
docs/examples/canonical/069-temporary-permissions.lucidia
Normal file
51
docs/examples/canonical/069-temporary-permissions.lucidia
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# 069: Temporary Permissions
|
||||||
|
# Permissions that auto-expire
|
||||||
|
|
||||||
|
# Permission expires after 1 hour
|
||||||
|
ask permission for: api.financial_data
|
||||||
|
expires_in: "1 hour"
|
||||||
|
purpose: "Show account balance"
|
||||||
|
|
||||||
|
if granted:
|
||||||
|
show_balance_dashboard()
|
||||||
|
|
||||||
|
# Permission is valid for 1 hour
|
||||||
|
# After that, must request again
|
||||||
|
|
||||||
|
# Permission expires after N uses
|
||||||
|
ask permission for: camera
|
||||||
|
expires_after_uses: 3
|
||||||
|
purpose: "Take up to 3 photos"
|
||||||
|
|
||||||
|
if granted:
|
||||||
|
for i in 1..3:
|
||||||
|
photo = take_photo()
|
||||||
|
save_photo(photo)
|
||||||
|
show "Photo {i} saved"
|
||||||
|
|
||||||
|
# Permission automatically revoked after 3 uses
|
||||||
|
|
||||||
|
# Until specific time
|
||||||
|
ask permission for: location
|
||||||
|
expires_at: "2025-01-15T23:59:59Z"
|
||||||
|
purpose: "Track run (today only)"
|
||||||
|
|
||||||
|
if granted:
|
||||||
|
track_running_session()
|
||||||
|
# At midnight, permission expires
|
||||||
|
|
||||||
|
# Conditional expiration
|
||||||
|
ask permission for: microphone
|
||||||
|
expires_when: call_ended
|
||||||
|
purpose: "Voice call"
|
||||||
|
|
||||||
|
if granted:
|
||||||
|
start_voice_call()
|
||||||
|
# When call_ended event fires, permission expires
|
||||||
|
|
||||||
|
# Check if permission is still valid
|
||||||
|
if consent.is_valid("camera"):
|
||||||
|
take_photo()
|
||||||
|
else:
|
||||||
|
show "Camera permission has expired"
|
||||||
|
# Request again if needed
|
||||||
60
docs/examples/canonical/070-consent-audit-log.lucidia
Normal file
60
docs/examples/canonical/070-consent-audit-log.lucidia
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# 070: Consent Audit Log
|
||||||
|
# Complete transparency of data access
|
||||||
|
|
||||||
|
# View all consent events
|
||||||
|
show_consent_history():
|
||||||
|
events = consent.get_audit_log()
|
||||||
|
|
||||||
|
show "Consent History ({events.length} events)"
|
||||||
|
|
||||||
|
for event in events:
|
||||||
|
show "━━━━━━━━━━━━━━━━"
|
||||||
|
show "Resource: {event.resource}"
|
||||||
|
show "Action: {event.action}"
|
||||||
|
show "Time: {event.timestamp}"
|
||||||
|
show "Purpose: {event.purpose}"
|
||||||
|
show "Granted: {event.granted}"
|
||||||
|
|
||||||
|
if event.access_count:
|
||||||
|
show "Times accessed: {event.access_count}"
|
||||||
|
|
||||||
|
# Filter audit log
|
||||||
|
show_last_24_hours():
|
||||||
|
recent = consent.get_audit_log()
|
||||||
|
.filter((e) => e.timestamp > now() - 24h)
|
||||||
|
|
||||||
|
show "Recent activity: {recent.length} events"
|
||||||
|
|
||||||
|
# Export for GDPR compliance
|
||||||
|
export_consent_data():
|
||||||
|
data = consent.export_all()
|
||||||
|
|
||||||
|
# Returns JSON with:
|
||||||
|
# - All permission grants
|
||||||
|
# - All access events
|
||||||
|
# - Timestamps
|
||||||
|
# - Purposes
|
||||||
|
# - User IP addresses (optional)
|
||||||
|
|
||||||
|
save_file("consent_data.json", data)
|
||||||
|
show "Consent data exported"
|
||||||
|
|
||||||
|
# Consent analytics (privacy-preserving)
|
||||||
|
show_consent_stats():
|
||||||
|
stats = consent.get_stats()
|
||||||
|
|
||||||
|
show "Most frequently accessed: {stats.most_accessed}"
|
||||||
|
show "Recently revoked: {stats.recently_revoked}"
|
||||||
|
show "Never used permissions: {stats.unused}"
|
||||||
|
|
||||||
|
# Settings UI
|
||||||
|
form consent_settings:
|
||||||
|
show "Your Privacy Dashboard"
|
||||||
|
|
||||||
|
button "View History" -> show_consent_history()
|
||||||
|
button "Export Data" -> export_consent_data()
|
||||||
|
button "Show Stats" -> show_consent_stats()
|
||||||
|
button "Revoke All" -> consent.revoke_all()
|
||||||
|
|
||||||
|
# This level of transparency builds trust
|
||||||
|
# Users see exactly what data is accessed, when, and why
|
||||||
32
docs/examples/canonical/071-store-data-locally.lucidia
Normal file
32
docs/examples/canonical/071-store-data-locally.lucidia
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# 071: Store Data Locally
|
||||||
|
# Save data to the device
|
||||||
|
|
||||||
|
# Simple storage
|
||||||
|
preferences = {
|
||||||
|
theme: "dark",
|
||||||
|
language: "en",
|
||||||
|
notifications: true
|
||||||
|
}
|
||||||
|
|
||||||
|
store preferences locally as "user_prefs"
|
||||||
|
show "Preferences saved"
|
||||||
|
|
||||||
|
# Store multiple items
|
||||||
|
user_data = { name: "Alex", email: "alex@example.com" }
|
||||||
|
app_settings = { sidebar_collapsed: false, font_size: 14 }
|
||||||
|
|
||||||
|
store user_data locally as "user"
|
||||||
|
store app_settings locally as "settings"
|
||||||
|
|
||||||
|
# Store lists
|
||||||
|
todos = [
|
||||||
|
{ id: 1, text: "Buy groceries", done: false },
|
||||||
|
{ id: 2, text: "Write code", done: true },
|
||||||
|
{ id: 3, text: "Exercise", done: false }
|
||||||
|
]
|
||||||
|
|
||||||
|
store todos locally as "todos"
|
||||||
|
show "Saved {todos.length} todos"
|
||||||
|
|
||||||
|
# Data persists across sessions
|
||||||
|
# Stored in IndexedDB (browser) or local filesystem (native)
|
||||||
43
docs/examples/canonical/072-load-data-locally.lucidia
Normal file
43
docs/examples/canonical/072-load-data-locally.lucidia
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# 072: Load Data Locally
|
||||||
|
# Retrieve saved data
|
||||||
|
|
||||||
|
# Load with default value
|
||||||
|
preferences = load "user_prefs" locally or {
|
||||||
|
theme: "light",
|
||||||
|
language: "en",
|
||||||
|
notifications: true
|
||||||
|
}
|
||||||
|
|
||||||
|
show "Theme: {preferences.theme}"
|
||||||
|
|
||||||
|
# Load and check if exists
|
||||||
|
todos = load "todos" locally
|
||||||
|
|
||||||
|
if todos != null:
|
||||||
|
show "Found {todos.length} saved todos"
|
||||||
|
for todo in todos:
|
||||||
|
show "- {todo.text}"
|
||||||
|
else:
|
||||||
|
show "No saved todos"
|
||||||
|
todos = []
|
||||||
|
|
||||||
|
# Load multiple items
|
||||||
|
user = load "user" locally
|
||||||
|
settings = load "settings" locally
|
||||||
|
|
||||||
|
if user != null:
|
||||||
|
show "Welcome back, {user.name}!"
|
||||||
|
|
||||||
|
# Common pattern: load or initialize
|
||||||
|
get_user_data():
|
||||||
|
data = load "user_data" locally
|
||||||
|
if data == null:
|
||||||
|
# First time - create default
|
||||||
|
data = { visits: 0, last_login: null }
|
||||||
|
data.visits = data.visits + 1
|
||||||
|
data.last_login = now()
|
||||||
|
store data locally as "user_data"
|
||||||
|
return data
|
||||||
|
|
||||||
|
user_data = get_user_data()
|
||||||
|
show "Visit #{user_data.visits}"
|
||||||
44
docs/examples/canonical/073-update-local-data.lucidia
Normal file
44
docs/examples/canonical/073-update-local-data.lucidia
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# 073: Update Local Data
|
||||||
|
# Modify stored data
|
||||||
|
|
||||||
|
# Load existing data
|
||||||
|
todos = load "todos" locally or []
|
||||||
|
|
||||||
|
# Add new todo
|
||||||
|
add_todo(text):
|
||||||
|
new_todo = {
|
||||||
|
id: generate_id(),
|
||||||
|
text: text,
|
||||||
|
done: false,
|
||||||
|
created_at: now()
|
||||||
|
}
|
||||||
|
|
||||||
|
todos.append(new_todo)
|
||||||
|
store todos locally as "todos"
|
||||||
|
show "Todo added"
|
||||||
|
|
||||||
|
# Mark as done
|
||||||
|
complete_todo(id):
|
||||||
|
todos = todos.map((todo) => {
|
||||||
|
if todo.id == id:
|
||||||
|
{ ...todo, done: true }
|
||||||
|
else:
|
||||||
|
todo
|
||||||
|
})
|
||||||
|
|
||||||
|
store todos locally as "todos"
|
||||||
|
show "Todo completed"
|
||||||
|
|
||||||
|
# Delete todo
|
||||||
|
delete_todo(id):
|
||||||
|
todos = todos.filter((todo) => todo.id != id)
|
||||||
|
store todos locally as "todos"
|
||||||
|
show "Todo deleted"
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
button "Add Todo" -> add_todo("New task")
|
||||||
|
button "Complete #1" -> complete_todo(1)
|
||||||
|
button "Delete #2" -> delete_todo(2)
|
||||||
|
|
||||||
|
generate_id():
|
||||||
|
return Math.random() * 1000000
|
||||||
45
docs/examples/canonical/074-delete-local-data.lucidia
Normal file
45
docs/examples/canonical/074-delete-local-data.lucidia
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# 074: Delete Local Data
|
||||||
|
# Remove stored data
|
||||||
|
|
||||||
|
# Delete specific item
|
||||||
|
delete_user_preferences():
|
||||||
|
delete "user_prefs" locally
|
||||||
|
show "Preferences deleted"
|
||||||
|
|
||||||
|
# Delete with confirmation
|
||||||
|
clear_all_todos():
|
||||||
|
ask "Are you sure? This cannot be undone." -> confirm
|
||||||
|
|
||||||
|
if confirm == "yes":
|
||||||
|
delete "todos" locally
|
||||||
|
show "All todos deleted"
|
||||||
|
else:
|
||||||
|
show "Cancelled"
|
||||||
|
|
||||||
|
# Delete multiple items
|
||||||
|
reset_app():
|
||||||
|
delete "user" locally
|
||||||
|
delete "settings" locally
|
||||||
|
delete "cache" locally
|
||||||
|
show "App reset to defaults"
|
||||||
|
|
||||||
|
# Delete all local data
|
||||||
|
clear_all_storage():
|
||||||
|
storage.clear_all()
|
||||||
|
show "All local data deleted"
|
||||||
|
|
||||||
|
# Conditional deletion
|
||||||
|
cleanup_old_data():
|
||||||
|
cached_data = load "cache" locally
|
||||||
|
|
||||||
|
if cached_data != null:
|
||||||
|
age = now() - cached_data.timestamp
|
||||||
|
|
||||||
|
if age > 7 * 24 * 60 * 60: # 7 days
|
||||||
|
delete "cache" locally
|
||||||
|
show "Old cache deleted"
|
||||||
|
|
||||||
|
button "Delete Preferences" -> delete_user_preferences()
|
||||||
|
button "Clear Todos" -> clear_all_todos()
|
||||||
|
button "Reset App" -> reset_app()
|
||||||
|
button "Cleanup" -> cleanup_old_data()
|
||||||
49
docs/examples/canonical/075-ephemeral-storage.lucidia
Normal file
49
docs/examples/canonical/075-ephemeral-storage.lucidia
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# 075: Ephemeral Storage
|
||||||
|
# Temporary data that doesn't persist
|
||||||
|
|
||||||
|
# Store in memory only (lost on refresh)
|
||||||
|
store session_token ephemeral
|
||||||
|
store temporary_files ephemeral
|
||||||
|
|
||||||
|
# Session-specific data
|
||||||
|
current_session = {
|
||||||
|
id: generate_session_id(),
|
||||||
|
started_at: now(),
|
||||||
|
active_tab: "home"
|
||||||
|
}
|
||||||
|
|
||||||
|
store current_session ephemeral as "session"
|
||||||
|
|
||||||
|
# Form draft (auto-save while editing)
|
||||||
|
state draft_message = ""
|
||||||
|
|
||||||
|
watch draft_message:
|
||||||
|
# Save draft in memory
|
||||||
|
store draft_message ephemeral as "message_draft"
|
||||||
|
|
||||||
|
# Load draft on page reload
|
||||||
|
draft = load "message_draft" ephemeral or ""
|
||||||
|
|
||||||
|
# Ephemeral vs local comparison
|
||||||
|
save_user_data(data, remember_me):
|
||||||
|
if remember_me:
|
||||||
|
# Persist across sessions
|
||||||
|
store data locally as "user"
|
||||||
|
else:
|
||||||
|
# Only for this session
|
||||||
|
store data ephemeral as "user"
|
||||||
|
|
||||||
|
# Shopping cart example
|
||||||
|
state cart = []
|
||||||
|
|
||||||
|
add_to_cart(item):
|
||||||
|
cart.append(item)
|
||||||
|
# Don't persist - cart resets on page refresh
|
||||||
|
store cart ephemeral as "cart"
|
||||||
|
|
||||||
|
# Upload progress (doesn't need to persist)
|
||||||
|
track_upload(file):
|
||||||
|
progress = { file: file.name, percent: 0 }
|
||||||
|
store progress ephemeral as "upload_progress"
|
||||||
|
|
||||||
|
# Update as upload progresses...
|
||||||
47
docs/examples/canonical/076-cloud-sync-setup.lucidia
Normal file
47
docs/examples/canonical/076-cloud-sync-setup.lucidia
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# 076: Cloud Sync Setup
|
||||||
|
# Optional cloud backup (local-first, cloud-optional)
|
||||||
|
|
||||||
|
# User configures sync
|
||||||
|
form sync_settings:
|
||||||
|
checkbox "Enable cloud sync" -> settings.cloud_sync
|
||||||
|
|
||||||
|
if settings.cloud_sync:
|
||||||
|
input cloud_endpoint -> settings.endpoint
|
||||||
|
placeholder: "https://sync.example.com"
|
||||||
|
|
||||||
|
select sync_frequency -> settings.frequency
|
||||||
|
options: ["on_change", "hourly", "daily", "manual"]
|
||||||
|
|
||||||
|
checkbox "Encrypt before upload" -> settings.encrypt
|
||||||
|
default: true
|
||||||
|
|
||||||
|
button "Save Settings" -> save_sync_settings(settings)
|
||||||
|
|
||||||
|
save_sync_settings(config):
|
||||||
|
store config locally as "sync_config"
|
||||||
|
|
||||||
|
if config.cloud_sync:
|
||||||
|
initialize_sync(config)
|
||||||
|
show "Cloud sync enabled"
|
||||||
|
else:
|
||||||
|
disable_sync()
|
||||||
|
show "Using local storage only"
|
||||||
|
|
||||||
|
# Sync specific data
|
||||||
|
sync_notes():
|
||||||
|
# Ask for permission
|
||||||
|
ask permission for: cloud.storage
|
||||||
|
purpose: "Backup notes to cloud"
|
||||||
|
|
||||||
|
if granted:
|
||||||
|
notes = load "notes" locally
|
||||||
|
|
||||||
|
sync notes to cloud with:
|
||||||
|
endpoint: settings.endpoint
|
||||||
|
encrypt: true
|
||||||
|
on_conflict: "last_write_wins"
|
||||||
|
|
||||||
|
show "Notes synced to cloud"
|
||||||
|
|
||||||
|
# Everything still works offline
|
||||||
|
# Cloud is purely optional backup/sync across devices
|
||||||
58
docs/examples/canonical/077-conditional-sync.lucidia
Normal file
58
docs/examples/canonical/077-conditional-sync.lucidia
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
# 077: Conditional Sync
|
||||||
|
# Sync only when conditions are met
|
||||||
|
|
||||||
|
# Sync only on WiFi
|
||||||
|
save_document(doc):
|
||||||
|
store doc locally as "documents/{doc.id}"
|
||||||
|
|
||||||
|
# Only sync to cloud if on WiFi
|
||||||
|
if network.type == "wifi" and user.settings.cloud_sync:
|
||||||
|
sync doc to cloud
|
||||||
|
|
||||||
|
# Sync only if changed
|
||||||
|
update_preferences(prefs):
|
||||||
|
old_prefs = load "preferences" locally
|
||||||
|
|
||||||
|
store prefs locally as "preferences"
|
||||||
|
|
||||||
|
# Only sync if actually different
|
||||||
|
if prefs != old_prefs:
|
||||||
|
sync prefs to cloud
|
||||||
|
|
||||||
|
# Sync based on data size
|
||||||
|
save_photo(photo):
|
||||||
|
store photo locally as "photos/{photo.id}"
|
||||||
|
|
||||||
|
# Large files only sync on WiFi
|
||||||
|
if photo.size > 10MB:
|
||||||
|
if network.type == "wifi":
|
||||||
|
sync photo to cloud
|
||||||
|
else:
|
||||||
|
# Small files sync on any connection
|
||||||
|
sync photo to cloud
|
||||||
|
|
||||||
|
# Sync during idle time
|
||||||
|
queue_sync(data):
|
||||||
|
store data locally as "sync_queue/{data.id}"
|
||||||
|
|
||||||
|
# Wait for idle period
|
||||||
|
on idle:
|
||||||
|
process_sync_queue()
|
||||||
|
|
||||||
|
process_sync_queue():
|
||||||
|
queue = load "sync_queue" locally
|
||||||
|
|
||||||
|
for item in queue:
|
||||||
|
if network.online:
|
||||||
|
sync item to cloud
|
||||||
|
delete "sync_queue/{item.id}" locally
|
||||||
|
|
||||||
|
# User-controlled sync
|
||||||
|
button "Sync Now" -> manual_sync()
|
||||||
|
button "Sync All" -> sync_all_data()
|
||||||
|
|
||||||
|
manual_sync():
|
||||||
|
if network.online:
|
||||||
|
sync_all_data()
|
||||||
|
else:
|
||||||
|
show "No internet connection"
|
||||||
59
docs/examples/canonical/078-conflict-resolution.lucidia
Normal file
59
docs/examples/canonical/078-conflict-resolution.lucidia
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# 078: Conflict Resolution
|
||||||
|
# Handle sync conflicts when local and cloud differ
|
||||||
|
|
||||||
|
# Simple strategy: last write wins
|
||||||
|
sync_notes():
|
||||||
|
notes = load "notes" locally
|
||||||
|
|
||||||
|
sync notes to cloud with:
|
||||||
|
on_conflict: "last_write_wins"
|
||||||
|
|
||||||
|
show "Notes synced (last write wins)"
|
||||||
|
|
||||||
|
# Keep both versions
|
||||||
|
sync_documents():
|
||||||
|
docs = load "documents" locally
|
||||||
|
|
||||||
|
sync docs to cloud with:
|
||||||
|
on_conflict: "keep_both"
|
||||||
|
|
||||||
|
# Creates: doc_local.txt and doc_cloud.txt
|
||||||
|
show "Sync complete (both versions saved)"
|
||||||
|
|
||||||
|
# Manual conflict resolution
|
||||||
|
sync_with_manual_resolution():
|
||||||
|
local_data = load "user_data" locally
|
||||||
|
cloud_data = fetch_from_cloud("user_data")
|
||||||
|
|
||||||
|
if local_data != cloud_data:
|
||||||
|
# Show conflict to user
|
||||||
|
show "Conflict detected!"
|
||||||
|
show "Local version: {local_data}"
|
||||||
|
show "Cloud version: {cloud_data}"
|
||||||
|
|
||||||
|
ask "Which version to keep?" -> choice
|
||||||
|
options: ["local", "cloud", "merge"]
|
||||||
|
|
||||||
|
choice is:
|
||||||
|
"local": {
|
||||||
|
sync local_data to cloud
|
||||||
|
show "Kept local version"
|
||||||
|
}
|
||||||
|
"cloud": {
|
||||||
|
store cloud_data locally as "user_data"
|
||||||
|
show "Kept cloud version"
|
||||||
|
}
|
||||||
|
"merge": {
|
||||||
|
merged = merge_data(local_data, cloud_data)
|
||||||
|
store merged locally as "user_data"
|
||||||
|
sync merged to cloud
|
||||||
|
show "Merged versions"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Smart merge
|
||||||
|
merge_data(local, cloud):
|
||||||
|
return {
|
||||||
|
# Keep newer values for each field
|
||||||
|
name: local.updated_at > cloud.updated_at ? local.name : cloud.name,
|
||||||
|
email: local.updated_at > cloud.updated_at ? local.email : cloud.email
|
||||||
|
}
|
||||||
68
docs/examples/canonical/079-offline-first-pattern.lucidia
Normal file
68
docs/examples/canonical/079-offline-first-pattern.lucidia
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# 079: Offline-First Pattern
|
||||||
|
# App works without internet, syncs when available
|
||||||
|
|
||||||
|
# Always write to local first
|
||||||
|
save_note(note):
|
||||||
|
# Save locally immediately
|
||||||
|
notes = load "notes" locally or []
|
||||||
|
note.id = generate_id()
|
||||||
|
note.synced = false
|
||||||
|
notes.append(note)
|
||||||
|
store notes locally as "notes"
|
||||||
|
|
||||||
|
show "Note saved"
|
||||||
|
|
||||||
|
# Try to sync in background
|
||||||
|
if network.online:
|
||||||
|
sync_in_background(note)
|
||||||
|
else:
|
||||||
|
show "(Will sync when online)"
|
||||||
|
|
||||||
|
# Background sync
|
||||||
|
sync_in_background(note):
|
||||||
|
try:
|
||||||
|
sync note to cloud
|
||||||
|
note.synced = true
|
||||||
|
update_note(note)
|
||||||
|
catch:
|
||||||
|
# Sync failed, will retry later
|
||||||
|
queue_for_retry(note)
|
||||||
|
|
||||||
|
# Load data (local always available)
|
||||||
|
load_notes():
|
||||||
|
notes = load "notes" locally or []
|
||||||
|
|
||||||
|
# Try to fetch updates from cloud
|
||||||
|
if network.online:
|
||||||
|
cloud_notes = fetch_from_cloud("notes")
|
||||||
|
merged = merge_notes(notes, cloud_notes)
|
||||||
|
store merged locally as "notes"
|
||||||
|
return merged
|
||||||
|
else:
|
||||||
|
# Offline - return local data
|
||||||
|
return notes
|
||||||
|
|
||||||
|
# Indicate sync status
|
||||||
|
display_notes():
|
||||||
|
notes = load_notes()
|
||||||
|
|
||||||
|
for note in notes:
|
||||||
|
show note.text
|
||||||
|
if note.synced:
|
||||||
|
show "✓ Synced"
|
||||||
|
else:
|
||||||
|
show "⋯ Pending sync"
|
||||||
|
|
||||||
|
# Retry pending syncs when back online
|
||||||
|
on network.online:
|
||||||
|
pending = load "sync_queue" locally or []
|
||||||
|
|
||||||
|
for item in pending:
|
||||||
|
sync item to cloud
|
||||||
|
mark_as_synced(item)
|
||||||
|
|
||||||
|
delete "sync_queue" locally
|
||||||
|
show "Synced {pending.length} pending items"
|
||||||
|
|
||||||
|
# The app ALWAYS works locally
|
||||||
|
# Cloud is enhancement, not requirement
|
||||||
62
docs/examples/canonical/080-storage-encryption.lucidia
Normal file
62
docs/examples/canonical/080-storage-encryption.lucidia
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
# 080: Storage Encryption
|
||||||
|
# Encrypt sensitive data before storing
|
||||||
|
|
||||||
|
# Encrypt by default
|
||||||
|
sensitive_data = {
|
||||||
|
password: "secret123",
|
||||||
|
api_key: "sk_live_xxxx",
|
||||||
|
ssn: "123-45-6789"
|
||||||
|
}
|
||||||
|
|
||||||
|
store sensitive_data locally as "credentials"
|
||||||
|
encrypt: true
|
||||||
|
# Uses device key or user-provided password
|
||||||
|
|
||||||
|
# Encrypted storage is automatic for sensitive field names
|
||||||
|
# (password, ssn, credit_card, api_key, etc.)
|
||||||
|
|
||||||
|
# User-provided encryption key
|
||||||
|
save_encrypted_diary(entry):
|
||||||
|
ask "Enter encryption password:" -> password
|
||||||
|
type: "password"
|
||||||
|
|
||||||
|
store entry locally as "diary/{entry.id}"
|
||||||
|
encrypt: true
|
||||||
|
key: password
|
||||||
|
|
||||||
|
show "Diary entry saved (encrypted)"
|
||||||
|
|
||||||
|
# Load encrypted data
|
||||||
|
load_encrypted_diary(entry_id):
|
||||||
|
ask "Enter encryption password:" -> password
|
||||||
|
type: "password"
|
||||||
|
|
||||||
|
entry = load "diary/{entry_id}" locally
|
||||||
|
decrypt: true
|
||||||
|
key: password
|
||||||
|
|
||||||
|
if entry == null:
|
||||||
|
show "Wrong password or entry doesn't exist"
|
||||||
|
else:
|
||||||
|
show entry.text
|
||||||
|
|
||||||
|
# Automatic encryption for regulated data
|
||||||
|
store_medical_record(record):
|
||||||
|
# Medical data automatically encrypted (HIPAA compliance)
|
||||||
|
store record locally as "medical_records/{record.id}"
|
||||||
|
# encryption: true is implicit for medical data
|
||||||
|
|
||||||
|
# Sync encrypted
|
||||||
|
sync_encrypted_data():
|
||||||
|
# Data synced to cloud remains encrypted
|
||||||
|
# Cloud never sees unencrypted data
|
||||||
|
encrypted_notes = load "private_notes" locally
|
||||||
|
encrypt: true
|
||||||
|
|
||||||
|
sync encrypted_notes to cloud
|
||||||
|
# Still encrypted in transit and at rest
|
||||||
|
|
||||||
|
show "Private notes synced (encrypted)"
|
||||||
|
|
||||||
|
# Everything sensitive is encrypted by default
|
||||||
|
# Lucidia assumes privacy, not surveillance
|
||||||
36
docs/examples/canonical/081-intent-comments.lucidia
Normal file
36
docs/examples/canonical/081-intent-comments.lucidia
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# 081: Intent Comments
|
||||||
|
# Tell the AI what you want, not how to do it
|
||||||
|
|
||||||
|
### Intent: Parse this CSV data and extract email addresses
|
||||||
|
data = """
|
||||||
|
name,email,age
|
||||||
|
Alex,alex@example.com,28
|
||||||
|
Jordan,jordan@example.com,25
|
||||||
|
Casey,casey@example.com,30
|
||||||
|
"""
|
||||||
|
|
||||||
|
# AI expands the intent into working code:
|
||||||
|
# emails = data.split("\n").slice(1).map(line => {
|
||||||
|
# parts = line.split(",")
|
||||||
|
# return parts[1]
|
||||||
|
# }).filter(email => email != "")
|
||||||
|
|
||||||
|
### Intent: Calculate the total price including tax
|
||||||
|
cart_items = [
|
||||||
|
{ name: "Book", price: 19.99 },
|
||||||
|
{ name: "Pen", price: 2.50 },
|
||||||
|
{ name: "Notebook", price: 5.99 }
|
||||||
|
]
|
||||||
|
tax_rate = 0.08
|
||||||
|
|
||||||
|
# AI generates:
|
||||||
|
# subtotal = cart_items.reduce((sum, item) => sum + item.price, 0)
|
||||||
|
# total = subtotal * (1 + tax_rate)
|
||||||
|
|
||||||
|
### Intent: Format this date as "January 15, 2025"
|
||||||
|
timestamp = 1705334400000 # Unix timestamp
|
||||||
|
|
||||||
|
# AI knows how to format dates in natural language
|
||||||
|
|
||||||
|
# The ### syntax tells the AI:
|
||||||
|
# "This is an intent, not just a comment - turn it into code"
|
||||||
42
docs/examples/canonical/082-ai-code-generation.lucidia
Normal file
42
docs/examples/canonical/082-ai-code-generation.lucidia
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# 082: AI Code Generation
|
||||||
|
# Let AI write implementations
|
||||||
|
|
||||||
|
# Explicit AI call
|
||||||
|
result = ai.generate:
|
||||||
|
input: sales_data
|
||||||
|
output: "summary report with charts"
|
||||||
|
constraints: [
|
||||||
|
"Group by region",
|
||||||
|
"Show top 5 products",
|
||||||
|
"Calculate growth percentage"
|
||||||
|
]
|
||||||
|
|
||||||
|
show result
|
||||||
|
|
||||||
|
# AI with type hints
|
||||||
|
generate_user_report(user_id):
|
||||||
|
# AI infers: fetch data, format nicely, return string
|
||||||
|
ai.generate:
|
||||||
|
task: "Create user activity report"
|
||||||
|
input: user_id
|
||||||
|
output: "markdown formatted report"
|
||||||
|
|
||||||
|
# Complex data transformation
|
||||||
|
messy_data = [
|
||||||
|
{ "firstName": "Alex", "last_name": "Chen", "AGE": "28" },
|
||||||
|
{ "firstName": "Jordan", "LastName": "Lee", "age": 25 }
|
||||||
|
]
|
||||||
|
|
||||||
|
### Intent: Normalize this data to consistent format
|
||||||
|
### Expected output: [{ first_name, last_name, age }]
|
||||||
|
|
||||||
|
normalized = ai.normalize(messy_data, {
|
||||||
|
format: "snake_case",
|
||||||
|
required_fields: ["first_name", "last_name", "age"]
|
||||||
|
})
|
||||||
|
|
||||||
|
show normalized
|
||||||
|
|
||||||
|
# AI shows proposed code, you approve
|
||||||
|
# Once approved, it runs
|
||||||
|
# Future runs use the cached implementation
|
||||||
41
docs/examples/canonical/083-ai-text-summary.lucidia
Normal file
41
docs/examples/canonical/083-ai-text-summary.lucidia
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# 083: AI Text Summary
|
||||||
|
# Automatic summarization
|
||||||
|
|
||||||
|
# Long text
|
||||||
|
article = """
|
||||||
|
Lucidia is a revolutionary programming language designed for the modern era.
|
||||||
|
Unlike traditional languages that were built for compilers, Lucidia is built
|
||||||
|
for intelligences - both human and artificial. It features consent as syntax,
|
||||||
|
local-first storage, and seamless AI collaboration. The language runs natively
|
||||||
|
in browsers via WebAssembly, requiring zero setup. Users can write intent
|
||||||
|
instead of implementation, letting AI handle the ceremony while maintaining
|
||||||
|
human creative control.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Summarize
|
||||||
|
summary = ai.summarize(article, max_words: 20)
|
||||||
|
show summary
|
||||||
|
# Output: "Lucidia is a modern programming language with AI collaboration,
|
||||||
|
# consent-as-syntax, and browser-native execution."
|
||||||
|
|
||||||
|
# Bullet point summary
|
||||||
|
logs = load "server_logs" locally
|
||||||
|
|
||||||
|
### Intent: Summarize last 24 hours of logs as bullet points
|
||||||
|
### Group by severity, show counts
|
||||||
|
|
||||||
|
bullet_summary = ai.summarize(logs, format: "bullets")
|
||||||
|
show bullet_summary
|
||||||
|
|
||||||
|
# Expected:
|
||||||
|
# • Errors: 3 (database timeout, API failure, memory leak)
|
||||||
|
# • Warnings: 47 (slow queries, deprecated API usage)
|
||||||
|
# • Info: 1,284 (normal operations)
|
||||||
|
|
||||||
|
# Different summary styles
|
||||||
|
ai.summarize(data, style: "executive") # High-level overview
|
||||||
|
ai.summarize(data, style: "technical") # Detailed technical
|
||||||
|
ai.summarize(data, style: "eli5") # Explain like I'm 5
|
||||||
|
|
||||||
|
# AI picks the important parts
|
||||||
|
# You don't have to
|
||||||
52
docs/examples/canonical/084-ai-classification.lucidia
Normal file
52
docs/examples/canonical/084-ai-classification.lucidia
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# 084: AI Classification
|
||||||
|
# Categorize data automatically
|
||||||
|
|
||||||
|
# Classify text
|
||||||
|
email_body = "Congratulations! You've won $1,000,000! Click here to claim..."
|
||||||
|
|
||||||
|
classification = ai.classify(email_body, {
|
||||||
|
categories: ["spam", "legitimate", "promotional"],
|
||||||
|
confidence: true
|
||||||
|
})
|
||||||
|
|
||||||
|
show "Category: {classification.category}"
|
||||||
|
show "Confidence: {classification.confidence}%"
|
||||||
|
|
||||||
|
if classification.category == "spam":
|
||||||
|
move_to_spam(email_body)
|
||||||
|
|
||||||
|
# Classify support tickets
|
||||||
|
ticket = {
|
||||||
|
subject: "Cannot login to my account",
|
||||||
|
body: "I keep getting 'invalid password' even though I'm sure it's correct"
|
||||||
|
}
|
||||||
|
|
||||||
|
category = ai.classify(ticket, {
|
||||||
|
categories: ["bug", "feature_request", "support", "billing"],
|
||||||
|
output: "category_only"
|
||||||
|
})
|
||||||
|
|
||||||
|
category is:
|
||||||
|
"bug": assign_to_engineering(ticket)
|
||||||
|
"support": assign_to_support(ticket)
|
||||||
|
"billing": assign_to_billing(ticket)
|
||||||
|
"feature_request": add_to_roadmap(ticket)
|
||||||
|
|
||||||
|
# Classify images (if available)
|
||||||
|
photo = load_image("photo.jpg")
|
||||||
|
|
||||||
|
tags = ai.classify(photo, {
|
||||||
|
categories: ["nature", "city", "people", "food", "animals"],
|
||||||
|
multiple: true # Can have multiple tags
|
||||||
|
})
|
||||||
|
|
||||||
|
show "Tags: {tags}" # ["nature", "animals"]
|
||||||
|
|
||||||
|
# Sentiment analysis
|
||||||
|
review = "This product is absolutely terrible. Waste of money!"
|
||||||
|
|
||||||
|
sentiment = ai.classify(review, {
|
||||||
|
categories: ["positive", "negative", "neutral"]
|
||||||
|
})
|
||||||
|
|
||||||
|
show "Sentiment: {sentiment}" # negative
|
||||||
55
docs/examples/canonical/085-ai-data-transformation.lucidia
Normal file
55
docs/examples/canonical/085-ai-data-transformation.lucidia
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# 085: AI Data Transformation
|
||||||
|
# Transform data between formats
|
||||||
|
|
||||||
|
# Convert formats
|
||||||
|
json_data = """
|
||||||
|
{
|
||||||
|
"users": [
|
||||||
|
{"id": 1, "name": "Alex"},
|
||||||
|
{"id": 2, "name": "Jordan"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
### Intent: Convert this JSON to CSV format
|
||||||
|
csv_data = ai.transform(json_data, {
|
||||||
|
from: "json",
|
||||||
|
to: "csv"
|
||||||
|
})
|
||||||
|
|
||||||
|
show csv_data
|
||||||
|
# Output:
|
||||||
|
# id,name
|
||||||
|
# 1,Alex
|
||||||
|
# 2,Jordan
|
||||||
|
|
||||||
|
# Restructure data
|
||||||
|
flat_data = [
|
||||||
|
{ user_id: 1, order_id: 101, amount: 50 },
|
||||||
|
{ user_id: 1, order_id: 102, amount: 75 },
|
||||||
|
{ user_id: 2, order_id: 103, amount: 30 }
|
||||||
|
]
|
||||||
|
|
||||||
|
### Intent: Group orders by user_id
|
||||||
|
nested_data = ai.transform(flat_data, {
|
||||||
|
operation: "group_by",
|
||||||
|
key: "user_id",
|
||||||
|
aggregate: "orders"
|
||||||
|
})
|
||||||
|
|
||||||
|
show nested_data
|
||||||
|
# Output:
|
||||||
|
# [
|
||||||
|
# { user_id: 1, orders: [{ order_id: 101, amount: 50 }, { order_id: 102, amount: 75 }] },
|
||||||
|
# { user_id: 2, orders: [{ order_id: 103, amount: 30 }] }
|
||||||
|
# ]
|
||||||
|
|
||||||
|
# Extract specific fields
|
||||||
|
complex_response = fetch "/api/users/detailed"
|
||||||
|
|
||||||
|
### Intent: Extract only name, email, and signup_date
|
||||||
|
simplified = ai.extract(complex_response, {
|
||||||
|
fields: ["name", "email", "signup_date"]
|
||||||
|
})
|
||||||
|
|
||||||
|
# AI handles nested structures, missing fields, different naming conventions
|
||||||
50
docs/examples/canonical/086-ai-with-constraints.lucidia
Normal file
50
docs/examples/canonical/086-ai-with-constraints.lucidia
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# 086: AI With Constraints
|
||||||
|
# Guide AI behavior with rules
|
||||||
|
|
||||||
|
# Generate code with constraints
|
||||||
|
user_input = "Show me all users"
|
||||||
|
|
||||||
|
query = ai.generate_sql:
|
||||||
|
intent: user_input
|
||||||
|
constraints: [
|
||||||
|
"Only SELECT queries allowed (no DELETE/UPDATE/DROP)",
|
||||||
|
"Must include LIMIT clause",
|
||||||
|
"No access to sensitive tables (passwords, tokens)"
|
||||||
|
]
|
||||||
|
|
||||||
|
show query # SELECT * FROM users LIMIT 100
|
||||||
|
|
||||||
|
# Generate UI with branding constraints
|
||||||
|
ai.generate_component:
|
||||||
|
type: "login_form"
|
||||||
|
constraints: [
|
||||||
|
"Use brand colors: #3366ff, #ffffff",
|
||||||
|
"No third-party auth (email/password only)",
|
||||||
|
"Include password strength indicator",
|
||||||
|
"Accessible (ARIA labels, keyboard navigation)"
|
||||||
|
]
|
||||||
|
|
||||||
|
# Content generation with constraints
|
||||||
|
ai.generate_text:
|
||||||
|
prompt: "Write product description"
|
||||||
|
constraints: [
|
||||||
|
"Max 100 words",
|
||||||
|
"Professional tone",
|
||||||
|
"No superlatives (amazing, incredible, etc.)",
|
||||||
|
"Include SEO keywords: sustainable, eco-friendly",
|
||||||
|
"End with call-to-action"
|
||||||
|
]
|
||||||
|
|
||||||
|
# Data transformation with validation
|
||||||
|
ai.transform(messy_data, {
|
||||||
|
to: "normalized",
|
||||||
|
constraints: [
|
||||||
|
"All email addresses must be valid",
|
||||||
|
"Ages must be between 0 and 120",
|
||||||
|
"Phone numbers in E.164 format",
|
||||||
|
"Reject records missing required fields"
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
# Constraints ensure AI stays within bounds
|
||||||
|
# Prevents security issues, maintains brand consistency
|
||||||
58
docs/examples/canonical/087-ai-code-review.lucidia
Normal file
58
docs/examples/canonical/087-ai-code-review.lucidia
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
# 087: AI Code Review
|
||||||
|
# AI suggests improvements
|
||||||
|
|
||||||
|
# Your code
|
||||||
|
calculate_total(items):
|
||||||
|
total = 0
|
||||||
|
for item in items:
|
||||||
|
total = total + item.price
|
||||||
|
return total
|
||||||
|
|
||||||
|
# Ask AI for review
|
||||||
|
review = ai.review_code(calculate_total)
|
||||||
|
|
||||||
|
show review.suggestions
|
||||||
|
# Suggestions:
|
||||||
|
# 1. Consider using reduce() for more idiomatic functional style
|
||||||
|
# 2. Add null check for items parameter
|
||||||
|
# 3. Add type hint for clarity: calculate_total(items: list) -> number
|
||||||
|
|
||||||
|
show review.improved_version
|
||||||
|
# Improved code:
|
||||||
|
# calculate_total(items: list) -> number:
|
||||||
|
# if items == null: return 0
|
||||||
|
# return items.reduce((sum, item) => sum + item.price, 0)
|
||||||
|
|
||||||
|
# Security review
|
||||||
|
handle_user_input(input):
|
||||||
|
query = "SELECT * FROM users WHERE name = '{input}'"
|
||||||
|
# SQL injection vulnerability!
|
||||||
|
|
||||||
|
security_review = ai.review_security(handle_user_input)
|
||||||
|
|
||||||
|
show security_review.issues
|
||||||
|
# Issues:
|
||||||
|
# ⚠️ Critical: SQL injection vulnerability
|
||||||
|
# - Never concatenate user input into SQL queries
|
||||||
|
# - Use parameterized queries instead
|
||||||
|
|
||||||
|
show security_review.fix
|
||||||
|
# Corrected code:
|
||||||
|
# handle_user_input(input):
|
||||||
|
# query = db.query("SELECT * FROM users WHERE name = ?", [input])
|
||||||
|
|
||||||
|
# Performance review
|
||||||
|
process_large_list(data):
|
||||||
|
results = []
|
||||||
|
for item in data:
|
||||||
|
if item.active:
|
||||||
|
results.append(transform(item))
|
||||||
|
return results
|
||||||
|
|
||||||
|
perf_review = ai.review_performance(process_large_list)
|
||||||
|
|
||||||
|
show perf_review.optimizations
|
||||||
|
# Optimizations:
|
||||||
|
# 1. Use filter and map instead of manual loop (10x faster)
|
||||||
|
# 2. Consider lazy evaluation for very large lists
|
||||||
|
# 3. Cache transform() results if same item processed multiple times
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
# 088: AI Optimization Suggestions
|
||||||
|
# AI finds inefficiencies
|
||||||
|
|
||||||
|
# Slow code
|
||||||
|
find_duplicates(list1, list2):
|
||||||
|
duplicates = []
|
||||||
|
for item1 in list1:
|
||||||
|
for item2 in list2:
|
||||||
|
if item1 == item2:
|
||||||
|
duplicates.append(item1)
|
||||||
|
return duplicates
|
||||||
|
|
||||||
|
# Ask for optimization
|
||||||
|
optimization = ai.optimize(find_duplicates)
|
||||||
|
|
||||||
|
show optimization.issues
|
||||||
|
# "Nested loop is O(n²). For large lists, this is slow."
|
||||||
|
|
||||||
|
show optimization.improved_version
|
||||||
|
# Improved (O(n)):
|
||||||
|
# find_duplicates(list1, list2):
|
||||||
|
# set2 = set(list2)
|
||||||
|
# return list1.filter(item => set2.contains(item))
|
||||||
|
|
||||||
|
show optimization.performance_gain
|
||||||
|
# "Estimated: 100x faster for 1000-item lists"
|
||||||
|
|
||||||
|
# Database query optimization
|
||||||
|
get_user_posts(user_id):
|
||||||
|
user = db.query("SELECT * FROM users WHERE id = ?", [user_id])
|
||||||
|
posts = db.query("SELECT * FROM posts WHERE author_id = ?", [user_id])
|
||||||
|
comments = db.query("SELECT * FROM comments WHERE author_id = ?", [user_id])
|
||||||
|
return { user, posts, comments }
|
||||||
|
|
||||||
|
# N+1 query problem
|
||||||
|
suggestions = ai.optimize_queries(get_user_posts)
|
||||||
|
|
||||||
|
show suggestions
|
||||||
|
# "Combine into single JOIN query to avoid multiple round-trips"
|
||||||
|
# Suggested:
|
||||||
|
# db.query("""
|
||||||
|
# SELECT users.*, posts.*, comments.*
|
||||||
|
# FROM users
|
||||||
|
# LEFT JOIN posts ON posts.author_id = users.id
|
||||||
|
# LEFT JOIN comments ON comments.author_id = users.id
|
||||||
|
# WHERE users.id = ?
|
||||||
|
# """, [user_id])
|
||||||
|
|
||||||
|
# Memory optimization
|
||||||
|
load_all_users():
|
||||||
|
return db.query("SELECT * FROM users") # Could be millions of rows!
|
||||||
|
|
||||||
|
memory_suggestions = ai.optimize_memory(load_all_users)
|
||||||
|
|
||||||
|
show memory_suggestions
|
||||||
|
# "Loading all rows into memory can crash the app"
|
||||||
|
# "Use pagination or streaming instead"
|
||||||
80
docs/examples/canonical/089-ai-error-explanations.lucidia
Normal file
80
docs/examples/canonical/089-ai-error-explanations.lucidia
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
# 089: AI Error Explanations
|
||||||
|
# AI explains what went wrong and how to fix it
|
||||||
|
|
||||||
|
# Runtime error
|
||||||
|
users = null
|
||||||
|
|
||||||
|
# This will error:
|
||||||
|
# first_user = users[0]
|
||||||
|
|
||||||
|
# Error message with AI explanation:
|
||||||
|
"""
|
||||||
|
Error: Cannot access property of null
|
||||||
|
|
||||||
|
AI Explanation:
|
||||||
|
The variable 'users' is null, so you can't access users[0].
|
||||||
|
This usually happens when:
|
||||||
|
1. Data hasn't loaded yet
|
||||||
|
2. API request failed
|
||||||
|
3. Database returned no results
|
||||||
|
|
||||||
|
Suggested fix:
|
||||||
|
if users != null and users.length > 0:
|
||||||
|
first_user = users[0]
|
||||||
|
else:
|
||||||
|
show "No users found"
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Type error
|
||||||
|
age = "28" # String
|
||||||
|
adult = age >= 18 # Comparing string to number
|
||||||
|
|
||||||
|
# AI explains:
|
||||||
|
"""
|
||||||
|
Error: Type mismatch (string vs number)
|
||||||
|
|
||||||
|
AI Explanation:
|
||||||
|
You're comparing age (string "28") with 18 (number).
|
||||||
|
JavaScript allows this but results in weird behavior.
|
||||||
|
|
||||||
|
Suggested fixes:
|
||||||
|
1. Convert age to number: Number(age) >= 18
|
||||||
|
2. Or compare as strings: age >= "18"
|
||||||
|
3. Better: Store age as number from the start
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Logic error (AI detected)
|
||||||
|
calculate_discount(price, percent):
|
||||||
|
discount = price * percent # Wrong! percent should be divided by 100
|
||||||
|
return price - discount
|
||||||
|
|
||||||
|
ai_warning = ai.analyze(calculate_discount)
|
||||||
|
|
||||||
|
show ai_warning
|
||||||
|
"""
|
||||||
|
⚠️ Logic Warning:
|
||||||
|
If percent is 20 (meaning 20%), you're calculating price * 20,
|
||||||
|
which would give a negative result after subtraction.
|
||||||
|
|
||||||
|
Did you mean: discount = price * (percent / 100)?
|
||||||
|
"""
|
||||||
|
|
||||||
|
# API error with AI context
|
||||||
|
try:
|
||||||
|
data = fetch "/api/users/999999"
|
||||||
|
catch error:
|
||||||
|
show ai.explain_error(error)
|
||||||
|
|
||||||
|
# Output:
|
||||||
|
"""
|
||||||
|
HTTP 404: Not Found
|
||||||
|
|
||||||
|
AI Explanation:
|
||||||
|
The user with ID 999999 doesn't exist in the database.
|
||||||
|
This is normal - just handle it gracefully:
|
||||||
|
|
||||||
|
user = fetch "/api/users/{id}"
|
||||||
|
if user == null:
|
||||||
|
show "User not found"
|
||||||
|
redirect_to "/users"
|
||||||
|
"""
|
||||||
68
docs/examples/canonical/090-ai-learning-user-style.lucidia
Normal file
68
docs/examples/canonical/090-ai-learning-user-style.lucidia
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# 090: AI Learning User Style
|
||||||
|
# AI adapts to your coding preferences
|
||||||
|
|
||||||
|
# After writing code for a while, AI learns your style
|
||||||
|
|
||||||
|
# You prefer functional programming:
|
||||||
|
numbers = [1, 2, 3, 4, 5]
|
||||||
|
doubled = numbers.map(x => x * 2)
|
||||||
|
evens = numbers.filter(x => x % 2 == 0)
|
||||||
|
total = numbers.reduce((sum, x) => sum + x, 0)
|
||||||
|
|
||||||
|
# AI notices and suggests functional style for new code:
|
||||||
|
### Intent: Filter active users and get their emails
|
||||||
|
|
||||||
|
# AI suggests (matching your style):
|
||||||
|
# active_emails = users
|
||||||
|
# .filter(user => user.active)
|
||||||
|
# .map(user => user.email)
|
||||||
|
|
||||||
|
# Instead of imperative:
|
||||||
|
# active_emails = []
|
||||||
|
# for user in users:
|
||||||
|
# if user.active:
|
||||||
|
# active_emails.append(user.email)
|
||||||
|
|
||||||
|
# You prefer verbose variable names:
|
||||||
|
user_email_address = "alex@example.com"
|
||||||
|
is_email_verified = false
|
||||||
|
total_purchase_amount = 0
|
||||||
|
|
||||||
|
# AI learns and suggests:
|
||||||
|
### Intent: Store user's phone number
|
||||||
|
# Suggested variable name: user_phone_number (not just "phone")
|
||||||
|
|
||||||
|
# You always add error handling:
|
||||||
|
fetch_user_data(id):
|
||||||
|
try:
|
||||||
|
return fetch "/api/users/{id}"
|
||||||
|
catch error:
|
||||||
|
show "Error loading user"
|
||||||
|
return null
|
||||||
|
|
||||||
|
# AI learns pattern and auto-adds error handling to suggestions:
|
||||||
|
### Intent: Fetch product details
|
||||||
|
|
||||||
|
# AI suggests (with error handling, matching your style):
|
||||||
|
# fetch_product(id):
|
||||||
|
# try:
|
||||||
|
# return fetch "/api/products/{id}"
|
||||||
|
# catch error:
|
||||||
|
# show "Error loading product"
|
||||||
|
# return null
|
||||||
|
|
||||||
|
# Style preferences stored locally
|
||||||
|
ai.get_learned_preferences()
|
||||||
|
# Returns:
|
||||||
|
# {
|
||||||
|
# style: "functional",
|
||||||
|
# naming: "verbose_snake_case",
|
||||||
|
# error_handling: "always_try_catch",
|
||||||
|
# comments: "minimal",
|
||||||
|
# max_line_length: 80
|
||||||
|
# }
|
||||||
|
|
||||||
|
# You can override per-project
|
||||||
|
ai.set_preference("style", "object_oriented") # For this project only
|
||||||
|
|
||||||
|
# AI becomes YOUR collaborator, not a generic one
|
||||||
77
docs/examples/canonical/091-todo-app.lucidia
Normal file
77
docs/examples/canonical/091-todo-app.lucidia
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# 091: Todo App
|
||||||
|
# Complete task management application
|
||||||
|
|
||||||
|
state todos = load "todos" locally or []
|
||||||
|
state filter = "all" # all, active, completed
|
||||||
|
|
||||||
|
# Add new todo
|
||||||
|
form add_todo:
|
||||||
|
input new_todo_text -> todo_text
|
||||||
|
placeholder: "What needs to be done?"
|
||||||
|
|
||||||
|
button "Add" -> add(todo_text)
|
||||||
|
|
||||||
|
add(text):
|
||||||
|
if text == "": return
|
||||||
|
|
||||||
|
new_todo = {
|
||||||
|
id: generate_id(),
|
||||||
|
text: text,
|
||||||
|
completed: false,
|
||||||
|
created_at: now()
|
||||||
|
}
|
||||||
|
|
||||||
|
todos.append(new_todo)
|
||||||
|
store todos locally as "todos"
|
||||||
|
todo_text = "" # Clear input
|
||||||
|
|
||||||
|
# Toggle completion
|
||||||
|
toggle(id):
|
||||||
|
todos = todos.map(todo => {
|
||||||
|
if todo.id == id:
|
||||||
|
{ ...todo, completed: not todo.completed }
|
||||||
|
else:
|
||||||
|
todo
|
||||||
|
})
|
||||||
|
store todos locally as "todos"
|
||||||
|
|
||||||
|
# Delete todo
|
||||||
|
delete_todo(id):
|
||||||
|
todos = todos.filter(todo => todo.id != id)
|
||||||
|
store todos locally as "todos"
|
||||||
|
|
||||||
|
# Filter todos
|
||||||
|
computed filtered_todos = todos.filter(todo => {
|
||||||
|
filter is:
|
||||||
|
"all": true
|
||||||
|
"active": not todo.completed
|
||||||
|
"completed": todo.completed
|
||||||
|
})
|
||||||
|
|
||||||
|
# Display todos
|
||||||
|
for todo in filtered_todos:
|
||||||
|
show_todo:
|
||||||
|
text: todo.text
|
||||||
|
completed: todo.completed
|
||||||
|
on_toggle: () => toggle(todo.id)
|
||||||
|
on_delete: () => delete_todo(todo.id)
|
||||||
|
|
||||||
|
# Filter buttons
|
||||||
|
show "Show: "
|
||||||
|
button "All ({todos.length})" -> filter = "all"
|
||||||
|
button "Active ({active_count})" -> filter = "active"
|
||||||
|
button "Completed ({completed_count})" -> filter = "completed"
|
||||||
|
|
||||||
|
computed active_count = todos.filter(t => not t.completed).length
|
||||||
|
computed completed_count = todos.filter(t => t.completed).length
|
||||||
|
|
||||||
|
# Clear completed
|
||||||
|
if completed_count > 0:
|
||||||
|
button "Clear Completed" -> clear_completed()
|
||||||
|
|
||||||
|
clear_completed():
|
||||||
|
todos = todos.filter(todo => not todo.completed)
|
||||||
|
store todos locally as "todos"
|
||||||
|
|
||||||
|
generate_id():
|
||||||
|
return now() + Math.random()
|
||||||
100
docs/examples/canonical/092-note-taking-app.lucidia
Normal file
100
docs/examples/canonical/092-note-taking-app.lucidia
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
# 092: Note Taking App
|
||||||
|
# Markdown notes with local storage and search
|
||||||
|
|
||||||
|
state notes = load "notes" locally or []
|
||||||
|
state current_note = null
|
||||||
|
state search_query = ""
|
||||||
|
|
||||||
|
# Create new note
|
||||||
|
create_note():
|
||||||
|
note = {
|
||||||
|
id: generate_id(),
|
||||||
|
title: "Untitled",
|
||||||
|
content: "",
|
||||||
|
created_at: now(),
|
||||||
|
updated_at: now(),
|
||||||
|
tags: []
|
||||||
|
}
|
||||||
|
|
||||||
|
notes.append(note)
|
||||||
|
current_note = note
|
||||||
|
store notes locally as "notes"
|
||||||
|
|
||||||
|
# Save note
|
||||||
|
save_note():
|
||||||
|
if current_note == null: return
|
||||||
|
|
||||||
|
current_note.updated_at = now()
|
||||||
|
|
||||||
|
notes = notes.map(note => {
|
||||||
|
if note.id == current_note.id:
|
||||||
|
current_note
|
||||||
|
else:
|
||||||
|
note
|
||||||
|
})
|
||||||
|
|
||||||
|
store notes locally as "notes"
|
||||||
|
show "Note saved"
|
||||||
|
|
||||||
|
# Delete note
|
||||||
|
delete_note(id):
|
||||||
|
notes = notes.filter(note => note.id != id)
|
||||||
|
current_note = null
|
||||||
|
store notes locally as "notes"
|
||||||
|
|
||||||
|
# Search notes
|
||||||
|
computed filtered_notes = notes.filter(note => {
|
||||||
|
if search_query == "": return true
|
||||||
|
|
||||||
|
query_lower = search_query.lowercase()
|
||||||
|
return note.title.lowercase().contains(query_lower) or
|
||||||
|
note.content.lowercase().contains(query_lower)
|
||||||
|
})
|
||||||
|
|
||||||
|
# UI Layout
|
||||||
|
show_layout:
|
||||||
|
# Sidebar
|
||||||
|
sidebar:
|
||||||
|
button "New Note" -> create_note()
|
||||||
|
|
||||||
|
input search_query -> search_query
|
||||||
|
placeholder: "Search notes..."
|
||||||
|
|
||||||
|
show "{filtered_notes.length} notes"
|
||||||
|
|
||||||
|
for note in filtered_notes:
|
||||||
|
note_list_item:
|
||||||
|
title: note.title
|
||||||
|
preview: note.content[0:50]
|
||||||
|
date: format_date(note.updated_at)
|
||||||
|
active: current_note?.id == note.id
|
||||||
|
on_click: () => current_note = note
|
||||||
|
|
||||||
|
# Editor
|
||||||
|
editor:
|
||||||
|
if current_note != null:
|
||||||
|
input current_note.title -> current_note.title
|
||||||
|
on_change: save_note()
|
||||||
|
|
||||||
|
textarea current_note.content -> current_note.content
|
||||||
|
on_change: save_note()
|
||||||
|
rows: 20
|
||||||
|
|
||||||
|
# Markdown preview
|
||||||
|
show_markdown(current_note.content)
|
||||||
|
|
||||||
|
# Tags
|
||||||
|
show "Tags:"
|
||||||
|
for tag in current_note.tags:
|
||||||
|
show_tag(tag)
|
||||||
|
|
||||||
|
button "Delete Note" -> delete_note(current_note.id)
|
||||||
|
else:
|
||||||
|
show "Select a note or create a new one"
|
||||||
|
|
||||||
|
format_date(timestamp):
|
||||||
|
# AI: Format as relative time
|
||||||
|
return "2 hours ago"
|
||||||
|
|
||||||
|
generate_id():
|
||||||
|
return now() + Math.random()
|
||||||
105
docs/examples/canonical/093-contact-manager.lucidia
Normal file
105
docs/examples/canonical/093-contact-manager.lucidia
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# 093: Contact Manager
|
||||||
|
# Store and organize contacts locally
|
||||||
|
|
||||||
|
state contacts = load "contacts" locally or []
|
||||||
|
state editing = null
|
||||||
|
|
||||||
|
# Add contact
|
||||||
|
form add_contact:
|
||||||
|
input firstname -> new_contact.firstname
|
||||||
|
input lastname -> new_contact.lastname
|
||||||
|
input email -> new_contact.email
|
||||||
|
validate: is_email
|
||||||
|
input phone -> new_contact.phone
|
||||||
|
input company -> new_contact.company
|
||||||
|
|
||||||
|
button "Save Contact" -> save_contact(new_contact)
|
||||||
|
|
||||||
|
save_contact(contact):
|
||||||
|
contact.id = generate_id()
|
||||||
|
contact.created_at = now()
|
||||||
|
contact.favorite = false
|
||||||
|
|
||||||
|
contacts.append(contact)
|
||||||
|
store contacts locally as "contacts"
|
||||||
|
|
||||||
|
# Clear form
|
||||||
|
new_contact = {}
|
||||||
|
show "Contact saved"
|
||||||
|
|
||||||
|
# Edit contact
|
||||||
|
edit_contact(id):
|
||||||
|
editing = contacts.find(c => c.id == id)
|
||||||
|
|
||||||
|
save_edit():
|
||||||
|
contacts = contacts.map(c => {
|
||||||
|
if c.id == editing.id:
|
||||||
|
editing
|
||||||
|
else:
|
||||||
|
c
|
||||||
|
})
|
||||||
|
|
||||||
|
store contacts locally as "contacts"
|
||||||
|
editing = null
|
||||||
|
show "Contact updated"
|
||||||
|
|
||||||
|
# Delete contact
|
||||||
|
delete_contact(id):
|
||||||
|
ask "Delete this contact?" -> confirm
|
||||||
|
|
||||||
|
if confirm == "yes":
|
||||||
|
contacts = contacts.filter(c => c.id != id)
|
||||||
|
store contacts locally as "contacts"
|
||||||
|
show "Contact deleted"
|
||||||
|
|
||||||
|
# Toggle favorite
|
||||||
|
toggle_favorite(id):
|
||||||
|
contacts = contacts.map(c => {
|
||||||
|
if c.id == id:
|
||||||
|
{ ...c, favorite: not c.favorite }
|
||||||
|
else:
|
||||||
|
c
|
||||||
|
})
|
||||||
|
|
||||||
|
store contacts locally as "contacts"
|
||||||
|
|
||||||
|
# Sort and filter
|
||||||
|
state sort_by = "lastname"
|
||||||
|
state show_favorites_only = false
|
||||||
|
|
||||||
|
computed sorted_contacts = contacts
|
||||||
|
.filter(c => not show_favorites_only or c.favorite)
|
||||||
|
.sort_by(c => c[sort_by])
|
||||||
|
|
||||||
|
# Export contacts
|
||||||
|
export_contacts():
|
||||||
|
csv = ai.transform(contacts, { to: "csv" })
|
||||||
|
save_file("contacts.csv", csv)
|
||||||
|
show "Contacts exported"
|
||||||
|
|
||||||
|
# Import from phone (with consent)
|
||||||
|
import_from_phone():
|
||||||
|
ask permission for: device.contacts
|
||||||
|
purpose: "Import your phone contacts"
|
||||||
|
|
||||||
|
if granted:
|
||||||
|
with consent.record:
|
||||||
|
phone_contacts = read_device_contacts()
|
||||||
|
contacts = contacts.concat(phone_contacts)
|
||||||
|
store contacts locally as "contacts"
|
||||||
|
show "Imported {phone_contacts.length} contacts"
|
||||||
|
|
||||||
|
# Display
|
||||||
|
for contact in sorted_contacts:
|
||||||
|
show_contact_card:
|
||||||
|
name: "{contact.firstname} {contact.lastname}"
|
||||||
|
email: contact.email
|
||||||
|
phone: contact.phone
|
||||||
|
company: contact.company
|
||||||
|
favorite: contact.favorite
|
||||||
|
on_favorite: () => toggle_favorite(contact.id)
|
||||||
|
on_edit: () => edit_contact(contact.id)
|
||||||
|
on_delete: () => delete_contact(contact.id)
|
||||||
|
|
||||||
|
generate_id():
|
||||||
|
return now() + Math.random()
|
||||||
112
docs/examples/canonical/094-expense-tracker.lucidia
Normal file
112
docs/examples/canonical/094-expense-tracker.lucidia
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
# 094: Expense Tracker
|
||||||
|
# Track spending with categories and insights
|
||||||
|
|
||||||
|
state expenses = load "expenses" locally or []
|
||||||
|
state budget = load "budget" locally or { monthly: 2000 }
|
||||||
|
|
||||||
|
# Add expense
|
||||||
|
form add_expense:
|
||||||
|
input amount -> new_expense.amount
|
||||||
|
type: "number"
|
||||||
|
placeholder: "0.00"
|
||||||
|
|
||||||
|
input description -> new_expense.description
|
||||||
|
placeholder: "What was this for?"
|
||||||
|
|
||||||
|
select category -> new_expense.category
|
||||||
|
options: ["Food", "Transport", "Entertainment", "Bills", "Shopping", "Other"]
|
||||||
|
|
||||||
|
date_picker date -> new_expense.date
|
||||||
|
default: today()
|
||||||
|
|
||||||
|
button "Add Expense" -> save_expense(new_expense)
|
||||||
|
|
||||||
|
save_expense(exp):
|
||||||
|
exp.id = generate_id()
|
||||||
|
exp.created_at = now()
|
||||||
|
|
||||||
|
expenses.append(exp)
|
||||||
|
store expenses locally as "expenses"
|
||||||
|
|
||||||
|
new_expense = {}
|
||||||
|
show "Expense added"
|
||||||
|
|
||||||
|
# Delete expense
|
||||||
|
delete_expense(id):
|
||||||
|
expenses = expenses.filter(e => e.id != id)
|
||||||
|
store expenses locally as "expenses"
|
||||||
|
|
||||||
|
# Analytics
|
||||||
|
computed this_month_expenses = expenses.filter(e => {
|
||||||
|
is_this_month(e.date)
|
||||||
|
})
|
||||||
|
|
||||||
|
computed total_this_month = this_month_expenses
|
||||||
|
.reduce((sum, e) => sum + e.amount, 0)
|
||||||
|
|
||||||
|
computed by_category = this_month_expenses
|
||||||
|
.group_by(e => e.category)
|
||||||
|
.map(group => ({
|
||||||
|
category: group.key,
|
||||||
|
total: group.items.reduce((sum, e) => sum + e.amount, 0),
|
||||||
|
count: group.items.length
|
||||||
|
}))
|
||||||
|
.sort_by(g => g.total)
|
||||||
|
.reverse()
|
||||||
|
|
||||||
|
computed budget_remaining = budget.monthly - total_this_month
|
||||||
|
|
||||||
|
computed is_over_budget = total_this_month > budget.monthly
|
||||||
|
|
||||||
|
# Display summary
|
||||||
|
show_card:
|
||||||
|
title: "This Month"
|
||||||
|
value: "${total_this_month.toFixed(2)}"
|
||||||
|
subtitle: "of ${budget.monthly} budget"
|
||||||
|
|
||||||
|
if is_over_budget:
|
||||||
|
show_warning "Over budget by ${(total_this_month - budget.monthly).toFixed(2)}"
|
||||||
|
else:
|
||||||
|
show_success "${budget_remaining.toFixed(2)} remaining"
|
||||||
|
|
||||||
|
# Category breakdown
|
||||||
|
show "Spending by Category"
|
||||||
|
for cat in by_category:
|
||||||
|
show_category_bar:
|
||||||
|
name: cat.category
|
||||||
|
amount: cat.total
|
||||||
|
percent: (cat.total / total_this_month * 100).toFixed(1)
|
||||||
|
|
||||||
|
# Recent expenses
|
||||||
|
show "Recent Expenses"
|
||||||
|
for exp in expenses.sort_by(e => e.created_at).reverse().take(10):
|
||||||
|
show_expense_row:
|
||||||
|
description: exp.description
|
||||||
|
category: exp.category
|
||||||
|
amount: "${exp.amount.toFixed(2)}"
|
||||||
|
date: format_date(exp.date)
|
||||||
|
on_delete: () => delete_expense(exp.id)
|
||||||
|
|
||||||
|
# AI Insights
|
||||||
|
button "Get Spending Insights" -> show_insights()
|
||||||
|
|
||||||
|
show_insights():
|
||||||
|
### Intent: Analyze spending patterns and provide insights
|
||||||
|
insights = ai.analyze(expenses, {
|
||||||
|
focus: "patterns, unusual spending, savings opportunities"
|
||||||
|
})
|
||||||
|
|
||||||
|
show insights
|
||||||
|
|
||||||
|
# Export
|
||||||
|
button "Export to CSV" -> export_data()
|
||||||
|
|
||||||
|
export_data():
|
||||||
|
csv = ai.transform(expenses, { to: "csv" })
|
||||||
|
save_file("expenses.csv", csv)
|
||||||
|
|
||||||
|
generate_id():
|
||||||
|
return now() + Math.random()
|
||||||
|
|
||||||
|
is_this_month(date):
|
||||||
|
return date.month == today().month and date.year == today().year
|
||||||
108
docs/examples/canonical/095-weather-dashboard.lucidia
Normal file
108
docs/examples/canonical/095-weather-dashboard.lucidia
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
# 095: Weather Dashboard
|
||||||
|
# Real-time weather with location and forecasts
|
||||||
|
|
||||||
|
state location = null
|
||||||
|
state weather_data = null
|
||||||
|
state forecast = []
|
||||||
|
state units = "celsius" # celsius or fahrenheit
|
||||||
|
|
||||||
|
# Get user's location
|
||||||
|
get_location():
|
||||||
|
ask permission for: location
|
||||||
|
purpose: "Show weather for your location"
|
||||||
|
|
||||||
|
if granted:
|
||||||
|
with consent.record:
|
||||||
|
location = get_current_location()
|
||||||
|
load_weather()
|
||||||
|
else:
|
||||||
|
show "Enter a city to see weather"
|
||||||
|
|
||||||
|
# Load weather data
|
||||||
|
load_weather():
|
||||||
|
if location == null: return
|
||||||
|
|
||||||
|
ask permission for: network
|
||||||
|
purpose: "Fetch weather data"
|
||||||
|
|
||||||
|
if granted:
|
||||||
|
# Fetch current weather
|
||||||
|
weather_data = fetch "https://api.weather.com/current" with:
|
||||||
|
params: {
|
||||||
|
lat: location.latitude,
|
||||||
|
lon: location.longitude,
|
||||||
|
units: units
|
||||||
|
}
|
||||||
|
|
||||||
|
# Fetch 7-day forecast
|
||||||
|
forecast = fetch "https://api.weather.com/forecast" with:
|
||||||
|
params: {
|
||||||
|
lat: location.latitude,
|
||||||
|
lon: location.longitude,
|
||||||
|
days: 7,
|
||||||
|
units: units
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cache locally
|
||||||
|
store weather_data locally as "weather_cache"
|
||||||
|
expires: "1 hour"
|
||||||
|
|
||||||
|
show "Weather updated"
|
||||||
|
|
||||||
|
# Manual city search
|
||||||
|
form search_city:
|
||||||
|
input city_name -> city
|
||||||
|
placeholder: "Enter city name"
|
||||||
|
|
||||||
|
button "Search" -> search_weather(city)
|
||||||
|
|
||||||
|
search_weather(city):
|
||||||
|
location = geocode(city) # Convert city name to coordinates
|
||||||
|
load_weather()
|
||||||
|
|
||||||
|
# Display current weather
|
||||||
|
if weather_data != null:
|
||||||
|
show_current_weather:
|
||||||
|
temperature: weather_data.temp
|
||||||
|
feels_like: weather_data.feels_like
|
||||||
|
condition: weather_data.description
|
||||||
|
humidity: weather_data.humidity
|
||||||
|
wind_speed: weather_data.wind_speed
|
||||||
|
icon: weather_data.icon
|
||||||
|
|
||||||
|
# 7-day forecast
|
||||||
|
show "7-Day Forecast"
|
||||||
|
for day in forecast:
|
||||||
|
show_forecast_card:
|
||||||
|
date: format_date(day.date)
|
||||||
|
high: day.temp_max
|
||||||
|
low: day.temp_min
|
||||||
|
condition: day.description
|
||||||
|
icon: day.icon
|
||||||
|
precipitation: day.precipitation_chance
|
||||||
|
|
||||||
|
# Settings
|
||||||
|
button "Toggle Units" -> toggle_units()
|
||||||
|
|
||||||
|
toggle_units():
|
||||||
|
units = units == "celsius" ? "fahrenheit" : "celsius"
|
||||||
|
load_weather() # Reload with new units
|
||||||
|
|
||||||
|
# Refresh
|
||||||
|
button "Refresh" -> load_weather()
|
||||||
|
|
||||||
|
# Works offline with cached data
|
||||||
|
if network.offline and weather_data == null:
|
||||||
|
cached = load "weather_cache" locally
|
||||||
|
if cached != null:
|
||||||
|
weather_data = cached
|
||||||
|
show "⚠️ Showing cached data (offline)"
|
||||||
|
|
||||||
|
# Auto-refresh every 30 minutes
|
||||||
|
on interval(30 * 60 * 1000):
|
||||||
|
if network.online:
|
||||||
|
load_weather()
|
||||||
|
|
||||||
|
# Initialize
|
||||||
|
on app.start:
|
||||||
|
get_location()
|
||||||
119
docs/examples/canonical/096-chat-application.lucidia
Normal file
119
docs/examples/canonical/096-chat-application.lucidia
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
# 096: Chat Application
|
||||||
|
# Real-time messaging with WebSocket
|
||||||
|
|
||||||
|
state messages = []
|
||||||
|
state username = load "username" locally or null
|
||||||
|
state connected = false
|
||||||
|
state websocket = null
|
||||||
|
|
||||||
|
# Set username
|
||||||
|
if username == null:
|
||||||
|
ask "Choose a username:" -> username
|
||||||
|
store username locally as "username"
|
||||||
|
|
||||||
|
# Connect to chat server
|
||||||
|
connect():
|
||||||
|
ask permission for: network
|
||||||
|
purpose: "Connect to chat server"
|
||||||
|
|
||||||
|
if granted:
|
||||||
|
websocket = connect_websocket("wss://chat.example.com")
|
||||||
|
|
||||||
|
on websocket.connected:
|
||||||
|
connected = true
|
||||||
|
show "Connected to chat"
|
||||||
|
|
||||||
|
on websocket.message:
|
||||||
|
handle_message(event.data)
|
||||||
|
|
||||||
|
on websocket.disconnected:
|
||||||
|
connected = false
|
||||||
|
show "Disconnected from chat"
|
||||||
|
# Auto-reconnect after 5 seconds
|
||||||
|
wait(5000)
|
||||||
|
connect()
|
||||||
|
|
||||||
|
# Handle incoming messages
|
||||||
|
handle_message(data):
|
||||||
|
message = JSON.parse(data)
|
||||||
|
|
||||||
|
message.type is:
|
||||||
|
"chat": {
|
||||||
|
messages.append({
|
||||||
|
id: message.id,
|
||||||
|
user: message.user,
|
||||||
|
text: message.text,
|
||||||
|
timestamp: message.timestamp
|
||||||
|
})
|
||||||
|
|
||||||
|
# Limit to last 100 messages
|
||||||
|
if messages.length > 100:
|
||||||
|
messages = messages.slice(-100)
|
||||||
|
}
|
||||||
|
"user_joined": {
|
||||||
|
show "{message.user} joined the chat"
|
||||||
|
}
|
||||||
|
"user_left": {
|
||||||
|
show "{message.user} left the chat"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Send message
|
||||||
|
state new_message = ""
|
||||||
|
|
||||||
|
form chat_input:
|
||||||
|
input new_message -> new_message
|
||||||
|
placeholder: "Type a message..."
|
||||||
|
on_enter: send_message()
|
||||||
|
|
||||||
|
button "Send" -> send_message()
|
||||||
|
|
||||||
|
send_message():
|
||||||
|
if new_message == "" or not connected: return
|
||||||
|
|
||||||
|
msg = {
|
||||||
|
type: "chat",
|
||||||
|
user: username,
|
||||||
|
text: new_message,
|
||||||
|
timestamp: now()
|
||||||
|
}
|
||||||
|
|
||||||
|
websocket.send(JSON.stringify(msg))
|
||||||
|
|
||||||
|
new_message = "" # Clear input
|
||||||
|
|
||||||
|
# Display messages
|
||||||
|
show_chat_window:
|
||||||
|
for message in messages:
|
||||||
|
show_message:
|
||||||
|
user: message.user
|
||||||
|
text: message.text
|
||||||
|
time: format_time(message.timestamp)
|
||||||
|
is_mine: message.user == username
|
||||||
|
|
||||||
|
# Status indicator
|
||||||
|
if connected:
|
||||||
|
show "🟢 Connected"
|
||||||
|
else:
|
||||||
|
show "🔴 Disconnected"
|
||||||
|
|
||||||
|
# Leave chat
|
||||||
|
button "Leave Chat" -> disconnect()
|
||||||
|
|
||||||
|
disconnect():
|
||||||
|
if websocket != null:
|
||||||
|
websocket.send(JSON.stringify({
|
||||||
|
type: "user_left",
|
||||||
|
user: username
|
||||||
|
}))
|
||||||
|
websocket.close()
|
||||||
|
|
||||||
|
# Initialize
|
||||||
|
on app.start:
|
||||||
|
connect()
|
||||||
|
|
||||||
|
# Clean up on exit
|
||||||
|
on app.close:
|
||||||
|
disconnect()
|
||||||
|
|
||||||
|
format_time(timestamp):
|
||||||
|
return "12:34 PM" # AI: Format timestamp
|
||||||
130
docs/examples/canonical/097-markdown-editor.lucidia
Normal file
130
docs/examples/canonical/097-markdown-editor.lucidia
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
# 097: Markdown Editor
|
||||||
|
# Live markdown editing with preview and export
|
||||||
|
|
||||||
|
state content = load "draft" locally or """
|
||||||
|
# Welcome to Lucidia Markdown Editor
|
||||||
|
|
||||||
|
Start writing your markdown here...
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- Live preview
|
||||||
|
- Syntax highlighting
|
||||||
|
- Export to HTML/PDF
|
||||||
|
- Auto-save
|
||||||
|
"""
|
||||||
|
|
||||||
|
state preview_mode = "split" # split, edit, preview
|
||||||
|
|
||||||
|
# Editor
|
||||||
|
form markdown_editor:
|
||||||
|
textarea content -> content
|
||||||
|
rows: 25
|
||||||
|
font: "monospace"
|
||||||
|
on_change: auto_save()
|
||||||
|
|
||||||
|
# Toolbar
|
||||||
|
toolbar:
|
||||||
|
button "Bold" -> insert("**bold**")
|
||||||
|
button "Italic" -> insert("*italic*")
|
||||||
|
button "Link" -> insert("[text](url)")
|
||||||
|
button "Image" -> insert("")
|
||||||
|
button "Code" -> insert("`code`")
|
||||||
|
button "Heading" -> insert("## ")
|
||||||
|
|
||||||
|
insert(syntax):
|
||||||
|
# Insert at cursor position
|
||||||
|
content = content + syntax
|
||||||
|
|
||||||
|
# Auto-save
|
||||||
|
auto_save():
|
||||||
|
store content locally as "draft"
|
||||||
|
|
||||||
|
# Preview modes
|
||||||
|
button "Split View" -> preview_mode = "split"
|
||||||
|
button "Edit Only" -> preview_mode = "edit"
|
||||||
|
button "Preview Only" -> preview_mode = "preview"
|
||||||
|
|
||||||
|
# Display based on mode
|
||||||
|
preview_mode is:
|
||||||
|
"split": {
|
||||||
|
show_layout:
|
||||||
|
left:
|
||||||
|
show_editor(content)
|
||||||
|
right:
|
||||||
|
show_markdown_preview(content)
|
||||||
|
}
|
||||||
|
"edit": {
|
||||||
|
show_editor(content)
|
||||||
|
}
|
||||||
|
"preview": {
|
||||||
|
show_markdown_preview(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Word count
|
||||||
|
computed word_count = content.split(/\s+/).length
|
||||||
|
computed char_count = content.length
|
||||||
|
|
||||||
|
show "Words: {word_count} | Characters: {char_count}"
|
||||||
|
|
||||||
|
# Export options
|
||||||
|
button "Export HTML" -> export_html()
|
||||||
|
button "Export PDF" -> export_pdf()
|
||||||
|
button "Download MD" -> export_md()
|
||||||
|
|
||||||
|
export_html():
|
||||||
|
html = ai.transform(content, {
|
||||||
|
from: "markdown",
|
||||||
|
to: "html",
|
||||||
|
include_css: true
|
||||||
|
})
|
||||||
|
|
||||||
|
save_file("document.html", html)
|
||||||
|
show "Exported to HTML"
|
||||||
|
|
||||||
|
export_pdf():
|
||||||
|
### Intent: Convert markdown to PDF
|
||||||
|
pdf = ai.transform(content, {
|
||||||
|
from: "markdown",
|
||||||
|
to: "pdf"
|
||||||
|
})
|
||||||
|
|
||||||
|
save_file("document.pdf", pdf)
|
||||||
|
show "Exported to PDF"
|
||||||
|
|
||||||
|
export_md():
|
||||||
|
save_file("document.md", content)
|
||||||
|
show "Downloaded markdown file"
|
||||||
|
|
||||||
|
# Load file
|
||||||
|
button "Open File" -> open_file()
|
||||||
|
|
||||||
|
open_file():
|
||||||
|
ask permission for: filesystem.read
|
||||||
|
purpose: "Open markdown file"
|
||||||
|
|
||||||
|
if granted:
|
||||||
|
file = open_file_picker(accept: ".md,.txt")
|
||||||
|
if file != null:
|
||||||
|
content = file.read_text()
|
||||||
|
show "File loaded"
|
||||||
|
|
||||||
|
# Save file
|
||||||
|
button "Save File" -> save_file_dialog()
|
||||||
|
|
||||||
|
save_file_dialog():
|
||||||
|
ask permission for: filesystem.write
|
||||||
|
purpose: "Save markdown file"
|
||||||
|
|
||||||
|
if granted:
|
||||||
|
save_file_picker("document.md", content)
|
||||||
|
show "File saved"
|
||||||
|
|
||||||
|
# Keyboard shortcuts
|
||||||
|
on key "Ctrl+B":
|
||||||
|
insert("**bold**")
|
||||||
|
|
||||||
|
on key "Ctrl+I":
|
||||||
|
insert("*italic*")
|
||||||
|
|
||||||
|
on key "Ctrl+S":
|
||||||
|
save_file_dialog()
|
||||||
149
docs/examples/canonical/098-image-gallery.lucidia
Normal file
149
docs/examples/canonical/098-image-gallery.lucidia
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
# 098: Image Gallery
|
||||||
|
# Photo gallery with local storage and albums
|
||||||
|
|
||||||
|
state images = load "images" locally or []
|
||||||
|
state current_album = "all"
|
||||||
|
state selected_image = null
|
||||||
|
|
||||||
|
# Upload images
|
||||||
|
button "Add Photos" -> upload_images()
|
||||||
|
|
||||||
|
upload_images():
|
||||||
|
ask permission for: [filesystem.read, storage]
|
||||||
|
purpose: "Upload and save photos"
|
||||||
|
|
||||||
|
if granted:
|
||||||
|
files = open_file_picker(accept: "image/*", multiple: true)
|
||||||
|
|
||||||
|
for file in files:
|
||||||
|
image = {
|
||||||
|
id: generate_id(),
|
||||||
|
name: file.name,
|
||||||
|
data: file.read_as_data_url(),
|
||||||
|
size: file.size,
|
||||||
|
uploaded_at: now(),
|
||||||
|
album: current_album,
|
||||||
|
tags: [],
|
||||||
|
favorite: false
|
||||||
|
}
|
||||||
|
|
||||||
|
images.append(image)
|
||||||
|
|
||||||
|
store images locally as "images"
|
||||||
|
show "Uploaded {files.length} photos"
|
||||||
|
|
||||||
|
# Create album
|
||||||
|
form create_album:
|
||||||
|
input album_name -> new_album
|
||||||
|
|
||||||
|
button "Create Album" -> create_album_action(new_album)
|
||||||
|
|
||||||
|
create_album_action(name):
|
||||||
|
# Albums are derived from image data
|
||||||
|
current_album = name
|
||||||
|
show "Album '{name}' created"
|
||||||
|
|
||||||
|
# Move to album
|
||||||
|
move_to_album(image_id, album):
|
||||||
|
images = images.map(img => {
|
||||||
|
if img.id == image_id:
|
||||||
|
{ ...img, album: album }
|
||||||
|
else:
|
||||||
|
img
|
||||||
|
})
|
||||||
|
|
||||||
|
store images locally as "images"
|
||||||
|
|
||||||
|
# Toggle favorite
|
||||||
|
toggle_favorite(id):
|
||||||
|
images = images.map(img => {
|
||||||
|
if img.id == id:
|
||||||
|
{ ...img, favorite: not img.favorite }
|
||||||
|
else:
|
||||||
|
img
|
||||||
|
})
|
||||||
|
|
||||||
|
store images locally as "images"
|
||||||
|
|
||||||
|
# Delete image
|
||||||
|
delete_image(id):
|
||||||
|
ask "Delete this image?" -> confirm
|
||||||
|
|
||||||
|
if confirm == "yes":
|
||||||
|
images = images.filter(img => img.id != id)
|
||||||
|
store images locally as "images"
|
||||||
|
selected_image = null
|
||||||
|
|
||||||
|
# Filter by album
|
||||||
|
computed filtered_images = images.filter(img => {
|
||||||
|
current_album == "all" or img.album == current_album
|
||||||
|
})
|
||||||
|
|
||||||
|
# Get unique albums
|
||||||
|
computed albums = images
|
||||||
|
.map(img => img.album)
|
||||||
|
.unique()
|
||||||
|
.sort()
|
||||||
|
|
||||||
|
# Album switcher
|
||||||
|
show "Albums:"
|
||||||
|
button "All ({images.length})" -> current_album = "all"
|
||||||
|
|
||||||
|
for album in albums:
|
||||||
|
count = images.filter(img => img.album == album).length
|
||||||
|
button "{album} ({count})" -> current_album = album
|
||||||
|
|
||||||
|
# Gallery grid
|
||||||
|
show_gallery_grid:
|
||||||
|
for image in filtered_images:
|
||||||
|
show_thumbnail:
|
||||||
|
src: image.data
|
||||||
|
name: image.name
|
||||||
|
favorite: image.favorite
|
||||||
|
on_click: () => selected_image = image
|
||||||
|
on_favorite: () => toggle_favorite(image.id)
|
||||||
|
|
||||||
|
# Lightbox view
|
||||||
|
if selected_image != null:
|
||||||
|
show_lightbox:
|
||||||
|
image: selected_image.data
|
||||||
|
name: selected_image.name
|
||||||
|
size: format_file_size(selected_image.size)
|
||||||
|
uploaded: format_date(selected_image.uploaded_at)
|
||||||
|
|
||||||
|
button "Close" -> selected_image = null
|
||||||
|
button "Delete" -> delete_image(selected_image.id)
|
||||||
|
button "Previous" -> show_previous()
|
||||||
|
button "Next" -> show_next()
|
||||||
|
|
||||||
|
show_previous():
|
||||||
|
index = filtered_images.find_index(img => img.id == selected_image.id)
|
||||||
|
if index > 0:
|
||||||
|
selected_image = filtered_images[index - 1]
|
||||||
|
|
||||||
|
show_next():
|
||||||
|
index = filtered_images.find_index(img => img.id == selected_image.id)
|
||||||
|
if index < filtered_images.length - 1:
|
||||||
|
selected_image = filtered_images[index + 1]
|
||||||
|
|
||||||
|
# Auto-tag with AI
|
||||||
|
button "Auto-Tag All" -> auto_tag_images()
|
||||||
|
|
||||||
|
auto_tag_images():
|
||||||
|
for image in images:
|
||||||
|
if image.tags.length == 0:
|
||||||
|
tags = ai.classify(image.data, {
|
||||||
|
categories: ["nature", "people", "food", "architecture", "animals"],
|
||||||
|
multiple: true
|
||||||
|
})
|
||||||
|
|
||||||
|
image.tags = tags
|
||||||
|
|
||||||
|
store images locally as "images"
|
||||||
|
show "Images auto-tagged"
|
||||||
|
|
||||||
|
generate_id():
|
||||||
|
return now() + Math.random()
|
||||||
|
|
||||||
|
format_file_size(bytes):
|
||||||
|
return "{(bytes / 1024).toFixed(1)} KB"
|
||||||
162
docs/examples/canonical/099-music-player.lucidia
Normal file
162
docs/examples/canonical/099-music-player.lucidia
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
# 099: Music Player
|
||||||
|
# Audio player with playlists and controls
|
||||||
|
|
||||||
|
state playlist = load "playlist" locally or []
|
||||||
|
state current_track = null
|
||||||
|
state playing = false
|
||||||
|
state current_time = 0
|
||||||
|
state volume = 0.7
|
||||||
|
|
||||||
|
# Add songs
|
||||||
|
button "Add Songs" -> add_songs()
|
||||||
|
|
||||||
|
add_songs():
|
||||||
|
ask permission for: [filesystem.read, storage]
|
||||||
|
purpose: "Add music files"
|
||||||
|
|
||||||
|
if granted:
|
||||||
|
files = open_file_picker(accept: "audio/*", multiple: true)
|
||||||
|
|
||||||
|
for file in files:
|
||||||
|
track = {
|
||||||
|
id: generate_id(),
|
||||||
|
title: extract_title(file.name),
|
||||||
|
artist: "Unknown Artist",
|
||||||
|
duration: 0, # Will be set when loaded
|
||||||
|
file: file,
|
||||||
|
added_at: now()
|
||||||
|
}
|
||||||
|
|
||||||
|
playlist.append(track)
|
||||||
|
|
||||||
|
store playlist locally as "playlist"
|
||||||
|
show "Added {files.length} songs"
|
||||||
|
|
||||||
|
# Playback controls
|
||||||
|
play():
|
||||||
|
if current_track == null and playlist.length > 0:
|
||||||
|
current_track = playlist[0]
|
||||||
|
|
||||||
|
if current_track != null:
|
||||||
|
audio.play(current_track.file)
|
||||||
|
playing = true
|
||||||
|
|
||||||
|
pause():
|
||||||
|
audio.pause()
|
||||||
|
playing = false
|
||||||
|
|
||||||
|
stop():
|
||||||
|
audio.stop()
|
||||||
|
playing = false
|
||||||
|
current_time = 0
|
||||||
|
|
||||||
|
next_track():
|
||||||
|
index = playlist.find_index(t => t.id == current_track.id)
|
||||||
|
if index < playlist.length - 1:
|
||||||
|
current_track = playlist[index + 1]
|
||||||
|
play()
|
||||||
|
|
||||||
|
previous_track():
|
||||||
|
index = playlist.find_index(t => t.id == current_track.id)
|
||||||
|
if index > 0:
|
||||||
|
current_track = playlist[index - 1]
|
||||||
|
play()
|
||||||
|
|
||||||
|
seek(time):
|
||||||
|
audio.seek(time)
|
||||||
|
current_time = time
|
||||||
|
|
||||||
|
set_volume(vol):
|
||||||
|
volume = vol
|
||||||
|
audio.set_volume(vol)
|
||||||
|
|
||||||
|
# Audio events
|
||||||
|
on audio.timeupdate:
|
||||||
|
current_time = audio.current_time
|
||||||
|
|
||||||
|
on audio.ended:
|
||||||
|
next_track()
|
||||||
|
|
||||||
|
# Now playing
|
||||||
|
if current_track != null:
|
||||||
|
show_now_playing:
|
||||||
|
title: current_track.title
|
||||||
|
artist: current_track.artist
|
||||||
|
artwork: current_track.artwork or "default.jpg"
|
||||||
|
|
||||||
|
# Progress bar
|
||||||
|
show_progress_bar:
|
||||||
|
current: current_time
|
||||||
|
total: current_track.duration
|
||||||
|
on_seek: (time) => seek(time)
|
||||||
|
|
||||||
|
# Time display
|
||||||
|
show "{format_time(current_time)} / {format_time(current_track.duration)}"
|
||||||
|
|
||||||
|
# Controls
|
||||||
|
show_controls:
|
||||||
|
button "⏮" -> previous_track()
|
||||||
|
|
||||||
|
if playing:
|
||||||
|
button "⏸" -> pause()
|
||||||
|
else:
|
||||||
|
button "▶" -> play()
|
||||||
|
|
||||||
|
button "⏹" -> stop()
|
||||||
|
button "⏭" -> next_track()
|
||||||
|
|
||||||
|
# Volume
|
||||||
|
show_volume_slider:
|
||||||
|
value: volume
|
||||||
|
on_change: (vol) => set_volume(vol)
|
||||||
|
|
||||||
|
# Playlist
|
||||||
|
show "Playlist ({playlist.length} songs)"
|
||||||
|
|
||||||
|
for track in playlist:
|
||||||
|
show_track_row:
|
||||||
|
title: track.title
|
||||||
|
artist: track.artist
|
||||||
|
duration: format_time(track.duration)
|
||||||
|
playing: current_track?.id == track.id
|
||||||
|
on_click: () => {
|
||||||
|
current_track = track
|
||||||
|
play()
|
||||||
|
}
|
||||||
|
on_remove: () => remove_track(track.id)
|
||||||
|
|
||||||
|
remove_track(id):
|
||||||
|
playlist = playlist.filter(t => t.id != id)
|
||||||
|
|
||||||
|
if current_track?.id == id:
|
||||||
|
stop()
|
||||||
|
current_track = null
|
||||||
|
|
||||||
|
store playlist locally as "playlist"
|
||||||
|
|
||||||
|
# Shuffle
|
||||||
|
button "Shuffle" -> shuffle_playlist()
|
||||||
|
|
||||||
|
shuffle_playlist():
|
||||||
|
playlist = shuffle(playlist)
|
||||||
|
show "Playlist shuffled"
|
||||||
|
|
||||||
|
# Clear playlist
|
||||||
|
button "Clear Playlist" -> clear_playlist()
|
||||||
|
|
||||||
|
clear_playlist():
|
||||||
|
playlist = []
|
||||||
|
current_track = null
|
||||||
|
stop()
|
||||||
|
store playlist locally as "playlist"
|
||||||
|
|
||||||
|
extract_title(filename):
|
||||||
|
return filename.replace(/\.[^.]+$/, "") # Remove extension
|
||||||
|
|
||||||
|
format_time(seconds):
|
||||||
|
mins = Math.floor(seconds / 60)
|
||||||
|
secs = Math.floor(seconds % 60)
|
||||||
|
return "{mins}:{secs.pad(2)}"
|
||||||
|
|
||||||
|
generate_id():
|
||||||
|
return now() + Math.random()
|
||||||
191
docs/examples/canonical/100-ecommerce-checkout.lucidia
Normal file
191
docs/examples/canonical/100-ecommerce-checkout.lucidia
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
# 100: E-Commerce Checkout
|
||||||
|
# Complete checkout flow with payment, consent, and local cart
|
||||||
|
|
||||||
|
state cart = load "cart" locally or []
|
||||||
|
state user = load "user" locally or null
|
||||||
|
state checkout_step = "cart" # cart, shipping, payment, confirmation
|
||||||
|
|
||||||
|
# Add to cart
|
||||||
|
add_to_cart(product):
|
||||||
|
existing = cart.find(item => item.id == product.id)
|
||||||
|
|
||||||
|
if existing != null:
|
||||||
|
existing.quantity = existing.quantity + 1
|
||||||
|
else:
|
||||||
|
cart.append({ ...product, quantity: 1 })
|
||||||
|
|
||||||
|
store cart locally as "cart"
|
||||||
|
show "Added to cart"
|
||||||
|
|
||||||
|
# Remove from cart
|
||||||
|
remove_from_cart(product_id):
|
||||||
|
cart = cart.filter(item => item.id != product_id)
|
||||||
|
store cart locally as "cart"
|
||||||
|
|
||||||
|
# Update quantity
|
||||||
|
update_quantity(product_id, quantity):
|
||||||
|
if quantity <= 0:
|
||||||
|
remove_from_cart(product_id)
|
||||||
|
return
|
||||||
|
|
||||||
|
cart = cart.map(item => {
|
||||||
|
if item.id == product_id:
|
||||||
|
{ ...item, quantity }
|
||||||
|
else:
|
||||||
|
item
|
||||||
|
})
|
||||||
|
|
||||||
|
store cart locally as "cart"
|
||||||
|
|
||||||
|
# Cart totals
|
||||||
|
computed subtotal = cart.reduce((sum, item) => {
|
||||||
|
sum + (item.price * item.quantity)
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
computed tax = subtotal * 0.08
|
||||||
|
computed shipping = subtotal > 50 ? 0 : 5.99
|
||||||
|
computed total = subtotal + tax + shipping
|
||||||
|
|
||||||
|
# Checkout flow
|
||||||
|
checkout_step is:
|
||||||
|
"cart": show_cart()
|
||||||
|
"shipping": show_shipping_form()
|
||||||
|
"payment": show_payment_form()
|
||||||
|
"confirmation": show_confirmation()
|
||||||
|
|
||||||
|
# Step 1: Cart
|
||||||
|
show_cart():
|
||||||
|
show "Shopping Cart ({cart.length} items)"
|
||||||
|
|
||||||
|
for item in cart:
|
||||||
|
show_cart_item:
|
||||||
|
name: item.name
|
||||||
|
price: "${item.price}"
|
||||||
|
quantity: item.quantity
|
||||||
|
total: "${(item.price * item.quantity).toFixed(2)}"
|
||||||
|
on_update: (qty) => update_quantity(item.id, qty)
|
||||||
|
on_remove: () => remove_from_cart(item.id)
|
||||||
|
|
||||||
|
show "Subtotal: ${subtotal.toFixed(2)}"
|
||||||
|
show "Tax: ${tax.toFixed(2)}"
|
||||||
|
show "Shipping: ${shipping.toFixed(2)}"
|
||||||
|
show "Total: ${total.toFixed(2)}"
|
||||||
|
|
||||||
|
if cart.length > 0:
|
||||||
|
button "Proceed to Checkout" -> checkout_step = "shipping"
|
||||||
|
|
||||||
|
# Step 2: Shipping
|
||||||
|
show_shipping_form():
|
||||||
|
form shipping_address:
|
||||||
|
input fullname -> shipping.name
|
||||||
|
input address -> shipping.address
|
||||||
|
input city -> shipping.city
|
||||||
|
select state -> shipping.state
|
||||||
|
options: ["CA", "NY", "TX", "FL", "WA"]
|
||||||
|
input zipcode -> shipping.zip
|
||||||
|
validate: is_zipcode
|
||||||
|
|
||||||
|
button "Continue to Payment" -> {
|
||||||
|
store shipping locally as "shipping"
|
||||||
|
checkout_step = "payment"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Step 3: Payment
|
||||||
|
show_payment_form():
|
||||||
|
# Request payment permission
|
||||||
|
ask permission for: payment.process
|
||||||
|
purpose: "Complete purchase of ${total.toFixed(2)}"
|
||||||
|
|
||||||
|
if granted:
|
||||||
|
form payment_method:
|
||||||
|
select method -> payment.method
|
||||||
|
options: ["Credit Card", "PayPal", "Apple Pay"]
|
||||||
|
|
||||||
|
if payment.method == "Credit Card":
|
||||||
|
input card_number -> payment.card_number
|
||||||
|
type: "text"
|
||||||
|
validate: is_credit_card
|
||||||
|
|
||||||
|
input expiry -> payment.expiry
|
||||||
|
placeholder: "MM/YY"
|
||||||
|
|
||||||
|
input cvv -> payment.cvv
|
||||||
|
type: "password"
|
||||||
|
maxlength: 4
|
||||||
|
|
||||||
|
checkbox save_payment -> payment.save
|
||||||
|
label: "Save payment method for future purchases"
|
||||||
|
|
||||||
|
button "Place Order" -> process_payment()
|
||||||
|
|
||||||
|
process_payment():
|
||||||
|
# Consent to charge
|
||||||
|
ask permission for: payment.charge
|
||||||
|
amount: total
|
||||||
|
purpose: "Complete purchase"
|
||||||
|
|
||||||
|
if granted:
|
||||||
|
with consent.record:
|
||||||
|
# Process payment
|
||||||
|
result = charge_payment(payment, total)
|
||||||
|
|
||||||
|
if result.success:
|
||||||
|
# Clear cart
|
||||||
|
order_id = result.order_id
|
||||||
|
cart = []
|
||||||
|
store cart locally as "cart"
|
||||||
|
|
||||||
|
# Save order
|
||||||
|
order = {
|
||||||
|
id: order_id,
|
||||||
|
items: cart,
|
||||||
|
total: total,
|
||||||
|
shipping: shipping,
|
||||||
|
date: now()
|
||||||
|
}
|
||||||
|
|
||||||
|
orders = load "orders" locally or []
|
||||||
|
orders.append(order)
|
||||||
|
store orders locally as "orders"
|
||||||
|
|
||||||
|
checkout_step = "confirmation"
|
||||||
|
else:
|
||||||
|
show "Payment failed: {result.error}"
|
||||||
|
else:
|
||||||
|
show "Payment cancelled"
|
||||||
|
|
||||||
|
# Step 4: Confirmation
|
||||||
|
show_confirmation():
|
||||||
|
show "✓ Order Confirmed!"
|
||||||
|
show "Order ID: {order_id}"
|
||||||
|
show "Total: ${total.toFixed(2)}"
|
||||||
|
|
||||||
|
show "A confirmation email has been sent"
|
||||||
|
|
||||||
|
button "Continue Shopping" -> {
|
||||||
|
checkout_step = "cart"
|
||||||
|
}
|
||||||
|
|
||||||
|
button "View Orders" -> show_order_history()
|
||||||
|
|
||||||
|
# Order history
|
||||||
|
show_order_history():
|
||||||
|
orders = load "orders" locally or []
|
||||||
|
|
||||||
|
show "Your Orders ({orders.length})"
|
||||||
|
|
||||||
|
for order in orders:
|
||||||
|
show_order_summary:
|
||||||
|
id: order.id
|
||||||
|
date: format_date(order.date)
|
||||||
|
total: "${order.total.toFixed(2)}"
|
||||||
|
items: order.items.length
|
||||||
|
|
||||||
|
# This example shows:
|
||||||
|
# - Local-first cart (persists across sessions)
|
||||||
|
# - Multi-step checkout flow
|
||||||
|
# - Consent for payment processing
|
||||||
|
# - Real payment integration
|
||||||
|
# - Order history
|
||||||
|
# - All data stored locally
|
||||||
|
# - Works offline (cart persists)
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user