[][][]  [][][]  []`\\  []`\\  [][][]  []`\\  []       //\\   []\ []  []====
  []      []  []  []_//  []_//  []===   []_//  []      []==[]  []\\[]  []  TT
  [][][]  [][][]  []     []     [][][]  [] \\  [][][]  []  []  [] \[]  [][][]

Copper Programming Language Documentation

  1. Introduction
  2. Basics
  3. System Messages
  4. Command Structures
  5. Built-in Functions

Introduction

Copper Lang is a statically-typed language that revolves around the concept of object-functions. It is similar to a number of other high-level languages, such as Javascript, but differs in its emphasis on stack-based paradigms and memory safety.

The interpreter is designed to be a stand-alone engine for easily embedding into other applications and is written in portable, version agnostic C++ (tested with both C++98 and C++11 settings on GCC).

Basics

There is no Standard In (stdin) or "main" function. Rather, the Copper Lang engine requires that it be fed a stream of characters or tokens. The program can be "ended" by running the "exit" token, which clears the stack. More information on this can be found in the Interpreter Usage Guide.

Copper Lang revolves around manipulating and using functions. User-created functions are essentially objects with two components: a persistent scope and a body of execution. By default, functions have an empty body and can be executed immediately. Bodies are constant, so they are often shared between functions in memory. Persistent scopes, however, are unique to each function. As their name implies, the lifetime of a persistent scope is long as the function it belongs to.

It is only possible to access the members of a function's persistent scope using the member-access token (a period (.)) or the "member" built-in function. There is no need to instantiate member variables. Accessing them automatically creates them. As with all variables, they default to empty functions.


a.b

Despite being a pseudo-dynamic language, Copper Lang is intended to guarantee memory safety during variable access. There are many objects, but all of these objects are saved in functions when assigned to variables. That way, they can all be accessed in the exact same way: by running the function. Furthermore, there is no "null"/"nil" or "void". The default function return for most cases is a new empty function, including for functions that don't have a return statement and pointer access failures.

There are three very important rules that govern the language:

  1. Data end an expression. - That means all numbers, strings, and anything else that does not perform processing will automatically cause the termination of an expression (statement of processing).
  2. A function call ends an expression. - That implies you can't perform actions such as scope-opening on the return of a function.
  3. A variable, not followed by the appropriate tokens, ends the expression. - That means if you don't try to call the function, assign to it, or access a member variable, the function will simply return itself to end the expression.
Consequently, there is no need for end-of-statement terminators, but the comma (,) is allowed if you want to make code more readable.

To ensure there are no memory leaks, Copper Lang variables have stack-based lifetimes. A function is owned by the first variable that it is assigned to. Assignment occurs via the assignment operator (=). The function belonging to one variable can be copied to another variable using the assignment operator. Functions can be pointed to using a special pointer assignment operator: the tilde (~).

Function creation is very liberal. Functions will default to returning a new empty function if no return function is called within the function body. Functions can be created in three possible ways: (1) beginning with an object section, which is then followed by the function body, (2) beginning with a object section, (3) beginning with the function body.

All three ways are demonstrated as follows:


a = []{}
b = []
c = {}

In function creation, the object section has two types of parameters: (1) names that will act as names for the arguments passed into the function during calls and (2) names that are assigned objects and saved in the persistent scope. In the latter case, a name must be followed by an assignment symbol (or pointer assignment symbol), which in turn is followed by some data or a variable.


a = [temp, persistent="Some data"] {}

Function return is done by calling the ret() function and passing it the object to return.


a = {
	ret(5)
}

Within a function body, persistent scope members must be accessed using the self-reference pointer "this".


a = {
	this.my_member = 5
}
a()
print( a.my_member() ) # Prints 5 #

Parent variables can be accessed using the parent-reference pointer, "super". Technically, this pointer accesses the scope of the variable whose member variable function is currently being run.


a.b = {
	super.c = 5
}
a.b()
print(a.c()) # Prints 5 #

A shorthand for calling functions without passing them any arguments is done by using the immediate-run token - a colon (:) - instead of parentheses.


a:

System Messages

Errors and Warnings

Not all mistakes result in calamity, but they may be unintended and result in unwanted activity. Therefore, warnings and errors are treated very differently. Finally, there are always miscellaneous messages that the engine may want to pass, though they don't indicate problems. These are given as info messages.

All messages are, by default, sent to an instance of the Logger class, which can be set via the engine interface.

Info

These messages provide general information about the state of the engine.

Warnings

Whenever the engine detects a strange anomaly in the code or an activity, it sends a warning. Generally, these warnings indicate when something unconventional has happened and will likely result in activity that the programmer did not intend. Therefore, it is advisable to not ignore them.

Errors

Errors, when they do occur, are usually fatal. They are very likely the result of poor code design and not mere accidents, and therefore they should occur very early in a program's execution, depending on the program's reliance on the defective part of its design. Syntax violations usually result in fatal errors.

Since in many cases it isn't possible to recover from many types of errors, errors result in stack crash (erasure). This means everything is effectively reset. While a possible exception-handling system could be developed, such errors are generally syntax errors and, if allowed to continue, would most likely result in actions the programmer did not intend anyways.

NOTE: Some built-in functions result in errors that, while non-fatal, would give meaningless results. These include are_same(), member(), and set_member().

Debug

For debugging purposes, the engine will send out messages to help find points of failure. These messages must be activated by preprocessor flags.

Built-in Control Structures

If-Statements

A block of code can be run under a condition using an if-statement, which is begun using the keyword "if". The keyword "if" is followed by a conditional. The conditional is enclosed in parentheses, which are then followed by a block of code to be executed if that conditional results in a boolean value of "true". Optionally following this block can be an "elif" or an "else". The "elif" keyword must be followed by a conditional in parentheses. Following this conditional is a block of code that is executed if the conditional is true and the conditional after the "if" keyword is false. Optionally, there may be an "else" keyword, which is followed by a block of code. If the previously listed conditionals fail, the block of code following the "else" is run.

All conditionals must result in a boolean value. An error is returned if this is not the case.


if ( a() ) {
	print("If true")
} elif ( b() ) {
	print("Elif true")
} else {
	print("Nothing true")
}

Loops

A block of code can be run repeatedly using a loop, which is begun using the keyword "loop". The keyword "loop" is followed by block that is executed endlessly unless the keyword "stop" is encountered in execution.


loop {
	if ( true ) { stop }
}

A loop cycle can be restarted by using the keyword "skip".


loop {
	if ( someCondition() ) {
		skip
	}
	stop
}

Built-in Functions

The Copper engine is very much a bare-bones interpreter, so it has a limited set of functions. Most notably, it contains no print() function.

Pointer Structures

While the following are syntactically similar to functions, they are structures that can only accept names (variables). Ordinary operations within them are invalid, nor would they make any sense.

own()

Accepts a variable and converts it from a pointer (if it is one) to the owner of the function it points to.

Disabled by default. Must be enabled with Engine::setOwnershipChangingEnabled().

WARNING: Misuse can cause cross-references of variables and create memory leaks. This function is not an essential feature but provided as a convenience.

is_ptr()

Accepts a variable and returns true if it is merely a pointer and not the owner of the function it points to.

is_owner()

Accepts a variable and returns true if it owns the function it points to.

Pointer Functions

are_same()

Accepts any number of arguments and returns true if all of them point to the same function.

Membership Functions

member()

Accepts two arguments - a function and a string. It attempts to find and return a persistent scope member (of the function) whose name matches the given string. If either argument is incorrect, it prints a warning and returns an empty function.

WARNING: This automatically creates a variable if it does not exist. You should purge strings of any characters that you do not wish to permit in names.

NOTE: This function returns only a function, not the return value of the function. To get the return value, save the returned function to a pointer and call it.


ab ~ member(a "b")
ab()

member_count()

Accepts a single function as an argument and returns how many member variables are in its persistent scope.

is_member()

Accepts two arguments - a function and a string. It checks if the string matches the name of a member in the persistent scope of the given function. If an argument is incorrect, it prints a warning and returns false.

set_member()

Accepts three arguments - a function, a string, and a value object. It opens the persistent scope of the given function and assigns the value object to the member whose name matches the given string.

NOTE: This automatically creates a variable if it does not exist. The namestring is purged as though it were a regular variable.

NOTE: (Currently) If the value is a function, it is copied, not pointed to.

member_list()

Accepts any number of functions as arguments and returns their members as a list.

union()

Accepts any number of functions and copies their persistent scopes into the persistent scope of a new function that it returns.

Type Checks

type_of()

Accepts a single arguments and returns the type name of the object as a string.

are_same_type()

Accepts any number of arguments and returns true if all of them are the same type.

are_fn()

Accepts any number of arguments and returns true if all of them are functions.

are_bool()

Accepts any number of arguments and returns true if all of them are boolean values.

are_string()

Accepts any number of arguments and returns true if all of them are strings.

are_list()

Accepts any number of arguments and returns true if all of them are lists.

are_number()

Accepts any number of arguments and returns true if all of them are numbers.

are_int()

Accepts any number of arguments and returns true if all of them are of the built-in integer class.

are_dcml()

Accepts any number of arguments and returns true if all of them are of the built-in decimal class.

Assertions

are_empty()

Accepts any number of arguments and returns true if all of them are empty functions.

assert()

Accepts any number of arguments and creates an error if any argument is not true.

copy_of()

Accepts a single argument and returns an independent copy of it.

For classes inheriting Cu::Object that return their own address (e.g. the "this" pointer) from their copy() method, this function will not create an independent copy.

Boolean Operations

not()

Accepts a single boolean object and returns its inverse. It will print a warning if the object is not a boolean value or more than one object has been passed to the function.

all()

Acts as the "and" operator. It accepts any number of objects and returns true if all of those objects are true. The default return is false.

any()

Acts as the "or" operator. It accepts any number of objects and returns true if any of those objects are true. The default return is false.

nall()

Acts as the "nand" operator. It accepts any number of objects and returns true if not all of the objects are true. The default return is true in accordance with it being the inverse of a function that defaults to returning false.

It is equivalent to:

not(all(x))

none()

Acts as the "nor" operator. It accepts any number of objects and returns true if none of those objects are true. The default return is true in accordance with it being the inverse of a function that defaults to returning false.

It is equivalent to:

not(any(x))

List Operations

list()

Creates a list from the given arguments.

When passed an function that is created in-line, the function is owned by the list and will only be destroyed with the list. For example:

list( {} )

However, when the function is from a variable, the list will contain only a pointer to that function.

a = {}, list(a)

To delink the list function from the variable, the function needs to be copied.

a = {}, list(copy_of(a))

length()

Accepts a list as an argument and returns the size of that list.

append()

Accepts a list and an object. It appends the object to the end of the list.

prepend()

Accepts a list and an object. It prepends the object to the beginning of the list.

insert()

Accepts a list, an index, and an object. It inserts the object at the given index. Decimal indexes will be converted to integers.

item_at()

Accepts a list and an index. It attempts to return the item in the list at the given index. In case the item does not exist, an empty function is returned.

erase()

Accepts a list and an index. It attempts to delete the item in the list at the given index. If the item is a pointer, the original will still be preserved. No warning is given if the index is out of bounds.

dump()

Accepts a single list and attempts to delete all of its contents. If there are pointers in the list, the originals will still be preserved.

swap()

Accepts a single list and two index values. It attempts to swap the positions of items in the list at those index values.

No warning is given if the indexes are out of range.

replace()

Accepts a single list, an index, and an object. It attempts to replace the item in the given list at the given index with the given object.

No warning is given if the index is out of range.

sublist()

Accepts a single list and two index values that can be mapped to the range 0 to list length. It creates a list containing shared links to the items in the given list.

NOTE:

String Operations

matching()

Accepts any number string arguments. Returns true if all of them are identical as strings. Returns false in all other cases, including when non-strings are passed. The return is always a string.

concat()

Accepts any number of arguments. Attempts to concatenate them into a single string, which is returned. Non-strings are ignored. The return is always a string.

WARNING: This function uses Cu::Object::writeToString, also used for printing. Therefore, implementations of writeToString should only be conversions of data, not string versions of types.