Loading Docs...
Language Reference

The LIPI Manual.

Complete reference for the LIPI programming language — from syntax fundamentals to real-world algorithm design. Everything documented here reflects the actual behavior of the v3.0 engine.

AST
Compiler Architecture
30+
Built-in Functions
Zero
Setup Required
DOM
Native Web Access
01 — Introduction

What is LIPI?

LIPI is a dynamically-typed, interpreted programming language that compiles to JavaScript at runtime inside the browser. Its pipeline is: Lexer → Parser → AST → Code Generator → JS Execution. It is designed to feel like a clean hybrid of Python and JavaScript — with Python's readable syntax and JavaScript's direct access to the browser's DOM.

LIPI is not a toy or a demo language. It features a full operator precedence parser (Pratt parser), a proper abstract syntax tree, a scope-aware code generator, and a production-grade runtime with 30+ built-in functions. Every feature documented here works exactly as described.

Clean Syntax

No semicolons. No curly-brace ceremony for conditions. Indentation is used for readability. Blocks are delimited with {}.

DOM-Native

HTML element selection and event binding are first-class language features. No framework or library needed to manipulate the page.

Transparent Async

Async is invisible. Call wait(ms) and execution pauses cleanly. No async/await keywords required in your code.

Design Philosophy

LIPI's primary use case is writing logic for interactive browser-based applications, visual simulations, algorithm demonstrations, and educational tooling. If you can think it in pseudocode, you can write it in LIPI.

01 — Quick Start

Getting Started

LIPI requires no installation, no build step, and no package manager. Include the engine script in your HTML page. The engine auto-initializes on DOMContentLoaded.

your-page.html
<!-- Step 1: Include the engine -->
<script src="lipi.js"></script>

<!-- Step 2: Write LIPI code with Lipi.run() -->
<script>
  document.addEventListener('DOMContentLoaded', () => {
    Lipi.run(`
      log("Hello from LIPI!")
      let x = 10 + 5
      log("Result: " + x)
    `);
  });
</script>

In the LIPI Studio editor, you write code directly — no setup at all. The main.lipi tab is your logic file; the index.html tab is your layout. Click RUN or press Ctrl+Enter to execute.

first-program.lipi
# Your first LIPI program
let language = "LIPI"
let version  = 11.0
let ready    = true

log("Language: " + language)
log("Version:  " + version)
log("Ready:    " + ready)
01 — Execution Model

How LIPI Runs Code

Understanding how LIPI executes code will help you write better programs and debug unexpected behavior. The pipeline has four stages:

1
Lexer (Tokenization)

Your source text is scanned character-by-character and converted into a flat stream of typed tokens: keywords, identifiers, strings, numbers, operators, and punctuation. Unknown characters cause a LexError immediately.

2
Parser (AST Construction)

The token stream is parsed using a Recursive Descent parser with a Pratt (top-down operator precedence) expression parser. This produces a typed Abstract Syntax Tree. Invalid syntax causes a ParseError with line numbers.

3
Code Generator (AST → JavaScript)

The AST is walked and each node is emitted as equivalent JavaScript. The generator wraps output in an async IIFE so that wait() can pause execution transparently using await. User-declared functions are tracked and awaited at all call sites.

4
JavaScript Execution

The generated JavaScript is executed via new Function() in the current page scope. All browser globals (Math, JSON, Object, Array, etc.) are accessible. Runtime errors are caught and displayed with full context.

Why no await in your code?

LIPI's code generator automatically inserts await in the compiled JavaScript for any call to wait(), getInput(), or any function you define with func. This means your LIPI code reads like synchronous code while executing asynchronously under the hood.

02 — Fundamentals

Variables & Types

All variables are declared with the let keyword. LIPI is dynamically typed — a variable can hold any value and its type can change during execution. There is no const or var.

types.lipi
# All five primitive types
let name    = "Ram"          # String
let score   = 98.5             # Number (integer or float)
let active  = true             # Boolean
let nothing = null             # Null
let pending = undefined        # Undefined

log(type(name))   # "string"
log(type(score))  # "number"
log(type(active)) # "boolean"
log(type(nothing))# "null"

Comments

Two comment styles are supported. Both are stripped before compilation.

# Hash comment — Python-style, can appear on its own line
let x = 10  # inline comment after a value token

// Double-slash comment — JavaScript-style
let y = 20  // also works inline

String Literals

Strings can use single quotes, double quotes, or backticks. Backtick strings support interpolation with {expression}.

strings.lipi
let name = "World"
let count = 3

# Standard concatenation
log("Hello, " + name + "!")

# Backtick interpolation (cleaner for complex strings)
log(`Hello, {name}! You have {count} messages.`)

# Escape sequences work in all string types
log("Line one\nLine two")
log("Tab:\there")
Variable Scope

LIPI uses JavaScript's function-level scoping. Variables declared with let inside a block (if, while, for, func) are scoped to that block. Variables declared at the top level are available throughout the script execution.

02 — Fundamentals

Operators & Expressions

LIPI supports a comprehensive set of operators. Expressions follow standard mathematical precedence rules, and all operators are parsed by the Pratt parser for correct associativity.

Arithmetic
let a = 10
let b = 3

log(a + b)   # 13  — addition
log(a - b)   # 7   — subtraction
log(a * b)   # 30  — multiplication
log(a / b)   # 3.33 — division
log(a % b)   # 1   — modulo
log(a // b)  # 3   — floor division
log(a ** b)  # 1000 — exponentiation
Comparison & Logical
let x = 5

log(x == 5)   # true  — strict equal
log(x != 3)   # true  — not equal
log(x > 3)    # true  — greater
log(x <= 5)   # true  — less or equal

log(x > 3 and x < 10) # true
log(x == 1 or  x == 5) # true
log(not false)           # true

Assignment Operators

assignment.lipi
let n = 10

n += 5   # n = 15  (add and assign)
n -= 3   # n = 12  (subtract and assign)
n *= 2   # n = 24  (multiply and assign)
n //= 5  # n = 4   (floor-divide and assign)
n **= 3  # n = 64  (exponentiate and assign)
n %= 10  # n = 4   (modulo and assign)
log(n)

Increment / Decrement

increment.lipi
let i = 0
i++        # postfix increment: i = 1
i++        # i = 2
--i        # prefix decrement: i = 1
log(i)     # 1

Ternary Expression

LIPI uses Python-style ternary syntax: value_if_true if condition else value_if_false.

ternary.lipi
let age    = 20
let status = "adult" if age >= 18 else "minor"
log(status) # "adult"

let score = 72
let grade = "A" if score >= 90 else "B" if score >= 70 else "C"
log(grade) # "B"

Operator Precedence

PrecedenceOperator(s)Description
7 (highest)**Exponentiation (right-associative)
6* / % //Multiply, divide, modulo, floor division
5+ -Addition, subtraction
4< <= > >=Comparison
3== !=Equality (strict)
2andLogical AND
1 (lowest)orLogical OR
02 — Fundamentals

Control Flow

LIPI supports if, else if, and else. Curly braces {} are always required around blocks. The condition does not need parentheses.

control.lipi
let temp = 22

if temp > 35 {
  log("Extreme heat warning")
} else if temp > 25 {
  log("Warm day")
} else if temp > 15 {
  log("Comfortable temperature")
} else {
  log("Cold conditions")
}

Compound Conditions

logic.lipi
let user   = "Ram"
let role   = "admin"
let active = true

if active and role == "admin" {
  log("Access granted to " + user)
}

if role == "guest" or not active {
  log("Limited access")
} else {
  log("Full access")
}

Assert

Use assert for defensive programming. If the condition is false, an error is thrown immediately with an optional message.

assert.lipi
let balance = 100

# Will pass — balance is positive
assert balance > 0, "Balance must be positive"

let items = [1, 2, 3]
assert len(items) == 3

log("All assertions passed")
typeof operator

Use typeof x to inspect the runtime type of a value. Returns "string", "number", "boolean", "object", "undefined", or "function". For arrays and null, use the type() builtin instead, which returns "array" and "null" correctly.

02 — Fundamentals

Loops

LIPI has two loop constructs: while for condition-based iteration, and for...in for iterating over collections. Both support break and continue.

While Loop

while.lipi
let fuel = 5

while fuel > 0 {
  log("Fuel remaining: " + fuel)
  fuel--
  wait(200)
}
log("Tank empty.")

For-In: Arrays

When the iterable is an array or string, for x in collection iterates over the values.

for-array.lipi
let planets = ["Mercury", "Venus", "Earth", "Mars"]

for planet in planets {
  log("Planet: " + planet)
}

For-In: Objects

When the iterable is an object, for k in obj iterates over the keys (property names).

for-object.lipi
let config = {host: "localhost", port: 8080, debug: true}

for key in config {
  log(key + " = " + config[key])
}

For-In: Numbers (Range Shorthand)

When the iterable is a number n, it iterates from 0 to n-1. For custom ranges, use the range() builtin.

for-range.lipi
# Iterate 0..4
for i in 5 {
  log("i = " + i)
}

# range(start, end)  — does NOT include end
for n in range(1, 6) {
  log(n)
}

# range(start, end, step)
for x in range(0, 10, 2) {
  log(x) # 0, 2, 4, 6, 8
}

Break & Continue

break-continue.lipi
# break — exit the loop early
for i in 10 {
  if i == 4 { break }
  log("i = " + i) # logs 0, 1, 2, 3
}

# continue — skip the rest of this iteration
for n in range(1, 8) {
  if n % 2 == 0 { continue }
  log(n) # logs 1, 3, 5, 7
}
02 — Fundamentals

Functions

Functions are declared with func. They can accept parameters and return a value with return. All user-declared functions are internally async, which means they can call wait() and other async functions freely.

functions.lipi
# Basic function with parameters and return
func add(a, b) {
  return a + b
}

# Functions can return any type
func greet(name) {
  return "Hello, " + name + "!"
}

# Functions without return implicitly return undefined
func printLine(msg) {
  log("[LOG] " + msg)
}

let sum = add(12, 8)
log(sum)                 # 20
log(greet("Ram"))     # "Hello, Ram!"
printLine("test")

Recursive Functions

recursion.lipi
func factorial(n) {
  if n <= 1 { return 1 }
  return n * factorial(n - 1)
}

for i in range(1, 8) {
  log(`{i}! = {factorial(i)}`)
}

Functions with Async Logic

Because all func declarations are async, they can freely call wait() and other user-defined functions without any special syntax.

async-func.lipi
func countdown(from) {
  let n = from
  while n > 0 {
    log("T-minus: " + n)
    wait(400)
    n--
  }
  log("🚀 Liftoff!")
}

countdown(5)
Hoisting Limitation

LIPI functions are declared sequentially. You must declare a function before calling it in top-level code. Mutual recursion (two functions calling each other) works because the compiler registers all declared function names before generating call sites.

03 — Data Structures

Arrays

Arrays are ordered, zero-indexed, mutable collections. They can hold any mix of types. Use [] to create an array and arr[i] for indexed access.

arrays.lipi
let scores = [95, 87, 62, 78, 91]

# Index access (zero-based)
log(scores[0])          # 95
log(scores[len(scores) - 1]) # 91 (last element)

# Mutation
scores[2] = 70
log(scores)

# Array size
log("Count: " + len(scores))

# Append / pop
append(scores, 100)
log(scores)
pop(scores)
log(scores)

Functional Operations

array-ops.lipi
let nums = [3, 1, 4, 1, 5, 9, 2, 6]

log(sum(nums))                   # 31
log(avg(nums))                   # 3.875
log(max(nums))                   # 9
log(min(nums))                   # 1
log(sort(nums))                  # [1, 1, 2, 3, 4, 5, 6, 9]
log(reverse(nums))               # reversed copy
log(includes(nums, 9))          # true
log(indexOf(nums, 4))           # 2
log(slice(nums, 2, 5))          # [4, 1, 5]
log(join(nums, ", "))            # "3, 1, 4, ..."
Copying Arrays

Arrays are reference types. Assigning let b = a makes both variables point to the same array. To create an independent copy, use copy(arr) for a shallow copy or deepCopy(arr) for a deep copy of nested structures.

03 — Data Structures

Objects

Objects are unordered key-value maps. Keys are strings. Values can be any type, including nested objects or arrays. Use dot notation for static keys and bracket notation for dynamic or computed keys.

objects.lipi
let user = {
  name:    "ram",
  age:     30,
  roles:   ["editor", "viewer"],
  address: { city: "Mumbai", zip: "400001" }
}

# Dot notation
log(user.name)
log(user.address.city)

# Bracket notation (dynamic key)
let field = "age"
log(user[field])

# Mutation
user.age = 31
user["email"] = "ram@example.com"

# Delete a property
delete user.address

# Enumerate keys
for key in user {
  log(`{key}: {user[key]}`)
}

Object Inspection

object-utils.lipi
let config = {host: "localhost", port: 3000, ssl: false}

log(keys(config))           # ["host", "port", "ssl"]
log(values(config))         # ["localhost", 3000, false]
log(len(config))            # 3
log(has(config, "port"))   # true
log(has(config, "auth"))   # false

# Merge two objects (non-destructive)
let override = {port: 443, ssl: true}
let final    = merge(config, override)
log(final)

JSON Serialization

json.lipi
let data = {name: "Ram", scores: [90, 85, 92]}

# Serialize to JSON string
let json = toJSON(data)
log(json)

# Parse back from string
let restored = fromJSON(json)
log(restored.name)
04 — Web Engine

DOM Selection

LIPI provides two syntaxes for selecting DOM elements. Both search within the preview container first, then fall back to the full document.

Shorthand (recommended)
#elementId

Prefix-hash notation. Selects by element ID. The # is a language-level token that compiles to a DOM lookup.

let btn = #submit-btn
Function call (flexible)
$("selector")

Accepts any CSS selector string — IDs, classes, attributes, or compound selectors.

let btn = $("#submit-btn")
dom-selection.lipi
# Shorthand selector — compiles to document.getElementById("title")
let title = #page-title

# Always check if the element was found before using it
if title == null {
  log("Element not found!")
} else {
  log("Found: " + title.id)
}

# Select multiple elements by CSS class
let cards = queryAll(".card")
log("Found " + len(cards) + " cards")
Selector Scope

In LIPI Studio, selectors search the Live Preview pane first. Elements in your index.html tab are the ones accessible at runtime. Elements in the editor UI itself are not targeted.

04 — Web Engine

Styles & Content

Once you have a reference to a DOM element, you can read and write its content and styles directly. LIPI also provides convenience style shortcut aliases so you can write box.style.bg instead of box.style.backgroundColor.

manipulation.lipi
let card = #my-card

# Content
card.innerText  = "Updated text"
card.innerHTML  = "<strong>Bold</strong> content"

# Style — standard CSS property names
card.style.backgroundColor = "#7c3aed"
card.style.fontSize         = "18px"
card.style.display          = "none"

# Style — LIPI shorthand aliases (same result)
card.style.bg      = "#7c3aed"   # backgroundColor
card.style.fg      = "white"      # color
card.style.size    = "18px"       # fontSize
card.style.radius  = "12px"       # borderRadius
card.style.shadow  = "0 4px 12px rgba(0,0,0,.4)"

Full Style Shortcut Reference

LIPI ShortcutCSS PropertyExample Value
style.bgbackgroundColor"#7c3aed" or "red"
style.fgcolor"white" or "#fff"
style.sizefontSize"16px" or "1.2em"
style.weightfontWeight"bold" or "600"
style.radiusborderRadius"8px" or "50%"
style.shadowboxShadow"0 4px 12px rgba(0,0,0,.3)"
style.opacityopacity"0.5" or "1"
style.cursorcursor"pointer" or "default"
style.transitiontransition"all 0.3s ease"
style.displaydisplay"flex", "none", "block"

DOM Helper Functions

dom-helpers.lipi
let el = #status

# Text and HTML content helpers
setText(el, "Hello World")     # sets innerText safely
setHTML(el, "<em>italic</em>") # sets innerHTML

# Visibility
show(el)   # removes display:none
hide(el)   # sets display:none

# CSS class management
addClass(el, "active")
removeClass(el, "inactive")
toggleClass(el, "highlighted")

# Clear content
clear(el)
04 — Web Engine

Events

The on keyword binds an event listener to a DOM element. The syntax is on target.eventName { ... }. The target must be a variable holding a DOM element, or the #id shorthand used directly.

events.lipi
let btn   = #action-btn
let input = #text-input

# Click event
on btn.click {
  log("Button was clicked")
}

# Mouse enter/leave
on btn.mouseenter {
  btn.style.bg = "#5b21b6"
}
on btn.mouseleave {
  btn.style.bg = "#7c3aed"
}

# Input change
on input.input {
  log("Value: " + input.value)
}

Async Inside Events

Event handlers are automatically async, so wait() and user functions work seamlessly inside them.

async-event.lipi
let box   = #demo-box
let count = 0

on box.click {
  count++
  box.innerText = "Processing..."
  box.style.bg  = "#f59e0b"

  wait(500)

  box.innerText = "Done! (click " + count + ")"
  box.style.bg  = "#10b981"

  wait(800)
  box.style.bg  = "#7c3aed"
  box.innerText = "Click Me"
}

Common Event Names

EventTrigger
clickMouse click or touch tap
mouseenter / mouseleaveMouse enters or leaves element bounds
mouseover / mouseoutMouse over (includes children)
inputInput field value changes
changeInput loses focus after changing
keydown / keyupKeyboard key pressed/released
submitForm is submitted
focus / blurElement receives/loses focus
scrollElement is scrolled
05 — Async & IO

Async & wait()

wait(ms) pauses code execution for the given number of milliseconds. Unlike JavaScript's setTimeout, it feels synchronous — the next line of code runs only after the wait completes. This is enabled by the async IIFE wrapper the code generator produces.

async.lipi
log("Starting process...")
wait(600)
log("Step 1 complete")
wait(600)
log("Step 2 complete")
wait(600)
log("All done! ✅")

Animated DOM Updates

Combine wait() with DOM updates to build smooth animations and step-by-step visualizations directly in Lipi code.

animation.lipi
# Cycle through a color palette with delays
let colors = ["#ef4444", "#f59e0b", "#10b981", "#3b82f6", "#8b5cf6"]
let box    = #demo-box

for c in colors {
  box.style.bg       = c
  box.innerText      = c
  wait(400)
}
box.innerText  = "Done"
Mental Model: How wait() works

When LIPI compiles wait(500), it emits await __wait(500) in JavaScript. The surrounding async IIFE pauses at this line for 500ms before continuing. The browser UI remains fully responsive during the pause — only your code is waiting, not the page.

05 — Async & IO

User Input

getInput("prompt") pauses execution and waits for the user to type something in the console. In the Studio, a live input field appears in the console panel. The function returns the entered string.

input.lipi
let name = getInput("What is your name?")
log("Hello, " + name + "!")

let numStr = getInput("Enter a number:")
let num    = int(numStr)
log("Squared: " + (num ** 2))
Input always returns a String

getInput() always returns a string value. If you need to do arithmetic with the result, convert it first with int() or float(). Empty input returns an empty string "".

06 — Standard Library

Math & Numbers

All standard math operations are available as top-level functions. The global Math object is also accessible directly.

math.lipi
log(abs(-42))            # 42
log(floor(3.9))          # 3
log(ceil(3.1))           # 4
log(round(3.567, 2))    # 3.57
log(sqrt(144))           # 12
log(pow(2, 10))          # 1024
log(max(10, 3, 7))      # 10
log(min(10, 3, 7))      # 3
log(random(1, 100))     # random integer in [1, 99]
log(random())            # float in [0, 1)

# Constants
log(PI)                  # 3.141592653589793
log(E)                   # 2.718281828459045
06 — Standard Library

String Helpers

strings.lipi
let msg = "  Hello, World!  "

log(trim(msg))              # "Hello, World!"
log(upper(msg))             # "  HELLO, WORLD!  "
log(lower(msg))             # "  hello, world!  "
log(replace(msg, "World", "LIPI"))  # "  Hello, LIPI!  "
log(len(msg))               # 18

let csv = "one,two,three"
let parts = split(csv, ",")
log(parts)                  # ["one", "two", "three"]
log(join(parts, " | "))    # "one | two | three"

log(startsWith(csv, "one")) # true
log(contains(csv, "two"))   # true
log(repeat("ab", 3))        # "ababab"
06 — Standard Library

Array Helpers

FunctionReturnsDescription
len(arr)numberNumber of elements
append(arr, val)arrayAdd to end (mutates)
prepend(arr, val)arrayAdd to start (mutates)
pop(arr)valueRemove and return last element
remove(arr, idx)Remove element at index
slice(arr, a, b)arrayExtract sub-array [a, b)
reverse(arr)arrayReturns reversed copy
sort(arr)arrayReturns sorted copy
includes(arr, val)booleanTrue if val is in array
indexOf(arr, val)numberFirst index of val, or -1
find(arr, fn)valueFirst element matching predicate
filter(arr, fn)arrayAll elements matching predicate
map(arr, fn)arrayTransform each element
flat(arr, depth)arrayFlatten nested arrays
sum(arr)numberSum of all numeric elements
avg(arr)numberAverage of all numeric elements
copy(arr)arrayShallow copy
deepCopy(arr)arrayDeep copy (JSON-safe)
array-funcs.lipi
let data = [4, 8, 15, 16, 23, 42]

# filter: keep only even numbers (using JS arrow syntax)
let evens = filter(data, n => n % 2 == 0)
log(evens)  # [4, 8, 16, 42]

# map: double each value
let doubled = map(data, n => n * 2)
log(doubled)

# find: first value over 20
let big = find(data, n => n > 20)
log(big)    # 23
Arrow Functions in Callbacks

Functions like filter(), map(), and find() accept JavaScript arrow functions (n => n * 2) as callbacks. These are passed through verbatim to the compiled output, so standard JavaScript callback syntax is fully supported as function arguments.

06 — Standard Library

DOM Helper Functions

FunctionDescription
$(selector)Select element by CSS selector
queryAll(selector)Select all matching elements (returns array)
setText(el, text)Set innerText safely
setHTML(el, html)Set innerHTML
getText(el)Get innerText
getHTML(el)Get innerHTML
show(el)Remove display:none
hide(el)Set display:none
clear(el)Empty innerHTML
addClass(el, cls)Add CSS class(es)
removeClass(el, cls)Remove CSS class(es)
toggleClass(el, cls)Toggle a CSS class
hasClass(el, cls)Check if class is present
appendTo(parent, child)Append child element to parent
06 — Standard Library

Type Conversion

types.lipi
# int() — parse string to integer
let n = int("42")
log(n + 8)          # 50

# float() — parse string to float
let pi = float("3.14159")
log(round(pi, 2))   # 3.14

# str() — convert any value to string
let s = str(99)
log(type(s))         # "string"

# bool() — convert to boolean
log(bool(0))        # false
log(bool(""))       # false
log(bool("hello")) # true

# type() — returns type as string
log(type([1,2,3]))  # "array"
log(type(null))     # "null"
log(type(true))     # "boolean"
07 — Advanced

Error Handling

LIPI errors fall into three categories. All errors display in the Studio's console and as an overlay notification.

LexError— Tokenization phase

Caused by an unrecognized character in your source code. Includes line number.

LexError (line 3): Unexpected character '@'
ParseError— AST construction phase

Caused by invalid syntax — a missing brace, wrong keyword order, unexpected token. Includes line number and what was expected.

ParseError (line 7): Expected 'in' but got '=' (Assign)
Runtime Error— Execution phase

Caused by logic errors at execution time — calling a method on null, type mismatches, assertion failures, etc.

Error: Cannot read properties of null (reading 'style')

Debugging Tips

1
Use log() liberally

Print variables at key points to trace execution flow and validate intermediate values.

2
Guard DOM selectors

Check if element == null before accessing properties. Selector failures are the most common runtime error.

3
Use assert for invariants

Use assert condition, "message" to enforce assumptions about your data at critical points in algorithms.

4
Check types before arithmetic

If a variable might come from getInput() or an array, convert it with int() or float() before doing math.

07 — Advanced

Best Practices

These patterns reflect how LIPI is designed to be used. They will save you from the most common classes of bugs.

Do
# Guard before accessing
let el = #my-box
if el != null {
  el.innerText = "Safe"
}

Always check DOM selectors before use.

Avoid
# This crashes if #my-box is missing
let el = #my-box
el.innerText = "Unsafe"

Don't access properties without a null check.

Do
# Convert input before math
let raw = getInput("Number:")
let n   = int(raw)
log(n * 2)

Always convert input to the correct type.

Avoid
# "5" * 2 works, but "5" + 2 = "52"
let raw = getInput("Number:")
log(raw + 10) # Concatenates!

Don't add numbers to raw string input.

Writing Algorithms in LIPI

Structure complex programs by declaring helper functions at the top, then calling them at the bottom. Keep each function focused on a single responsibility. Use objects to group related state, and arrays as ordered queues or stacks.

For data that changes over time (e.g., simulation state), store it in a top-level let variable. Pass it to functions by reference — objects and arrays are reference types, so mutations inside a function affect the original.

07 — Advanced

Real-World Examples

These examples demonstrate LIPI's capability for real algorithmic work. Each one runs in the console. Click RUN to execute.

Fibonacci with Memoization

Classic dynamic programming example. Stores computed results in an object to avoid redundant recursive calls.

fibonacci.lipi
let cache = {}

func fib(n) {
  if n <= 1 { return n }
  if has(cache, str(n)) { return cache[str(n)] }
  let result = fib(n - 1) + fib(n - 2)
  cache[str(n)] = result
  return result
}

for i in range(1, 16) {
  log(`fib({i}) = {fib(i)}`)
}

State Machine

Models a system that transitions between named states based on events — a common pattern for UI flows, game logic, and network protocols.

state-machine.lipi
let states = {
  idle:    {on_start: "running"},
  running: {on_pause: "paused",  on_stop: "idle", on_error: "error"},
  paused:  {on_resume: "running", on_stop: "idle"},
  error:   {on_reset: "idle"}
}

let current = "idle"

func dispatch(event) {
  let map = states[current]
  let key = "on_" + event
  if has(map, key) {
    let next = map[key]
    log(`[{current}] --{event}--> [{next}]`)
    current = next
  } else {
    log(`Invalid: '{event}' in state '{current}'`)
  }
}

dispatch("start")
dispatch("pause")
dispatch("stop")   # Invalid from paused
dispatch("resume")
dispatch("error")
dispatch("reset")
log("Final state: " + current)

Bubble Sort with Visualization

Implements bubble sort and logs each pass, demonstrating loops, mutation, and algorithmic thinking.

bubble-sort.lipi
func bubbleSort(arr) {
  let n      = len(arr)
  let sorted = copy(arr)
  let passes = 0

  for i in range(n) {
    let swapped = false
    for j in range(n - i - 1) {
      if sorted[j] > sorted[j + 1] {
        let tmp   = sorted[j]
        sorted[j] = sorted[j + 1]
        sorted[j + 1] = tmp
        swapped = true
      }
    }
    passes++
    if not swapped { break }
  }
  log(`Completed in {passes} passes`)
  return sorted
}

let data   = [64, 34, 25, 12, 22, 11, 90]
log("Input:  " + join(data, ", "))
let result = bubbleSort(data)
log("Output: " + join(result, ", "))

Memory Block Allocator

Simulates a simple block-based memory allocator. Demonstrates objects as mutable state containers, dynamic property keys, and has() for existence checking.

memory.lipi
let heap   = {capacity: 32, used: 0, blocks: {}}
let nextId = 0

func alloc(size) {
  if heap.used + size > heap.capacity {
    log("ERR: Out of heap space!")
    return -1
  }
  let id = nextId
  nextId++
  heap.blocks[str(id)] = {size: size, id: id}
  heap.used += size
  log(`alloc(${size}) → block #${id}  [${heap.used}/${heap.capacity} used]`)
  return id
}

func free(id) {
  let key = str(id)
  if not has(heap.blocks, key) {
    log("ERR: Block #" + id + " not found")
    return
  }
  heap.used -= heap.blocks[key].size
  delete heap.blocks[key]
  log(`free(#${id})  [${heap.used}/${heap.capacity} used]`)
}

func status() {
  let count = len(heap.blocks)
  log(`--- Heap: ${heap.used}/${heap.capacity} bytes, ${count} blocks ---`)
}

let a = alloc(8)
let b = alloc(12)
let c = alloc(6)
status()
free(a)
free(b)
let d = alloc(18)
status()

BFS Graph Traversal

Breadth-First Search across a directed graph stored as an adjacency list. Demonstrates queues, object membership checks, and nested loops.

bfs.lipi
let graph = {
  A: ["B", "C"],
  B: ["D", "E"],
  C: ["F"],
  D: [],
  E: ["F"],
  F: []
}

func bfs(start) {
  let visited = {}
  let queue   = [start]
  let order   = []

  visited[start] = true

  while len(queue) > 0 {
    let node = queue[0]
    queue       = slice(queue, 1)
    append(order, node)
    log("Visiting: " + node)

    for neighbor in graph[node] {
      if not has(visited, neighbor) {
        visited[neighbor] = true
        append(queue, neighbor)
      }
    }
  }
  return order
}

let path = bfs("A")
log("BFS order: " + join(path, " → "))

Async Progress Simulation

Demonstrates the power of wait() in loops to create timed output sequences — useful for visualizing algorithms step-by-step.

simulation.lipi
func simulate(label, steps) {
  log(`[{label}] Starting...`)
  let i = 0
  while i < steps {
    i++
    let pct = round((i / steps) * 100)
    let bar = repeat("█", i) + repeat("░", steps - i)
    log(`[{label}] {bar} {pct}%`)
    wait(150)
  }
  log(`[{label}] Complete ✓`)
}

simulate("Compile", 6)
simulate("Deploy", 4)
Console Output
// Output will appear here