Skip to content

Questions and answers

Each answer holds a program that compiles, and the target code below it is the output of the compiler for that program.

When the items are known, write an array literal. The literal has two forms. ([] a b c) takes the element type from the items. ([] _:T (a b c)) states the type, and the marker _:T must be followed by a group in parentheses.

docs/examples/faq/array-literal.rgr
class Main {
sfn main:void () {
; One statement, and the compiler knows the size.
def numbers:[int] ([] 1 2 3 4 5)
; A literal with an explicit element type needs a parenthesised group.
def names:[string] ([] _:string ("Ada" "Alan" "Grace"))
print ("numbers " + (array_length numbers))
print ("first name " + (itemAt names 0))
}
}
main function, JavaScript
const numbers = [1, 2, 3, 4, 5];
const names = ["Ada", "Alan", "Grace"];
console.log("numbers " + numbers.length);
console.log("first name " + names[0]);
The complete file
class Main {
constructor() {
}
}
/* static JavaSript main routine at the end of the JS file */
function __js_main() {
const numbers = [1, 2, 3, 4, 5];
const names = ["Ada", "Alan", "Grace"];
console.log("numbers " + numbers.length);
console.log("first name " + names[0]);
}
__js_main();

The compiler knows the number of items, so the target code holds one statement: [1, 2, 3, 4, 5] in JavaScript, []int64 {…} in Go and vec![…] in Rust.

How do I build an array while the program runs?

Section titled “How do I build an array while the program runs?”

When the items come from a loop, from a file or from a calculation, the number of them is not known in advance. Use the operators that change the array.

Operator Function
push Add an item to the end.
set_at Replace the item at an index.
remove_index Remove the item at an index.
removeLast Remove the last item.
clear Remove every item.
insert Add an item at an index.
docs/examples/faq/array-mutation.rgr
class Main {
sfn main:void () {
; The items are not known in advance, so the program adds them one at
; a time.
def numbers:[int]
def i 1
while (i < 4) {
push numbers (i * 10)
i = i + 1
}
print ("after push " + (array_length numbers))
set_at numbers 0 99
print ("first " + (itemAt numbers 0))
removeLast numbers
remove_index numbers 0
print ("left " + (array_length numbers))
clear numbers
print ("after clear " + (array_length numbers))
}
}
main function, JavaScript
let numbers = [];
let i = 1;
while (i < 4) {
numbers.push(i * 10);
i = i + 1;
};
console.log("after push " + numbers.length);
numbers[0] = 99;
console.log("first " + numbers[0]);
numbers.pop();
numbers.splice(0, 1).pop();
console.log("left " + numbers.length);
numbers.length = 0;
console.log("after clear " + numbers.length);
The complete file
class Main {
constructor() {
}
}
/* static JavaSript main routine at the end of the JS file */
function __js_main() {
let numbers = [];
let i = 1;
while (i < 4) {
numbers.push(i * 10);
i = i + 1;
};
console.log("after push " + numbers.length);
numbers[0] = 99;
console.log("first " + numbers[0]);
numbers.pop();
numbers.splice(0, 1).pop();
console.log("left " + numbers.length);
numbers.length = 0;
console.log("after clear " + numbers.length);
}
__js_main();

The array operators page holds the complete set, and each entry states which targets it writes code for. insert and remove both write code for the command-line targets.

The operator is !, and it is in prefix form like every other operator: (! value). The argument is a boolean.

docs/examples/faq/not-operator.rgr
class Main {
sfn main:void () {
def ready false
if (! ready) {
print "the program is not ready"
}
def empty:[int]
if (! ((array_length empty) > 0)) {
print "the array holds no item"
}
}
}
main function, JavaScript
const ready = false;
if ( false == ready ) {
console.log("the program is not ready");
}
let empty = [];
if ( false == (empty.length > 0) ) {
console.log("the array holds no item");
}
The complete file
class Main {
constructor() {
}
}
/* static JavaSript main routine at the end of the JS file */
function __js_main() {
const ready = false;
if ( false == ready ) {
console.log("the program is not ready");
}
let empty = [];
if ( false == (empty.length > 0) ) {
console.log("the array holds no item");
}
}
__js_main();

A singleton class has one instance, and each part of the program uses that instance. Add the annotation @singleton(true) to the class. The compiler then writes a static function __singleton(), and each call gives the same instance.

docs/examples/faq/singleton.rgr
class CounterStore @singleton(true) {
def total:int 0
fn add:void (n:int) {
total = total + n
}
}
class Main {
sfn main:void () {
def a (CounterStore.__singleton())
def b (CounterStore.__singleton())
a.add(3)
b.add(7)
print ("total " + b.total)
}
}
main function, JavaScript
const a = CounterStore.__singleton();
const b = CounterStore.__singleton();
a.add(3);
b.add(7);
console.log("total " + b.total);
The complete file
class CounterStore {
constructor() {
if (CounterStore.__singleton_instance != null) {
return CounterStore.__singleton_instance;
}
this.total = 0;
CounterStore.__singleton_instance = this;
}
add (n) {
this.total = this.total + n;
};
}
CounterStore.__singleton_instance = null;
CounterStore.__singleton = function() {
if (CounterStore.__singleton_instance == null) {
CounterStore.__singleton_instance = new CounterStore();
}
return CounterStore.__singleton_instance;
};
class Main {
constructor() {
}
}
/* static JavaSript main routine at the end of the JS file */
function __js_main() {
const a = CounterStore.__singleton();
const b = CounterStore.__singleton();
a.add(3);
b.add(7);
console.log("total " + b.total);
}
__js_main();

The instance is per target process. The compiler writes the registry that the target language needs, so the program does not hold a global variable.

How do I write one class that holds several types?

Section titled “How do I write one class that holds several types?”

Declare the class with @params(...) and give the arguments with @(...).

docs/examples/faq/generic-class.rgr
; A type parameter is declared with @params and given with @(...). Nothing is
; asked of the argument type, so each of these is a separate concrete class
; after the compiler expands it.
class History @params(Op) {
def ops:[Op]
fn record:void (op:Op) {
push ops op
}
fn count:int () {
return (array_length ops)
}
fn newest:Op () {
def v:Op (last ops)
return v
}
}
class Main {
sfn main:void () {
def numbers:History@(int) (new History@(int) ())
numbers.record(3)
numbers.record(9)
def n (numbers.count())
def top (numbers.newest())
print (("numbers " + n) + " newest " + top)
def words:History@(string) (new History@(string) ())
words.record("first")
words.record("second")
def wn (words.count())
def wtop (words.newest())
print (("words " + wn) + " newest " + wtop)
}
}
main function, JavaScript
const numbers = new History_int();
numbers.record(3);
numbers.record(9);
const n = numbers.count();
const top = numbers.newest();
console.log((("numbers " + n) + " newest ") + top);
const words = new History_string();
words.record("first");
words.record("second");
const wn = words.count();
const wtop = words.newest();
console.log((("words " + wn) + " newest ") + wtop);
The complete file
class Main {
constructor() {
}
}
class History_int {
constructor() {
this.ops = [];
}
record (op) {
this.ops.push(op);
};
count () {
return this.ops.length;
};
newest () {
const v = this.ops[(this.ops.length - 1)];
return v;
};
}
class History_string {
constructor() {
this.ops = [];
}
record (op) {
this.ops.push(op);
};
count () {
return this.ops.length;
};
newest () {
const v = this.ops[(this.ops.length - 1)];
return v;
};
}
/* static JavaSript main routine at the end of the JS file */
function __js_main() {
const numbers = new History_int();
numbers.record(3);
numbers.record(9);
const n = numbers.count();
const top = numbers.newest();
console.log((("numbers " + n) + " newest ") + top);
const words = new History_string();
words.record("first");
words.record("second");
const wn = words.count();
const wtop = words.newest();
console.log((("words " + wn) + " newest ") + wtop);
}
__js_main();

The target code holds History_int and History_string, two separate classes. The compiler makes one class for each set of arguments before it writes the target, so a target language does not need generics of its own and no target writes the type parameter. Neither class can see the fields of the other.

A type parameter is usable as an array element, as a map value, as a parameter type and as a return type. The argument can be a primitive, a class, a record, a shape, an array (History@([string])) or a map (Store@([string:int])). There are no bounds and no constraints, so when the class must compare two values, give it the comparison function at construction.

A generic class has no static side. Only the instantiations exist, so a sfn in a generic class is not reachable. Put the static functions in a plain class beside it.

How do I write an object to JSON and read it back?

Section titled “How do I write an object to JSON and read it back?”

Add the annotation @serialize(true) to the class. The compiler then writes toDictionary() and the static fromDictionary().

docs/examples/faq/serialize.rgr
class Point @serialize(true) {
def x:int 0
def y:int 0
}
class Main {
sfn main:void () {
def p:Point (new Point())
p.x = 3
p.y = 4
def data:JSONDataObject (p.toDictionary())
def back:Point (Point.fromDictionary(data))
print ("x " + back.x + " y " + back.y)
}
}
main function, JavaScript
const p = new Point();
p.x = 3;
p.y = 4;
const data = p.toDictionary();
const back = Point.fromDictionary(data);
console.log((("x " + back.x) + " y ") + back.y);
The complete file
class Point {
constructor() {
this.x = 0;
this.y = 0;
}
toDictionary () {
let res = {};
try {
res["x"] = this.x;
res["y"] = this.y;
} catch(e) {
}
return res;
};
}
Point.fromDictionary = function(dict) {
const obj = new Point();
try {
const v = isNaN( parseInt(dict ["x"]) ) ? undefined : parseInt(dict ["x"])
;
if ( (typeof(v) !== "undefined" && v != null ) ) {
obj.x = v;
}
const v_1 = isNaN( parseInt(dict ["y"]) ) ? undefined : parseInt(dict ["y"])
;
if ( (typeof(v_1) !== "undefined" && v_1 != null ) ) {
obj.y = v_1;
}
} catch(e) {
}
return obj;
};
class Main {
constructor() {
}
}
/* static JavaSript main routine at the end of the JS file */
function __js_main() {
const p = new Point();
p.x = 3;
p.y = 4;
const data = p.toDictionary();
const back = Point.fromDictionary(data);
console.log((("x " + back.x) + " y ") + back.y);
}
__js_main();

Two rules apply:

  • Every class that the serialized class refers to must also be @serialize(true), or it must hold toDictionary and fromDictionary by hand. The compiler stops with the name of the class that is not serializable.
  • The generated code uses the JSON operators. The tabs above show which targets have them.

Which annotations change the code for C++, Rust and Swift?

Section titled “Which annotations change the code for C++, Rust and Swift?”

Most targets collect the memory that a program does not use. C++, Rust and Swift count references instead, and two objects that hold each other keep each other alive. Ranger has four annotations for this: weak, strong, lives and temp.

The program below is the case that matters: a parent holds a child, and the child holds the parent back. The back reference carries @(weak).

docs/examples/faq/annotations-memory.rgr
class Node {
def name:string ""
def child@(optional):Node
; The parent must not keep the child alive: the two would then hold each
; other, and a target that counts references would free neither.
def parent@(weak optional):Node
}
class Main {
sfn main:void () {
def root:Node (new Node())
root.name = "root"
def leaf:Node (new Node())
leaf.name = "leaf"
root.child = leaf
leaf.parent = root
def kid:Node (!! root.child)
print ("child of root " + kid.name)
}
}
main function, JavaScript
const root = new Node();
root.name = "root";
const leaf = new Node();
leaf.name = "leaf";
root.child = leaf;
leaf.parent = root;
const kid = root.child;
console.log("child of root " + kid.name);
The complete file
class Node {
constructor() {
this.name = "";
this.child = undefined;
this.parent = undefined;
}
}
class Main {
constructor() {
}
}
/* static JavaSript main routine at the end of the JS file */
function __js_main() {
const root = new Node();
root.name = "root";
const leaf = new Node();
leaf.name = "leaf";
root.child = leaf;
leaf.parent = root;
const kid = root.child;
console.log("child of root " + kid.name);
}
__js_main();

This is what the three targets do with that annotation. The statements come from the output above and from the writers of the compiler. A tab holds the main function, and the class with the two fields is in the complete file behind the link under the code.

std::shared_ptr<Node> child;
r_weak<Node> parent;

r_weak<T> is a small wrapper that the compiler writes above the classes. It holds a std::weak_ptr<T> and gives the shared pointer back at the read, so a field access, a null test and an assignment stay as they were.

The measurement is a leak check. A parent and a child that hold each other with strong references, built with g++ -fsanitize=address, reports 168 byte(s) leaked in 3 allocation(s). With @(weak) on the back reference the same program leaks nothing.

var child : Node?
weak var parent : Node?

ARC then frees the pair. Swift needs two things: the storage must be a var, and the type must be optional. Swift sets a weak reference to nil when the object goes away. Write @(weak optional), as the example does. A field that states weak without optional keeps the strong form, because weak var parent : Node is not Swift that compiles.

child : Option<Box<Node>>,
parent : Option<Weak<RefCell<Node>>>,

The Rust writer reads weak, and the assignment becomes Rc::downgrade(…).

The Rust output of that assignment does not compile. rustc rejects the read of the field, and the Rc that Rc::downgrade takes is a temporary, so the weak reference would be empty even if it compiled. The cause is under the annotation: a Rust class is a plain struct, so no Rc holds the parent. Do not use @(weak) in a program that must compile for Rust.

Annotation Measured effect
strong The target code does not change.
lives The target code does not change. The compiler uses the annotation for its own lifetime bookkeeping.
temp The target code does not change, for the same reason.

On a target that collects memory — JavaScript, TypeScript, Java, Kotlin, Dart, C#, Go, Python, PHP and Scala — none of the four changes the output. The collector handles a cycle.

Ownership and lifetime states the model behind these annotations, and the memory page states what each target writes.

Ranger is an s-expression language. A call is (function argument argument).

A call on a dotted receiver may also be written the way the C family writes it, with nothing between the name and the (. The parser folds the two into one call node, so both spellings below are the same program:

Both of these work
return this.helper() return (this.helper())
return this.helper() + 10 def v:int (this.helper()) then return (v + 10)
def v:int (this.helper() + 1) def v:int (this.helper()) then def w:int (v + 1)
this.other(this.helper()) this.other((this.helper()))
return a.b().c() return (a.b().c())

What still needs the parentheses, or a local:

Write Do not write Why
return (fn1(3)) return fn1(3) The fold reaches a dotted receiver only. An undotted name in front of a ( is also how new Type(...) and a method declaration are spelled.
def recv:T (expr) then recv.method() (expr).method() A statement must not start with a receiver in parentheses.
def n:T (h.nodeOf()) then f(n.plain) f((h.nodeOf()).plain) A parenthesised receiver followed by a field does not resolve in argument position.
One statement per line { def c:int 5 return c } Two statements on one line is a parse error.

A property read on a parenthesised receiver compiles in an infix operator: ((unwrap x).v == 1) and (1 + (f()).v). A method call on that receiver inside an infix expression still needs a local. A statement must not start with a parenthesised receiver.

docs/examples/faq/lisp-form.rgr
class Helper {
fn value:int () {
return 42
}
}
class Main {
sfn main:void () {
def helper:Helper (new Helper())
; A call gives a value. On a dotted receiver the parentheses around it
; are optional, so `(helper.value())` and `helper.value()` are the same
; call, and arithmetic may use one directly.
def n (helper.value())
def total (helper.value() + 1)
print ("total " + total)
}
}
main function, JavaScript
const helper = new Helper();
const n = helper.value();
const total = helper.value() + 1;
console.log("total " + total);
The complete file
class Helper {
constructor() {
}
value () {
return 42;
};
}
class Main {
constructor() {
}
}
/* static JavaSript main routine at the end of the JS file */
function __js_main() {
const helper = new Helper();
const n = helper.value();
const total = helper.value() + 1;
console.log("total " + total);
}
__js_main();

What does “Could not find suitable match for the operator node” mean?

Section titled “What does “Could not find suitable match for the operator node” mean?”

The call is to a defn macro, and no macro of that name fits the arguments.

A macro declares no types. The compiler selects one by trial. It expands the body of each macro with that name into the call site and compiles the result. The first candidate that makes no error wins. The message says that every candidate made an error.

The errors above the message come from the candidate with the fewest errors. That candidate is not always the macro that you had in mind. Read the message together with the bodies on the macros page, and start from the types of your arguments. ForEach is an example: one macro reads an array, and another one reads a hash map, and only the body states the difference.

compiler/Lang.rgr is the language definition. Ranger has no separate standard library. You can declare more library operators for the compiler in Lang.rgr and in imported files. A custom operator needs a new compiler.

  • The operator reference lists Lang.rgr. Every program can use those operators.
  • stdlib.rgr is an imported file. It adds map, filter, reduce, any, all and slice.
  • JSON.rgr and the other imported files work the same way.

The operator / on two integers gives a double. The operator idiv gives an integer and truncates toward zero.

docs/examples/faq/integer-division.rgr
class Main {
sfn main:void () {
def a 10
def b 3
print ("real " + (a / b))
print ("integer " + (idiv a b))
}
}
main function, JavaScript
const a = 10;
const b = 3;
console.log("real " + a / b);
console.log("integer " + ((a / b) | 0));
The complete file
class Main {
constructor() {
}
}
/* static JavaSript main routine at the end of the JS file */
function __js_main() {
const a = 10;
const b = 3;
console.log("real " + a / b);
console.log("integer " + ((a / b) | 0));
}
__js_main();

Why does the compiler say that a class does not have a method that I wrote?

Section titled “Why does the compiler say that a class does not have a method that I wrote?”

The compiler resolves some method names in another place. A class can define one of these names, and the class compiles. Each call to that method then fails with Class X does not have method ….

Do not use as a method name A name that works
contains hasSub
startsWith beginsWith
endsWith finishesWith
trim trimWs
first lowest
last highest
remove removeNode
insert insertNode
write toText
read fromText
normalize collapse
toString asString
has mentions
sqrt squareRoot

The list is the names that programs have hit. It is not a complete list of reserved names.

Ranger-authored compiler sources use the MIT license, unless a file states a different license. Ranger-authored files in gallery/ use the GNU Affero General Public License, version 3 or a later version, unless a file states a different license. The license of compiled output follows the source. A compiled gallery program stays under the AGPL. The Licenses page states the rule.

Which target language does the compiler write?

Section titled “Which target language does the compiler write?”

Thirteen languages from the command line: JavaScript, TypeScript, Go, Rust, Python, Java, Kotlin, Dart, Swift, C#, C++, PHP and Scala. The target page states what each one supports, and each operator page states the support per operator.

Ranger 3.5.1 · commit f323fd4 · development build