Language reference

Documentation

Getting started

Install the Humal compiler globally with npm:

npm install -g @qvelop444/humal

Create a file named main.hum:

print "Hello World!"

Run the file with:

humal main.hum

Humal compiles the source file to JavaScript and runs it with Node.js.

Text output

Use print to output a value to the console. Parentheses are optional for simple output.

print "Hello World!"
print 2 + 2

Variables

Declare variables with let. Humal keeps the familiar JavaScript semantics of let.

let name = "Bob"
let age = 25
let city = "Warsaw"

print name
print age
print city

Expressions

Humal keeps JavaScript expressions familiar, including arithmetic, function calls, indexing, property access and method calls.

let a = 10
let b = 5

let result = (a + b) * 2

print result
print Math.floor(10 / 3)

Operators

Humal supports familiar JavaScript arithmetic, comparison and logical operators.

let a = 10
let b = 5

print a + b
print a - b
print a * b
print a / b
print a % b

print a > b
print a < b
print a >= b
print a == b
print a != b

Logical operators

let age = 20
let active = true
let enabled = false

if age >= 18 && active
    print "AND works"

if active || enabled
    print "OR works"

if !enabled
    print "NOT works"

Ternary operator

let age = 20

let status = age >= 18 ? "Adult" : "Minor"

print status

Nullish coalescing

let nickname = null
print nickname ?? "Unknown"

Optional chaining

let user = { name: "Bob" }
print user?.name
print user?.profile?.name

Strings

Strings can use single or double quotation marks.

let single = 'Hello'
let double = "World"

print single
print double

JavaScript string methods and properties remain available.

let text = "Hello Humal"

print text.length
print text.toUpperCase()
print text.split(" ")

Functions

Define functions with fn. The function body is determined by indentation.

fn greet(name)
    print "Hello " + name

greet("Bob")
greet("Alex")

Function syntax without parentheses

Humal also supports a shorter function declaration syntax without parentheses.

fn add a, b
    return a + b

print add(10, 20)

Default parameters

fn welcome(name = "World")
    print "Welcome " + name

welcome()
welcome("Humal")

Rest parameters

fn sumAll(...numbers)
    return numbers.reduce((a, b) => a + b, 0)

print sumAll(1, 2, 3, 4)

Arrow functions

let multiply = (a, b) => a * b

print multiply(5, 6)

Conditions

Humal uses indentation instead of curly braces for conditional blocks. Parentheses are not required around the condition.

let age = 20

if age >= 18
    print "Adult"
else
    print "Underage"

Multiple conditions

let score = 75

if score >= 90
    print "Excellent"
elif score >= 70
    print "Good"
elif score >= 50
    print "Pass"
else
    print "Fail"

Input

input reads a value from the terminal. The Humal implementation uses the readline-sync package.

Install the dependency with:

npm install readline-sync

Then use:

let name = input "What is your name? "

print "Hello " + name

Loops

while

Use while to repeat an indented block while a condition is true.

let i = 0

while i < 5
    print i
    i++

for ... in

let numbers = [1, 2, 3, 4, 5]

for number in numbers
    print number

break

let counter = 0

while counter < 10
    if counter == 3
        break

    print counter
    counter++

continue

let numbers = [1, 2, 3, 4, 5]

for number in numbers
    if number == 3
        continue

    print number

Arrays

Arrays use familiar JavaScript syntax and methods.

let numbers = [10, 20, 30]

print numbers[0]
print numbers.length

numbers.push(40)

print numbers

Objects

Object literals keep JavaScript's curly braces because the braces describe the object itself, not a code block.

let user = { name: "Bob", age: 25 }

print user.name
print user.age

Object spread

let user = { name: "Bob", age: 25 }

let updatedUser = { ...user, age: 30 }

print updatedUser.age

Destructuring

Object destructuring

Curly braces are required because they identify the object properties being extracted.

let user = { name: "Bob", age: 25 }

let { name: userName, age: userAge } = user

print userName
print userAge

Array destructuring

let numbers = [10, 20, 30]

let [first, second, third] = numbers

print first
print second
print third

Modern JavaScript expressions

Humal keeps JavaScript expressions familiar, so modern JavaScript operators and expressions can be used directly when supported by the generated target.

Optional chaining

let user = { name: "Bob" }

print user?.name
print user?.profile?.name

The second expression safely produces undefined when profile does not exist.

Nullish coalescing

let name = null

print name ?? "Unknown"

Spread syntax

let numbers = [10, 20, 30]
let moreNumbers = [...numbers, 40, 50]

print moreNumbers.length

Async

Humal supports async fn, await and return for asynchronous JavaScript code.

async fn getValue()
    let value = await Promise.resolve(42)
    return value

async fn runAsync()
    let value = await getValue()
    print value

runAsync()

Error handling

Humal supports try, catch and throw using indentation for the blocks.

try
    throw new Error("Boom")

catch
    print "Error handled"

The current compiler and TypeScript validation should be considered when working with caught error values. The documented example intentionally uses a catch block without an error binding.

Modules

Humal is designed to work with both ESM and CommonJS modules and with existing Node.js packages.

CommonJS

let crypto = require("crypto")

let hash = crypto.createHash("sha256")
hash.update("Hello World!")

let result = hash.digest("hex")

print result

ESM import

import path from "path"

print path.basename("/tmp/hello.txt")

Named ESM import

import { readFileSync } from "fs"

let data = readFileSync("package.json", "utf8")

print data

Namespace import

import * as path from "path"

print path.basename("/tmp/hello.txt")

JavaScript interoperability

Humal compiles to JavaScript, so Node.js APIs and JavaScript libraries can be used from Humal projects.

Node.js crypto

let crypto = require("crypto")

let hash = crypto.createHash("sha256")
hash.update("Hello World!")

let result = hash.digest("hex")

print result

Node.js fs

let fs = require("fs")

fs.writeFileSync("hello.txt", "Hello from Humal!")

print "File written!"

Node.js os

let os = require("os")

print os.platform()
print os.arch()
print os.homedir()

Express

let express = require("express")

let app = express()

fn home(req, res)
    res.send("Hello from Humal!")

app.get("/", home)

app.listen(3000)

print "Server running on port 3000"

Test the server from another terminal:

curl http://localhost:3000

CLI

Humal includes a project-oriented CLI for creating, building, checking and developing Humal projects.

Initialize a project

humal init my-project

Build

humal build

Development mode

humal dev

Watch mode

humal watch

Check

humal check

Format

humal fmt

Configuration

Humal projects can use humal.config.json to define the entry file, source directory and module format.

{
  "entry": "src/main.hum",
  "sourceDir": "src",
  "module": "esm"
}

The module can be configured as esm, cjs or left unset for automatic behavior.

Formatter

Use humal fmt to normalize Humal source formatting.

humal fmt

The current formatter focuses on source normalization such as line endings, trailing whitespace and the final newline.

Tooling

Humal includes development tooling around the compiler and integrates with the JavaScript ecosystem.

VS Code

The Humal VS Code extension provides support for .hum files, including syntax highlighting and editor assistance.

Validation

Humal can validate generated JavaScript with the project's TypeScript and ESLint tooling through humal check and related compiler workflows.

Source maps

Humal provides source-map support so generated JavaScript can remain linked to the original Humal source. This is useful for development and debugging.

Source-map support is part of the compiler/tooling layer rather than a separate Humal language construct.

Humal 1.4.x · Browser development

Humal Web

Build browser-based applications and websites using Humal’s indentation-based syntax. Humal Web provides a browser runtime, DOM manipulation utilities, access to browser APIs, and integration with Parcel.

Overview

Humal Web is a convenience layer over browser functionality. It does not replace the browser or implement every browser API itself.

Quick start

The following instructions use the browser project template included in the Humal CLI release that supports --env browser.

1. Create a browser project

humal init myapp --env browser
cd myapp
npm install

The initialization command creates a project configuration, a Humal entry file, an HTML entry point, a Parcel configuration and npm scripts.

2. Start development

npm run dev:web

Open the local URL printed by Parcel in your terminal. The default development address is commonly http://localhost:1234/.

3. Create a production build

npm run build:web

Parcel writes the generated production assets to its output directory, normally dist/.

Project configuration

The humal.config.json file controls the project's entry file, source directory, module format and environment.

{
  "entry": "src/main.hum",
  "sourceDir": "src",
  "module": "esm",
  "env": "browser",
  "webEntry": "index.html"
}
  • entry — the main Humal source file.
  • sourceDir — the directory containing Humal source files.
  • module — the JavaScript module format.
  • env — selects browser or node tooling behavior.
  • webEntry — the HTML entry point used by Parcel.

Use "env": "browser" for a browser application. It enables browser-specific tooling behavior, including the web API type declarations when supported by the installed Humal release.

The env setting does not create a browser by itself. The browser runtime must still be loaded by the compiled application.

HTML entry point

The HTML page is the entry point for the browser application. It defines the elements that your Humal code will interact with.

A typical generated index.html looks like this:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Humal Web</title>
</head>
<body>
  <h1>Humal Web</h1>
  <label for="name">Name</label>
  <input id="name">
  <button id="save">Save</button>
  <p id="status">Ready</p>

  <script type="module" src="./src/main.hum"></script>
</body>
</html>

The src path is relative to the HTML file. If your HTML file is in another directory, adjust the path accordingly.

Your first Humal Web program

Create or edit src/main.hum:

web.document.title = "Humal Web"

web.text("#status", "Ready")

fn saveName()
    let name = web.value("#name")
    web.localstorage.setItem("name", name)
    web.text("#status", "Saved: " + name)

web.value("#name", web.localstorage.getItem("name") ?? "")

web.button("#save").onClick(saveName)

This example updates the page title, reads the input value, stores the name in Local Storage and updates the status message when the button is clicked.

DOM helpers

The web namespace provides helpers for common DOM operations. A target can be a CSS selector or, where supported, an existing DOM element.

Set text

web.text("#status", "Hello from Humal")

web.text() assigns text content to the target element. The value is converted to a string.

Read and set input values

let name = web.value("#name")
web.value("#name", "Alex")

Pass one argument to read an element's value, or two arguments to set it. Reading a value is most useful with form controls.

Access an element

let status = web.element("#status")

If a selector does not match an element, the runtime reports an error instead of silently returning a missing element.

Event handling

Use Humal functions as browser event callbacks.

Button events

fn handleClick()
    web.text("#status", "Button clicked")

web.button("#save").onClick(handleClick)

The button helper also supports .text(value) and .disable(flag).

General event listeners

fn handleChange(event)
    web.text("#status", event.type)

web.on("#name", "input", handleChange)

Remove an event listener with web.off(). Pass the same callback function used when the listener was registered.

web.off("#name", "input", handleChange)

Wait for the DOM

fn initialize()
    web.text("#status", "Page is ready")

web.ready(initialize)

Browser APIs

The runtime exposes supported browser properties and functions through the web namespace. The document and window properties provide direct access to browser objects.

web.document.title = "My Application"

let currentURL = web.location.href
let language = web.navigator.language

let identifier = web.crypto.randomUUID()

web.alert("Hello from Humal")

Browser API methods and properties follow the behavior of the underlying browser API. Permissions, security restrictions and browser support continue to apply.

Selected API aliases

The runtime provides lowercase aliases for several browser constructors and objects:

web.localstorage
web.sessionstorage
web.indexeddb
web.urlsearchparams
web.abortcontroller
web.intersectionobserver
web.mutationobserver
web.resizeobserver
web.websocket
web.sharedworker
web.broadcastchannel
web.textencoder
web.textdecoder
web.formdata
web.filereader

For example, create URL search parameters or encode a string:

let params = new web.urlsearchparams("?name=Humal")
let name = params.get("name")

let encoder = new web.textencoder()
let bytes = encoder.encode("Hello Humal")

print name
print bytes.length

A constructor alias can be used with new. Check the browser's support before relying on a particular API.

Browser storage

Humal Web exposes Local Storage, Session Storage and IndexedDB through lowercase aliases.

Local Storage

web.localstorage.setItem("theme", "dark")

let theme = web.localstorage.getItem("theme")

if theme == null
    web.localstorage.setItem("theme", "light")

Local Storage keeps string values across browser sessions for the same origin. Use JSON.stringify() and JSON.parse() when storing structured data.

Session Storage

web.sessionstorage.setItem("step", "2")

let step = web.sessionstorage.getItem("step")

Session Storage is intended for data associated with the current browser tab's session.

IndexedDB

let database = web.indexeddb

IndexedDB is a browser database API. Opening a database, creating object stores and performing transactions follow the standard IndexedDB interface and its asynchronous event model.

Do not store passwords, secret keys or other sensitive credentials in client-side storage.

Async code and network requests

Humal supports async fn and await where supported by the compiler. These constructs let browser programs work with asynchronous JavaScript APIs.

async fn loadData()
    let response = await web.fetch("/api/data")

    if !response.ok
        throw new Error("Request failed")

    let data = await response.json()
    web.text("#status", JSON.stringify(data))

web.button("#load").onClick(loadData)

A Fetch request generally rejects for network errors, but an HTTP response with a status such as 404 or 500 does not normally reject automatically. Check response.ok or response.status.

Handle network failures with try and catch:

async fn loadData()
    try
        let response = await web.fetch("/api/data")

        if !response.ok
            throw new Error("Request failed")

        let data = await response.json()
        web.text("#status", JSON.stringify(data))
    catch
        web.text("#status", "Unable to load data")

Browser input

Humal's terminal-oriented input feature normally reads from the terminal using readline-sync. In a browser build, the Web integration can replace this input mechanism with the browser's prompt() dialog.

let name = input "What is your name? "

web.text("#status", "Hello " + name)

Browser input is interactive and blocks while the prompt is open. For normal web forms, prefer HTML input elements and web.value().

Parcel integration

Parcel bundles the HTML entry point and its dependencies. Humal's Parcel transformer compiles Humal source files into JavaScript and connects them to the Web runtime.

When using the browser project template generated by a compatible Humal CLI release, Parcel configuration and npm scripts are created for you.

Manual configuration

For an existing project, install Humal and Parcel as development dependencies:

npm install --save-dev parcel @qvelop444/humal

Create a .parcelrc file in the project root:

{
  "extends": "@parcel/config-default",
  "transformers": {
    "*.hum": [
      "./node_modules/@qvelop444/humal/src/parcel.js"
    ]
  }
}

The relative path points to the transformer inside the installed Humal package. This is different from naming it as a scoped Parcel plugin package.

Your HTML entry point must reference a Humal source file, and the project must have the relevant Parcel commands configured.

Production builds

Use the browser build command to create production assets:

humal build:web

Or, if the project's npm scripts have been generated, run:

npm run build:web

Review the generated files before deployment. Do not put server credentials or private keys into browser code: assets served to users can be inspected.

Troubleshooting

“Parcel is not installed in this project”

Run npm install from the project directory. The browser CLI uses the project's local Parcel installation.

“Browser entry file not found”

Check webEntry in humal.config.json and ensure the HTML file exists at the configured path.

“Cannot find module” for the Parcel transformer

Check that Humal is installed in the project and that .parcelrc uses the correct relative path to node_modules/@qvelop444/humal/src/parcel.js. Then run npm install again.

“Cannot find name 'web'” during checking

Confirm that the project's environment is set to "env": "browser" and that the installed Humal package contains types/humal-web.d.ts. The declaration file provides type information; it does not create the runtime object.

Humal and JavaScript

Humal changes the parts of JavaScript syntax that add unnecessary visual noise, especially block braces and semicolons, while keeping JavaScript expressions, APIs and ecosystem compatibility familiar.

When a construct is meaningful JavaScript syntax, such as an object literal, its braces remain part of the expression.

View Humal on GitHub