Quo is a small, interpreted, embeddable, dynamically typed programming language with a tiny header-only C implementation.
Warning
Quo is Work-in-Progress. API is subject to change.
Because Quo is tiny, it builds very fast. About 0.5 seconds for standard build and 3 seconds for release build if included all standard library modules.
To build quo CLI run:
# Standard build
./build.sh -b
# O3 optimization release build
./build.sh -b release
# Debug build with logs enabled
./build.sh -b debugQuo files use the .quo extension.
To run .quo scripts, use the quo command-line tool.
Run script:
$ quo script.quoprint("Hello, World!")Quo doesn't have a class type, but classes can be easily created using functions as constructors which return a dictionary.
Think of the returned dict as self, and because functions defined in dictionaries recieve this dictionary as first argument it's all works out.
We can implement inheritance by creating explicit inherit(super_class) function.
# Create a new "class" with the given name
var class = fn(name) {
return {
"name": name,
# Create function to inherit fields from other "classes"
"inherit": fn(self, super) {
# Get all keys from super "class" and set them on self
if super and type(super) == "dict" {
var keys = super.keys()
loop (var i = 0, i < keys.len(), i += 1) {
var key = keys.get(i)
# Do not set keys that already exist on self
if self.has(key) continue
self.set(key, super.get(key))
}
}
}
}
}
# Create Animal class and add speak() method
var animal_class = fn() {
var self = class("Animal")
self.set("speak", fn(self, text) { print(self.name + " says \"" + text + "\"") })
return self
}
# Create class instance
var cat = class("Cat")
# Inherit methods from Animal class
cat.inherit(animal_class())
cat.speak("meow") # Cat says "meow"# This is a line comment| Keyword | Description |
|---|---|
var |
Variable declaration |
fn |
Function declaration |
if |
Conditional statement |
else |
Conditional statement |
loop |
Loop statement |
break |
Loop control statement |
continue |
Loop control statement |
return |
Function return statement |
nil |
Nil literal |
true |
Boolean literal |
false |
Boolean literal |
| Type | Description | Literal |
|---|---|---|
nil |
No value | nil |
bool |
Boolean | true, false |
num |
Integer or floating-point number | 42, 3.14, 10_000_000 |
str |
String | "hello" |
arr |
Dynamic array | [1, 2.3, "4"] |
dict |
Key-value dictionary | {"key": 69, "foo": "hello"} |
Variables are declared using the var keyword.
var foo = 69Because Quo is dynamically typed, the type of a variable can change.
var foo = 69
foo = "hello"Function definitions are expressions that are assigned to variables using the fn keyword.
var add = fn(a, b) {
return a + b
}Variables are scoped to the block they are declared in, surrounded by {}.
var foo = 69 # Global variable
{
var bar = 42 # Local variable
var foo = 420 # Shadowing of global variable `foo`
}Quo has if, else, else if keywords for control flow.
var foo = 69
if foo < 42 {
println("foo is less than 42")
} else if foo > 50 {
println("foo is greater than 50")
} else {
println("foo is between 42 and 50")
}Quo has only one loop construct loop.
It works like classic C for loop.
loop (var i = 0, i < 10, i += 1) {
println(i)
}Infinite loop example:
loop (,true,) {
println("infinite loop")
}Loop also has break and continue keywords for early termination and skipping iterations.
loop (var i = 0, i < 10, i += 1) {
if i == 5 continue
if i == 7 break
println(i) # 0 1 2 3 4 6 8 9
}Quo supports the following operators:
- Arithmetic:
+,-,*,/,%var a = 69 + 42 - 10 * 2 / 3 % 2 var s = "Hello" + "World" # "HelloWorld" var t = "foo" * 3 # "foofoofoo"
- Grouping:
()var a = (69 + 42) - 10 * 2 / 3 % 2
- Assignment:
+=,-=,*=,/=,%=var a = 69 a += 42 # a = a + 42 a -= 10 # a = a - 10 a *= 2 # a = a * 2 a /= 3 # a = a / 3 a %= 2 # a = a % 2
- Comparison:
==,!=,<,>,<=,>=var a = 69 var b = 42 if a == b { println("a is equal to b") } else if a > b { println("a is greater than b") } else { println("a is less than b") }
- Logical and:
andvar a = true var b = false if a and b { println("a and b are both true") } else { println("a and b are not both true") }
- Logical or:
orvar a = true var b = false if a or b { println("a or b are true") } else { println("a and b are not both true") }
- Logical not:
!var a = true if !a { println("a is false") } else { println("a is true") }
import(path): Imports a module from the given path. Returns the module object.print(value, ...): Prints the values to the console, separated by spaces, adding new line at the end.type(value): Get the type of the variable as string.input(value, ...): Get the user input as string, printing optional prompt.
Methods are functions that are associated with a type and can be called on a value of that type.
They are accessed using the dot notation: value.method().
str:len(): Returns the length of the UTF-8 string.get(n): Get the n-th character of the string.contains(s): Returnstrueif the string contains the substrings.strip(): Returns a new string with leading and trailing whitespace removed.replace(old, new): Returns a new string with all occurrences ofoldreplaced bynew.split(sep): Returns an array of strings split by the separatorsep.startswith(s): Returnstrueif the string starts withs.endswith(s): Returnstrueif the string ends withs.
dict:len(): Returns the number of key-value pairs in the dictionary.get(key): Returns the value associated withkeyin the dictionary.set(key, value): Sets the value associated withkeyin the dictionary.values(): Returns an array of all values in the dictionary.keys(): Returns an array of all keys in the dictionary.
arr:len(): Returns the length of the array.get(n): Get the n-th element of the array.set(n, value): Sets the n-th element of the array tovalue.push(value): Appendsvalueto the end of the array.pop(): Removes and returns the last element of the array.
quo CLI has multiple built-in modules.
They're source is in the include directory with quo-mod-*.h names.
They are can be selectively disabled when Embedding in your own code.
All functions are implemented in C so they are fast and efficient.
Example usage:
var b64 = import("base64")
var encoded_string = b64.encode("Hello, World!")
print(encoded_string) # SGVsbG8sIFdvcmxkIQ==Modules and methods:
-
base64: Encodes and decodes strings using base64.encode(s): Returns the base64 encoding ofs.encode_url(s): Returns the base64 encoding ofsin URL-safe format.decode(s): Returns the decoded string ofs.decode_url(s): Returns the decoded string ofsfrom URL-safe format.
-
csv: Parses and generates CSV files.parse(s): Returns an array of rows parsed from strings.parse_dict(s): Returns a dictionary with headers parsed from strings.stringify(rows): Stringify array of arrays to CSVstringify_dict(dict): Stringify array of dictionaries to CSV
-
env: Access environment variables.get(key): Returns the value of the environment variablekey.set(key, value): Sets the value of the environment variablekeytovalue.unset(key): Removes the environment variablekey.all(): Returns a dictionary of all environment variables.has(key): Returnstrueif the environment variablekeyexists,falseotherwise.
-
dl: Dynamic loading of C libraries.open(libname): Loads the C librarylibnameand returns aQuoDLHandle.QuoDLHandle: A handle to a loaded C library.sym(name): Returns theQuoDLSymsymbol from the library.call(sym, args): Calls theQuoDLSymsymbol withargsand returns the result.close(): Closes the handle and unloads the library.
-
json: Encodes and decodes JSON strings.decode(s): Decodes JSON string and returnsdict.encode(obj): Returns the JSON string fromdict.
-
time: Time-related functions.now(): Returns the current time as anum.clock(): Returns the current clock time as anum.sleep(seconds): Sleeps forsecondsseconds.
-
os: Operating system functions.system(command): Executes thecommandin the operating system shell.name(): Returns the name of the operating system.
-
net: Network functions.get(url): Sends a GET request tourland returns the response.post(url, data): Sends a POST request tourlwithdataand returns the response.put(url, data): Sends a PUT request tourlwithdataand returns the response.patch(url, data): Sends a PATCH request tourlwithdataand returns the response.delete(url): Sends a DELETE request tourland returns the response.request(url, method, data, headers): Sends a custom request tourlwithmethod,data, andheadersdict and returns the response.encode(s): URL encodes the stringsand returns the result.decode(s): URL decodes the stringsand returns the result.
-
fs: File system functions.open(path): Opens a file atpathand returns aQuoFSFileobject.QuoFSFile: A file object that can be used to read and write to a file.read(): Reads the contents of the file and returns it as a string.read_lines(): Reads the contents of the file and returns it as a array of strings.write(data): Writesdatastring to the file.
exists(path): Returnstrueif the file atpathexists,falseotherwise.stat(path): Returns the stat information of the file atpath.ls(path): Returns a list of files in the directory atpath.mkdir(path): Creates a directory atpath.rm(path): Removes the file atpath.rmdir(path): Removes the directory atpath.rename(old_path, new_path): Renames the file atold_pathtonew_path.cp(src_path, dst_path): Copies the file atsrc_pathtodst_path.cwd(): Returns the current working directory.cd(path): Changes the current working directory topath.get_tmp_dir(): Returns the path of the temporary directory.
-
math: Math library- Constants:
pi: Pi (π)e: Euler's number (e)tau: Tau (2π)
- Functions:
floor(num): Returns the largest integer less than or equal tonum.ceil(num): Returns the smallest integer greater than or equal tonum.round(num): Returns the nearest integer tonum.trunc(num): Returns the integer part ofnum.abs(num): Returns the absolute value ofnum.sqrt(num): Returns the square root ofnum.cbrt(num): Returns the cube root ofnum.pow(base, exp): Returnsbaseraised to the power ofexp.exp(num): Returns e raised to the power ofnum.log(num): Returns the natural logarithm ofnum.log2(num): Returns the base-2 logarithm ofnum.log10(num): Returns the base-10 logarithm ofnum.sin(num): Returns the sine ofnum.cos(num): Returns the cosine ofnum.tan(num): Returns the tangent ofnum.asin(num): Returns the arcsine ofnum.acos(num): Returns the arccosine ofnum.atan(num): Returns the arctangent ofnum.atan2(y, x): Returns the arctangent ofy/x.sinh(num): Returns the hyperbolic sine ofnum.cosh(num): Returns the hyperbolic cosine ofnum.tanh(num): Returns the hyperbolic tangent ofnum.min(a, b): Returns the smaller ofaandb.max(a, b): Returns the larger ofaandb.clamp(num, min, max): Returnsnumclamped to the rangemintomax.random(max): Returns a random number between 0 andmax.random_float(max): Returns a random floating-point number between 0 andmax.deg_to_rad(num): Convertsnumfrom degrees to radians.rad_to_deg(num): Convertsnumfrom radians to degrees.
- Constants:
-
uuid: UUID functions.v4(): Generates a random UUID v4.v7(): Generates a UUID v7 (time-ordered).parse(str): Parses a UUID string and returns a dictionary withvalid,version, andvariantfields.is_valid(str): Checks if a string is a valid UUID.
Quo is written in header-only C, so embedding it in your own code is easy.
Just copy the headers from the include directory to your project.
Quo can be used as scripting language for your game, configuration language for your program etc.
The main file is quo.h, it contains:
- Lexer/Parser
- Bytecode Compiler
- Virtual Machine
- All of the embedding functions
Quo modules are quo-mod-*.h files. Modules can be excluded if not needed.
It is very simple to embed Quo in your own code.
#define QUO_IMPLEMENTATION // Define this before including quo.h in ONE of your source files
#include "../include/quo.h"
// Include modules that you need to be available in your quo code.
#include "../include/quo-mod-base64.h"
#include "../include/quo-mod-csv.h"
#include "../include/quo-mod-dl.h"
#include "../include/quo-mod-env.h"
#include "../include/quo-mod-fs.h"
#include "../include/quo-mod-json.h"
#include "../include/quo-mod-math.h"
#include "../include/quo-mod-net.h"
#include "../include/quo-mod-os.h"
#include "../include/quo-mod-time.h"
#include "../include/quo-mod-uuid.h"
int main() {
const char *path = "path/to/script.quo";
char *source = quo_read_file(path);
if (!source) {
fprintf(stderr, "Failed to read file: %s\n", path);
return 1;
}
char *cwd = quo_dirname(path);
QuoModule *m = quo_module_new(NULL, cwd, path, source, NULL);
quo_dealloc(source);
quo_dealloc(cwd);
// If module is NULL, there was compilation error.
if (!m) return 1;
// Load stdlib modules
quo_mod_base64_init(m);
quo_mod_csv_init(m);
quo_mod_dl_init(m);
quo_mod_env_init(m);
quo_mod_fs_init(m);
quo_mod_json_init(m);
quo_mod_math_init(m);
quo_mod_net_init(m);
quo_mod_os_init(m);
quo_mod_time_init(m);
quo_mod_uuid_init(m);
// Run the module and get the result.
int exit_code = 0;
QuoVar result = quo_module_run(m);
if (quo_var_is_err(&result)) {
fprintf(stderr, "Runtime Error: %s\n", result.val_err);
exit_code = 1;
} else if (quo_var_is_num(&result)) exit_code = (int)result.val_num;
quo_var_unref(&result);
quo_obj_unref((QuoObj *)m);
return exit_code;
}To see the example of embedding Quo in your own code, see the main.c file.