Pun.
Pun 1.1.0 · Hun Gall Bani!

Backend scripting that speaks Punjabi.

PUNjabi Server-side Scripting, or Pun for short, is a small PHP-style language. Write ordinary HTML, drop in <?pun … ?> blocks with keywords like je, kaam and dasso, and Pun Server sends back the rendered page.

hello.punHTML + Pun
<h1>Hello</h1>

<?pun
naam = mang.GET["naam"]

je naam == khali {
    naam = "Dost"
}

dasso "<p>Sat Sri Akal " + naam + "</p>"
?>
Get started

Requirements and download

  1. Install Java 21 or newer. Any JDK or JRE works, for example Eclipse Temurin. Check with java -version.
  2. Get the repo. Clone it, or download it as a ZIP from GitHub and unzip it:
    git clone https://github.com/skgchaudhary/pun.git
    cd pun
  3. Find the two folders that matter. target/pun-server.jar is Pun Server (the engine, one executable JAR with everything it needs), and chowk/ is the default website folder it serves.
    pun/
      chowk/              your website: .pun pages, CSS, JS, images
        index.pun
        sample_site/
      target/
        pun-server.jar    Pun Server
        *.md              the full documentation

You can also download just pun-server.jar and point it at any folder of .pun files.

Run Pun Server

Option 1: the desktop app (Pun Server Manager)

  1. Open the JAR. Double-click target/pun-server.jar, or run it without arguments:
    java -jar target/pun-server.jar
    The Pun Server Manager window opens.
  2. Project Folder. It starts at the chowk folder next to target. Click Browse to serve another folder.
  3. Port. 8080 unless you change it.
  4. Click Start Server. Status changes to RUNNING and the URL appears, for example http://localhost:8080.
  5. Click Open Browser to open the site. Every request is listed in the log at the bottom of the window.
  6. Click Stop Server when you're done.

If double-clicking opens the JAR as a ZIP file, Java isn't set up to open .jar files. Use the java -jar command instead.

Option 2: the command line

Use this in a terminal, over SSH, in Docker or on a headless server. Logs go to the terminal; stop the server with Ctrl+C.

CommandWhat it does
java -jar target/pun-server.jar serveServes chowk/ on port 8080 (creates chowk next to target if it's missing)
java -jar target/pun-server.jar serve ./my-siteServes another folder
java -jar target/pun-server.jar serve ./my-site --port 9000Uses another port (short form: -p 9000)
java -jar target/pun-server.jar replOpens the interactive Pun prompt
java -jar target/pun-server.jar helpLists the commands
java -jar target/pun-server.jar versionPrints the version

On Windows PowerShell use backslashes: java -jar target\pun-server.jar serve. Exit codes: 0 success, 1 runtime or startup error, 2 wrong command-line usage.

With the server running, URLs map to files in the website folder:

GET /                 -> index.pun
GET /form.pun         -> form.pun, run by Pun
GET /css/style.css    -> css/style.css, sent as it is
GET /sample_site/     -> sample_site/index.pun

The REPL

The REPL runs Pun statements with the same lexer, parser and interpreter as .pun pages. Variables and functions stay until you leave. Use dasso to print: plain expressions run but don't print. mang and jawab only exist in pages served over HTTP.

pun> x = 2
pun> kaam double(n) { wapas n * 2 }
pun> dasso double(x)
4
pun> je sach {
...>     dasso "ok"
...> }
ok
pun> :quit

REPL commands: :help, :quit, :exit.

Language reference

Files and script blocks

Pun pages use the .pun extension. Everything outside <?pun … ?> is sent to the browser as it is; each block is replaced by whatever it prints with dasso.

<h1>Header</h1>

<?pun
dasso "<p>Generated by Pun</p>"
?>

<footer>Footer</footer>
  • All blocks on a page share one set of variables: set naam in the first block and use it in the last.
  • Each block must be complete. A { opened in one block can't be closed in another, so build conditional HTML with dasso inside one block.
  • ?> always ends the block, even inside a string or a comment.
  • Semicolons are optional. Braces { } mark blocks.

Comments

// a line comment
/* a block comment
   over several lines */

Keywords

Every keyword is an everyday Punjabi word chosen for what it does.

PunGurmukhiMeansIn Pun
jeਜੇifStarts a condition
nahi_taਨਹੀਂ ਤਾਂotherwiseThe else branch
jad_takਜਦ ਤੱਕas long asWhile loop
har_ikਹਰ ਇੱਕeach oneLoop over a collection
vichਵਿੱਚinNames the collection in har_ik
kaamਕੰਮworkDefines a function
wapasਵਾਪਸbackReturns a value
dassoਦੱਸੋtellPrints into the page, like echo
sachਸੱਚtruthtrue
jhoothਝੂਠliefalse
khaliਖ਼ਾਲੀemptynull

Built-ins: mang (ਮੰਗ, the request), jawab (ਜਵਾਬ, the response), soochi (ਸੂਚੀ, list), shabdkosh (ਸ਼ਬਦਕੋਸ਼, dictionary), jatha (ਜੱਥਾ, set) and bhandar (ਭੰਡਾਰ, a database). nava is reserved for a later version.

Values, variables and printing

Assign to create a variable; there's no declaration keyword. Pun has numbers, strings, sach / jhooth, khali, and three collections.

naam   = "Suneet"      // strings use double quotes only
umar   = 37
price  = 99.95
active = sach
value  = khali         // prints as nothing

dasso "Hello " + naam + "\n"   // \" and \n work inside strings
dasso "Total: " + 30           // Total: 30
dasso 10 / 4                   // 2.5: division keeps decimals

+ joins left to right, so "Total: " + 10 + 20 prints Total: 1020; write "Total: " + (10 + 20) for Total: 30. Whole numbers print without .0.

Operators

KindOperatorsNotes
Arithmetic+ - * / %% works on decimals too: x - x % 1 drops the fraction
Comparison== != > < >= <=< > <= >= need numbers
Logic&& || !Stop early: x == khali || x < 1 is safe
Assignment=

Precedence, lowest first: assignment, ||, &&, equality, comparison, + -, * / %, unary, call. So 10 + 20 * 2 is 50. In conditions, jhooth, khali and 0 are false; text is true.

Conditions and loops

je umar >= 18 {
    dasso "Adult"
}
nahi_ta {
    dasso "Minor"
}

// Pun has no "else if": put a je inside nahi_ta
je bill >= 1000 {
    dasso "free lassi"
}
nahi_ta {
    je bill >= 500 {
        dasso "10% off"
    }
}

i = 1
jad_tak i <= 5 {          // while loop
    dasso i
    i = i + 1
}

har_ik dish vich soochi("Saag", "Dal") {     // lists and sets: values
    dasso dish
}
har_ik key, value vich shabdkosh("naam": "Suneet") {   // maps: key and value
    dasso key + "=" + value
}

With one name, har_ik over a map gives its keys. The loop works on a copy taken when it starts, so changing the collection inside the loop is safe. Pun stops a jad_tak loop that runs more than 1,000,000 times.

Functions and scope

kaam jod(a, b) {
    wapas a + b
}

kaam factorial(n) {
    je n <= 1 { wapas 1 }
    wapas n * factorial(n - 1)    // functions can call themselves
}

kaam do_vaar(f, x) {
    wapas f(f(x))                 // functions are values
}

dasso jod(10, 20)                 // 30
dasso do_vaar(factorial, 3)       // 720
  • Define a function before the line that calls it.
  • Inside a function, parameters and new names are local. Assigning to a name the page already has changes the page's variable.
  • A variable first created inside a je or loop block is gone after the block. Create it before the block to keep its value.
Collections

soochi, shabdkosh and jatha

names = soochi("Suneet", "Aman")              // list, indexed from 0
user  = shabdkosh("naam": "Suneet", "umar": 37)  // map with text keys
roles = jatha("admin", "dev", "admin")        // set: keeps each value once

names[0] = "Simran"          // change by index
user["city"] = "Mumbai"      // add or change a key
dasso user["missing"]        // a missing key gives khali
dasso roles.ginti()          // 2

order = shabdkosh("table": 4, "items": soochi("Dal", "Roti"))
order["items"].jodo("Lassi") // reach inside with [ ] and methods
dasso order["items"][1]      // Roti
Methodsoochishabdkoshjatha
ginti()number of itemsnumber of keysnumber of members
hai(x)contains the valuehas the keycontains the member
jodo(x)append–add
hatao(x)remove first matchremove the keyremove the member
saaf()empty itempty itempty it
pehla() / akhri()first / last, or khali––
chaabiyan()–list of keys–
mull()–list of values–
  • shabdkosh keys must be text and keep the order you added them. Checking map.hai(khali) is an error, so test for khali first.
  • List indexes must be whole numbers inside the list; use jodo() to add to the end. jatha has no indexes.
  • Collections are shared, not copied: b = a makes two names for one list. A jatha can't hold other collections.
  • dasso prints a collection as [soochi], [shabdkosh] or [jatha]; loop over it to print its contents.
Web pages

mang: the request

dasso mang.method                // GET or POST
dasso mang.path                  // /form.pun
naam  = mang.GET["naam"]         // ?naam=Suneet
email = mang.POST["email"]       // a form field

je naam == khali || naam == "" {    // missing gives khali, empty gives ""
    naam = "Dost"
}
je mang.POST.hai("email") {
    dasso "got an email"
}
  • mang.GET and mang.POST are read-only shabdkosh values, already URL-decoded (?naam=Suneet%20Kumar gives Suneet Kumar). With repeated keys the last value wins.
  • POST bodies must be application/x-www-form-urlencoded, as an HTML form or fetch with URLSearchParams sends them. Other non-empty bodies get 415.
  • Limits: query string 16 KiB (414), form body 1 MiB (413), 1,000 parameters; bad URL encoding gives 400.
  • Pun has no HTML-escaping helper yet, so dasso mang.GET["naam"] puts exactly what was sent into the page. Compare or look up request values instead of printing them.
<form method="POST" action="process.pun">
    <input name="naam">
    <input name="email" type="email">
    <button type="submit">Submit</button>
</form>

jawab: the response

jawab.status (default 200) and jawab.type (default text/html; charset=UTF-8) can be set anywhere in the page, even inside a function.

jawab.type = "application/json; charset=UTF-8"
jawab.status = 422
dasso "{\"ok\": false, \"error\": \"Pick a dish\"}"
Database

MySQL with bhandar

Pun talks to MySQL through prepared statements: values are bound with mull(), never joined into the SQL text.

db = bhandar("mysql", "localhost", 3306, "pun_test", "root", "password")
db.jodo()                                    // open the connection

stmt = db.taiyar("SELECT naam, email FROM users WHERE active = ?")
stmt.mull(1, sach)                           // parameters count from 1
har_ik user vich stmt.saare() {              // every row, as shabdkosh values
    dasso user["naam"]
}

one = db.taiyar("SELECT naam FROM users WHERE id = ?")
one.mull(1, 10)
user = one.ik()                              // one row, or khali
je user != khali { dasso user["naam"] }

add = db.taiyar("INSERT INTO users (naam, email) VALUES (?, ?)")
add.mull(1, "Simran")
add.mull(2, "simran@example.com")
add.chalao()                                 // INSERT, UPDATE, DELETE, CREATE
dasso add.badle()                            // rows changed

db.band()                                    // close the connection
APIDoes
bhandar(driver, host, port, database, user, password)Describes a connection ("mysql" only, for now)
db.jodo() / db.band()Open / close; calling either twice is safe
db.taiyar(sql)Prepares a statement with ? placeholders
stmt.mull(index, value)Binds a string, number, boolean or khali
stmt.ik() / stmt.saare()One row (or khali) / a soochi of rows
stmt.chalao() / stmt.badle()Run a change / count the rows it changed

Rows come back as shabdkosh values keyed by column name or SQL alias. SQL NULL becomes khali, numbers become numbers, booleans become sach / jhooth, and dates come back as text. Pun can't read .env files or environment variables, so connection details live in the .pun file that uses them. Keep that file out of git, and keep the server private while it holds a real password.

Server

How Pun Server behaves

Pages and files

.pun files are run by Pun. .html, .css, .js, .json, .txt, .png, .jpg, .gif, .svg and .ico are sent as they are. A folder serves its index.pun. Paths outside the website folder are refused.

Errors

A syntax or runtime error replaces the page with a development error page (HTTP 500) giving the file, line and message, never a Java stack trace:

Pun Script Error
File: index.pun
Line: 12
Message: Variable 'user' does not exist.

Sandbox

Scripts get no Java reflection, JVM classes, process execution, filesystem access, environment variables or System.exit. Only mang, jawab, the collections and bhandar are exposed.

Keep secrets out of the website folder

Pun Server sends any file in the website folder that isn't a .pun page, including dotfiles such as .env. Keep secrets beside the website folder, not inside it.

Not in Pun yet

Cookies and sessions, imports and modules (so no server-side includes), JSON and multipart request bodies, string functions such as converting "7" to 7, HTML escaping, reading .env, database drivers other than MySQL, transactions and connection pooling, a package manager, and a production server mode.

Learn more

Sample site, editor support and docs

Sample site: Pind Dhaba

A restaurant dashboard in chowk/sample_site: a page that loads a fragment, live JSON updates, a MySQL members list, and a tour that runs every feature with its output. Start Pun Server and open http://localhost:8080/sample_site/.

VS Code highlighting

vscode-pun colours .pun files like PHP: HTML outside the blocks, Pun inside. Install it from that folder:

npx @vscode/vsce package --skip-license --allow-missing-repository
code --install-extension pun-language-0.0.1.vsix

Full documentation

The Markdown docs ship with the JAR in target/:

Cheat sheet

Pun on one screen

// variable and output
naam = "Suneet"
dasso "Hello " + naam

// if / else
je naam == "Suneet" { dasso "Welcome" } nahi_ta { dasso "Ji Aayan Nu" }

// loops
i = 1
jad_tak i <= 3 {
    dasso i
    i = i + 1
}
har_ik key, value vich shabdkosh("naam": "Suneet") { dasso key + "=" + value }

// function
kaam jod(a, b) { wapas a + b }
dasso jod(10, 20)

// collections
names = soochi("Suneet", "Aman")
names.jodo("Simran")
dasso names.ginti()

// request and response
naam = mang.GET["naam"]
email = mang.POST["email"]
jawab.status = 200
jawab.type = "text/html"

// MySQL
db = bhandar("mysql", "localhost", 3306, "pun_test", "root", "password")
db.jodo()
stmt = db.taiyar("SELECT naam FROM users WHERE id = ?")
stmt.mull(1, 10)
user = stmt.ik()
db.band()
About the developer

Suneet Chaudhary

Hi, I’m Suneet Chaudhary. I’ve been working with PHP since 2016, and over the years it has often made me curious about what actually happens behind the scenes when a script is parsed, executed, and turned into a working web application. Following that curiosity little by little eventually led me to build a server-side scripting language of my own, with its own lexer, parser, interpreter, and a Java-powered runtime.

But after building it, I realized it was still just another backend scripting language. I wanted it to have an identity of its own.

Why Punjabi?

Over the years, while working in multinational companies with colleagues from the UK, Canada, the US, and other countries, I noticed something interesting. Whenever I asked foreign colleagues about their favourite Indian music, very often they would mention a Punjabi pop song—even if they didn’t understand the language.

That always stood out to me. Punjabi music, culture, and expressions have travelled far beyond Punjab and India, giving the language a remarkably global presence.

India itself is a country of many languages and cultures, and PUN is not meant to suggest that one language represents all of India. It is simply an experiment in bringing one of India's most internationally recognizable languages into programming.

There is another interesting part to the story: Punjabi isn't even my native language.

My native language is Odia. I am originally from Odisha but grew up in Mumbai, Maharashtra, surrounded by different Indian languages and cultures. While building PUN, I used ChatGPT to help translate and refine programming concepts into Punjabi words that could feel natural as scripting-language tokens.

That is how an experimental backend language became PUN — PUNjabi Server-side Scripting: PHP-inspired scripting, powered by Java, with a distinctly Punjabi personality.

What is built

  • The language: PHP-style <?pun ?> blocks with Punjabi keywords such as je, kaam and dasso
  • The engine: lexer, parser, interpreter and template engine
  • Pun Server: the web server, MySQL support through bhandar, the command line and REPL, and the Pun Server Manager desktop app

Get in touch

Connect with me on LinkedIn, or follow the project on GitHub.

Pun Server is covered by the PUN Runtime License. The runtime is proprietary, and the applications you write in Pun are yours.