Feather Documentation
This page covers every part of Feather, from basic output and variables to classes, HTTP requests, and building web applications. Use the sidebar to jump to any topic.
Installation
Choose your platform below to install Feather. Once installed, the feather command will be available in your terminal.
curl https://codeberg.org/Feather_lang/Feather/raw/branch/dev/install.sh | bash
cd %LOCALAPPDATA%
git clone https://codeberg.org/Feather_lang/Feather
cd Feather
make build
cd Feather\Interpreter
setx PATH "%PATH%;%LOCALAPPDATA%\Feather\Interpreter"
Usage
Run any .feather file using the Feather CLI:
feather <script.feather>
Output
Use OUT to print a value. By default it does not add a newline.
OUT "Hello, World!"
Add &wrap (or &w) at the end to print with a newline:
OUT "Hello" &wrap
OUT "World" &wrap
Use WRAP on its own line to print a blank newline:
OUT "Hello"
WRAP
OUT "World"
Variables
Variables store text values. Declare and assign them with VARIABLE (shorthand: v).
VARIABLE name = "Alice"
OUT name
You can reassign a variable at any time:
VARIABLE color = "red"
VARIABLE color = "blue"
OUT color
String Interpolation
Embed a variable or expression inside a string using {...}:
VARIABLE name = "Alice"
OUT "Hello, {name}!"
Global Variables
To make variables accessible in every scope, prefix the declaration with GLOBAL:
GLOBAL VARIABLE name = "Alice"
String Literals
Feather has two kinds of string literals: regular strings and raw strings.
Regular Strings
Written with double or single quotes. Supports {...} interpolation and escape sequences.
VARIABLE item = "apple"
OUT "I have an {item}"
| Sequence | Meaning |
|---|---|
\n | Newline |
\t | Tab |
\\ | Backslash |
Raw Strings (backticks)
Written using backticks. Raw strings do not support interpolation. Useful for JSON, templates, and literal content.
VARIABLE greeting = "Hello, world!"
OUT `Greeting: {greeting}`
^ Outputs 'Greeting: {greeting}' NOT 'Greeting: Hello, world!'
String Methods
String methods are accessed with dot notation on any string variable.
| Method / Property | Description |
|---|---|
.length() / .size() | Number of characters |
.upper | Uppercase version |
.lower | Lowercase version |
.scrape | Trim surrounding whitespace and control characters |
.condense | Remove all whitespace from the string |
.find(sub) | Returns the index of first occurrence of sub. Returns -1 if not found |
Counters
Counters are integer variables. Declare them with COUNTER (shorthand: c).
COUNTER score = 0
OUT score
Quickly increment or decrement with + / -:
COUNTER score = 0
+ score
+ score
- score
OUT score
^ Outputs: 1
Global Counters
GLOBAL COUNTER total = 0
Compound Assignment
All assignment shorthands work on both variables and counters:
COUNTER n = 10
n += 5
n -= 2
n *= 3
n /= 2
OUT n
VARIABLE text = "hello"
text += " world"
OUT text
^ Outputs: hello world
Counter Properties
| Property | Description |
|---|---|
.zero? | "true" if the counter is 0 |
.length | Number of digits |
COUNTER x = 0
OUT x.zero? &wrap
+ x
OUT x.zero? &wrap
Operators & Expressions
Feather supports arithmetic, comparison, and logical operators anywhere an expression is expected.
Arithmetic
| Operator | Description |
|---|---|
+ | Addition |
- | Subtraction |
* | Multiplication |
/ | Division |
% | Modulo |
COUNTER result = 3 + 4 * 2
OUT result
^ Outputs: 11
COUNTER result = (3 + 4) * 2
OUT result
^ Outputs: 14
Comparison
| Operator | Description |
|---|---|
= | Equal |
!= | Not equal |
< | Less than |
> | Greater than |
<= | Less than or equal |
>= | Greater than or equal |
Logical
| Operator | Description |
|---|---|
AND / && | Logical AND |
OR / || | Logical OR |
NOT / ! | Logical NOT |
Taking User Input
Get user input with the .take method. The string it's called on is the prompt shown to the user.
VARIABLE name = "What's your name? ".take
WRAP
COUNTER age = "How old are you? ".take
WRAP
OUT "My name is {name}! I am {age} year(s) old!" &wrap
TAKE requires the user to press Enter. For single-keystroke input without waiting for Enter, use KEYPRESS:
KEYPRESS -> key
OUT "You pressed: {key}" &wrap
Control Flow
Run a block of code only when a condition is true. The body is indented under the IF line.
VARIABLE score = "85"
IF score >= 80:
OUT "Pass" &wrap
IF / ELSE
VARIABLE score = "55"
IF score >= 60:
OUT "Pass" &wrap
ELSE:
OUT "Fail" &wrap
ELSE IF
VARIABLE score = "72"
IF score >= 90:
OUT "A" &wrap
ELSE IF score >= 80:
OUT "B" &wrap
ELSE IF score >= 70:
OUT "C" &wrap
ELSE:
OUT "D" &wrap
Combining Conditions
VARIABLE age = "25"
VARIABLE member = "true"
IF age >= 18 AND member == "true":
OUT "Welcome!" &wrap
Using Boolean Properties
COUNTER score = 0
IF score.zero?:
OUT "You lose!"
Loops
WHILE
Repeats a block as long as a condition is true.
COUNTER i = 0
WHILE i < 5:
OUT "{i} " &wrap
i += 1
Use EXIT to break out of a loop early:
COUNTER i = 0
WHILE i < 100:
IF i = 3:
EXIT
OUT "{i} " &wrap
i += 1
REPEAT
Repeats a block a fixed number of times.
REPEAT 5:
OUT "hello" &wrap
Track the current iteration count with AS:
REPEAT 3 AS i:
OUT "Iteration {i}" &wrap
Lists
Lists are ordered collections of values, similar to arrays. Declare them with LIST (shorthand: l).
LIST fruits = ("apple", "banana", "cherry")
^ Multi-line form:
LIST fruits = (
"apple",
"banana",
"cherry"
)
Access items by index (starts at 0), print the whole list, or get its length:
OUT fruits[0] &wrap
OUT fruits[2] &wrap
OUT fruits ^ print entire list
OUT fruits.length &wrap
Assign to an index:
fruits[1] = "blueberry"
OUT fruits[1] &wrap
Empty List
LIST items
Nested Lists
LIST matrix = (("1", "2"), ("3", "4"))
OUT matrix[0][1] &wrap
^ Outputs: 2
Global Lists
GLOBAL LIST shared = ("a", "b", "c")
FOR Loop
LIST colors = ("red", "green", "blue")
FOR color IN colors:
OUT color &wrap
Count iterations with AS:
LIST colors = ("red", "green", "blue")
FOR color IN colors AS reps:
OUT reps &wrap
List Methods
Called with dot notation on any list variable.
| Method / Property | Description |
|---|---|
.length() / .size() | Returns number of elements |
.empty? | Returns "true" if the list has no elements |
.append(val) | Add val to the end |
.pop() | Remove and return the last element |
.find(val) | Returns the index of the first instance of val, or -1 |
.sort("+") | Sort ascending. Use "-" for descending |
.reverse() | Reverse in place (modifies list, no return) |
.join(sep) | Join all elements into one string separated by sep |
.slice(start, end) | Slice from start to end (inclusive), modifies list |
.static | Parse Feather list to JSON |
.contains(query) | Returns 'true' or 'false' if the list contains the query |
Tables
Tables are key-value stores identical to JSON dicts. Declare them with TABLE (shorthand: t).
TABLE person = {
"name": "Alice",
"age": "30",
"city": "New York"
}
Access values, assign to keys, and get the entry count:
OUT person["name"] &wrap
OUT person["age"] &wrap
person["age"] = "31"
OUT person.length() &wrap
Nested Tables
TABLE config = {
"server": {
"host": "localhost",
"port": "8080"
},
"debug": "false"
}
OUT config["server"]["host"] &wrap
Tables with List Values
TABLE data = {
"colors": ("red", "green", "blue"),
"count": "3"
}
FOR i IN data["colors"]:
OUT i &wrap
Global Tables
GLOBAL TABLE settings = {"debug": "false"}
FOREACH loop
TABLE scores = {"Alice": "95", "Bob": "87", "Carol": "92"}
FOREACH name, score IN scores:
OUT "{name}: {score}" &wrap
Count iterations with AS:
FOREACH name, score IN scores AS reps:
OUT "{name}: {score} | {reps}" &wrap
Table Methods
All table methods use dot notation.
| Method / Property | Description |
|---|---|
.size() / .length() | Number of key-value pairs |
.empty? | "true" if the table has no entries |
.set(key, val) | Set or update key to val |
.add(key, val) | Same as .set, add or update a key |
.drop(key) | Remove the entry with key |
.has(key) | Returns "true" if key exists |
.keys | Returns a list of all keys |
.values | Returns a list of all values |
.merge(other) | Merge another table in, overwriting duplicate keys |
.rename(old, new) | Rename a key |
.clear() | Removes all entries |
.copy | Return a shallow copy of the table |
.static | Serialize the table to a JSON string |
TABLE inventory = {"apples": "10", "bananas": "5"}
OUT inventory.has("bananas") &wrap ^ true
inventory.drop("bananas")
OUT inventory.size &wrap ^ 1
inventory.set("oranges", "20")
OUT inventory["oranges"] &wrap ^ 20
.keys and .values
TABLE prices = {"apple": "1", "banana": "2", "cherry": "3"}
LIST k = prices.keys
LIST v = prices.values
JSON Serialization
^ Table to JSON string:
VARIABLE json = myTable.static
^ JSON string to Table:
TABLE parsed = jsonString.dynamic()
Functions (Programs)
Define functions with PROGRAM. Call them with CALL. Return a value with RETURN.
Basic Function
PROGRAM greet:
OUT "Hello from greet!" &wrap
END
CALL greet
Function with Parameters
PROGRAM add(a, b):
VARIABLE result = a + b
RETURN result
END
OUT add(10, 5) &wrap
^ Outputs: 15
Inline Call in Expressions
Use funcname(args) directly inside {...} to embed a call inline:
PROGRAM square(x):
RETURN x * x
END
OUT "Square of 5 is: {square(5)}" &wrap
Returning a List
PROGRAM make_list:
RETURN ("x", "y", "z")
END
LIST result = make_list()
OUT result &wrap
Returning a Table
PROGRAM make_user(n, a):
RETURN {"name": n, "age": a}
END
TABLE user = make_user("Bob", "25")
OUT user["name"] &wrap
Scoping
Programs have their own scopes. Variables defined outside are not accessible inside unless declared as GLOBAL:
GLOBAL VARIABLE name = "Alice"
PROGRAM greet():
OUT "Hi, {name}" &wrap
END
CALL greet
HTTP Requests
Make HTTP requests with the REQUEST command. The result is stored as a table with response and status keys.
REQUEST myReq {
"url": "https://api.example.com/users",
"method": "GET"
}
OUT myReq["status"] &wrap
OUT myReq["response"] &wrap
POST with a JSON Body
TABLE credentials = {
"username": "alice",
"password": "secret"
}
VARIABLE body = credentials.static
REQUEST loginReq {
"url": "https://api.example.com/login",
"method": "POST",
"content-type": "application/json",
"body": body
}
OUT loginReq["status"] &wrap
Parse a JSON Response
REQUEST dataReq {
"url": "https://api.example.com/data",
"method": "GET"
}
VARIABLE rawJson = dataReq["response"]
TABLE data = rawJson.dynamic()
OUT data["key"] &wrap
Custom Headers
REQUEST authReq {
"url": "https://api.example.com/protected",
"method": "GET",
"header": "my-token-here"
}
OUT authReq["status"] &wrap
Classes
Classes define a reusable blueprint for object-oriented programming. Data stored in classes uses the class's own scope, not the global scope.
CLASS Animal:
VARIABLE name = "unknown"
VARIABLE sound = "..."
VARIABLE cat = Animal.new
VARIABLE cat.name = "Cat"
VARIABLE cat.sound = "Meow"
OUT cat.name &wrap
OUT cat.sound &wrap
Methods in Classes
Classes can contain programs that act as methods. Programs inside classes cannot see class-level variables directly, you can use RELATIVE for shared scope:
CLASS Counter:
RELATIVE COUNTER count = 0
PROGRAM increment:
+ count
END
PROGRAM reset:
RELATIVE COUNTER count = 0
END
VARIABLE timer = Counter.new
CALL timer.increment
CALL timer.increment
CALL timer.increment
OUT timer.count &wrap
^ Outputs: 3
CALL timer.reset
OUT timer.count &wrap
^ Outputs: 0
Libraries
Import libraries from the libs folder with IMPORT.
IMPORT 'utils.feather'
'utils' is not part of the standard library, this example assumes you have created a file called utils.feather.Custom Methods
In addition to built-in methods, you can define your own. Methods are similar to programs but attached to a data type. self holds the value being operated on; self.type holds its type name.
VARIABLE:METHOD method_name:
^ ... put code here ...
RETURN self
END
Accept multiple types by separating them with commas:
VARIABLE, COUNTER:METHOD name:
^ ...
RETURN self.type
END
Methods with Parameters
VARIABLE:METHOD multiply(amount):
RETURN self * amount
END
OUT `hello`.multiply(3)
^ Outputs: hellohellohello
Methods in Imported Files
^ Methods in separate files work normally after import.
^ If the file only contains methods, no alias is needed:
IMPORT 'methods.feather'
Using Ruby in Feather
Feather lets you embed and run Ruby code directly. The last expression in the Ruby block is the return value.
RUBY ():
puts "Hello, world!"
200
END
Passing Variables and Getting Output
VARIABLE input_example = "Hello, world!"
RUBY (input_example):
puts input_example
"Done executing!"
END => output_var
OUT output_var
^ Outputs: Done executing!
RUBY ("Hello"). Ruby may take a moment on first run if it needs to initialize a fresh instance.Sending Commands to the System
Run system shell commands with SHELL. Use a raw string (backticks) for the command to avoid symbol conflicts.
SHELL `ls -la` -> output_var
OUT output_var &wrap
Miscellaneous Commands
COLOR
Set the terminal text color. Available colors: BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, bold variants (BOLDRED, etc.), and RESET.
COLOR RED
OUT "This is red"
COLOR RESET
OUT "Normal again"
DEBUG
Print each line before it executes, useful for tracing bugs.
DEBUG true
OUT "This line will be traced"
DEBUG false
SAVE
Save the current script state to a file.
SAVE "output.feather"
FORCE_CLOSE
Immediately terminate the interpreter.
FORCE_CLOSE
DISABLE
Disable a command so it does nothing when encountered.
DISABLE $OUT
!IGNOREWARNINGS
Suppress all runtime warnings and errors.
!IGNOREWARNINGS
Interacting with Files
Before doing anything with a file, open it with open(file). Files use lists and each index is one line.
Writing to Files
LIST conts = ("line1", "line2")
CALL open('example.txt').write(conts)
^ Write a single line directly:
CALL open('example.txt').write('put content here')
Reading Files
LIST file_data = open('example.txt').read
OUT file_data
^ Or in-line:
OUT open('example.txt').read
Deleting Files
CALL open('example.txt').delete
Creating Web Applications
Defining Routes
Each route is defined with SERVER. You must always specify the allowed HTTP methods.
^ GET only:
SERVER ('/', ('GET')):
OUT "Got a GET request!" &wrap
RETURN "Hello from Feather"
END
^ GET and POST:
SERVER ('/', ('GET', 'POST')):
RETURN "Handled"
END
Request Object
| Key | Contents |
|---|---|
request.method | HTTP method (GET, POST, …) |
request.path | Request path |
request.body | Raw request body |
request.header | Request headers |
Starting the Server
SERVER ('/', ('GET')):
RETURN "Hello"
END
SERVER.start()
Serving External Files
Return a filename string to serve an HTML file directly:
SERVER ('/', ('GET')):
RETURN 'index.html'
END
SERVER.start()
Multiple Routes
SERVER ('/', ('GET')):
RETURN 'main'
END
SERVER ('/about/', ('GET')):
RETURN 'about'
END
SERVER.start()
Full Example
^ app.feather
SERVER ('/', ('GET')):
RETURN 'index.html'
END
SERVER.start()
Comments
Use
^to write a comment. The entire line is ignored by the interpreter. Comments must be on their own line.