NAV Navbar
copper cpp
  • Introduction
  • The Language of Copper
  • The Virtual Machine
  • FAQ
  • Introduction

    guest = get_your_name:
    print("Welcome, " guest: "!")
    

    Welcome to the (NEW) documentation of the Copper interpreted programming language!

    Copper Lang is a statically-typed language that revolves around the concept of object-functions. It emphasizes stack-based paradigms and memory safety, eliminating the problems of memory-leaks and null pointers.

    The virtual machine 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).

    To jump in and start using it right away, read:

    Overview

    Copper as a language is like a cross between Javascript, Rust, and Lisp. Every object is a function and vice versa. Object-functions are created in a manner similar to (but not the same as) Javascript. Members are created like in Python. And in order to do just about anything (including access data), you have to run a function. Whitespace is almost completely irrelevant other than that it must exist between names.

    To guarantee memory safety, variables only store and handle functions and function pointers (which are safetly converted to normal functions upon access failure). There is no null! Furthermore, variables are all tied to the virtual stack in some way, thereby preventing memory leaks.

    The Copper virtual machine is a set of files containing all of the basic necessities to process the language. The main entry point is via the Copper Engine, which requires an input feed of characters and performs all of the parsing and operations. Some basic extensions have been provided for reading and printing to the terminal/console as well as performing some extra mathematical operations.

    Built-in data-types include booleans, integers, numbers with decimals, strings, and lists.

    Attractive Features

    (Current IDE support is gtksourceview2 syntax highlighters.)

    Reasons to NOT Use Copper

    Uses for Copper


    The Language of Copper

    Copper revolves around the concept of the object-function (or "function-object" if you prefer). Variables in Copper only store object-functions or pointers to them, nothing else. Operators are used only for manipulating function objects. Data manipulation is only done by functions (especially foreign functions).

    Copper has a strict set of rules regarding its interpretation. (See Fundamental Rules.) As a small set of rules, it reeduces the mental focus needed to read the code.

    Comments are created by sandwiching the comment with two pound signs, "#", one on each side.


    Fundamental Rules

    There are three very important rules that govern the language:

    Consequently, there is no need for end-of-statement terminators, but the comma (,) is allowed if you want to make code more readable.

    Function calls end expressions because they return data.

    Object-functions

    The object-function is a data type that has both a table of persistent, publically-accessible members and a body of code that can be executed. The members are stored in a "persistent scope" - a hash-mapped table of variables that belong to only one object-function. The body of code (called the "execution body") is also unique to the object-function (however, because it is constant, it might be shared with multiple objects to save space).

    # Construction 1 # []
    # Construction 2 # {}
    # Construction 3 # []{}
    
    [ # object component # ] { # execution body # }
    

    An object-function can be created directly in three ways:

    # Creating an object-function with a member, parameter, and body #
    some_object_function = [
        member = "has data"
        parameter
    ] {
        print("In the body is a " parameter:)
    }
    
    // Analogous C++ object
    class Object {
    public:
        virtual ~Object() {}
        virtual Object* operator() = 0;
    };
    

    The object component plays a dual role in the grammar. Within the object-body tokens there are two types of parameters: function parameters and persistent-scope parameters. Any parameter that is assigned with the assignment operator or pointer operator becomes a member of the object-function, stored away in the persistent scope of the function. (See Variables and Members for more about assignment.) Any unassigned parameter becomes a regular parameter and is assigned whatever the function is passed via the argument tokens.

    Optional: The arguments can be separated with a comma.

    some_func:
    some_func(arg)
    

    To call a function, it must first reside within a variable. Appending the direct-call operator ":" or the argument-wrapping tokens "(" and ")" (parentheses) to the function will call it. The direct-call operator is faster, but only the arguments operator can take the accepted arguments and map them to the object-function parameters.

    Arguments are always passed by reference. Raw data passed as an argument is stored in a new function assigned to the function parameter at its argument index.

    Function calls can accept any number of arguments. However, there will only be as many arguments mapped as there are parameters. When not enough arguments are given to a function, the parameter is set to an empty function.

    a = {
        # "this" points to "a", super points to nothing #
        this.c = 10
    }
    a.b = {
        # "this" points to "b", super points to "a" #
        super.c = 5
    }
    print(a.c:) # prints 5 #
    

    When a function is called, a function is given a member variable named this or that member is updated. The this member is a pointer to the wrapping object-function itself to allow you to access its members. If the function is stored in a variable that is a member of another object-function, it is given a member named super that points to that preceding object-function in the address chain.

    Function return is done by calling the built-in return function, "ret". Only one argument will be returned. All others are ignored. Arguments are returned by reference. Data is stored in a new function when returned.


    Variables and Members

    a # valid Copper #
    b = {
        # accesing a global variable #
        a()
    
        # accessing a member variable #
        this.member = 5
    
        # local variable #
        c = 4
        ret(c)
    }
    

    A variable can be created by simply stating its name. If the variable was created at the local scope level of a function body, it will be destroyed when the function terminates. However, global variables cannot be deleted by other Copper code.

    All variables can trace their ancestry to either the global or local scope, thereby limiting all of them to stack-based lifetimes.

    A variable can either own a function or be a pointer to it. When a pointer, a variable shares the function with another variable that does own the function. If the original function is destroyed, then the pointer variable is given its own new function the next time it is accessed, and that way, there is no segmentation fault.

    The members of a function assigned to a variable can be accessed using the member-access operator "." (period) appended to the end of a variable (such as "this"). A member can be created simply declaring it following the member-access operator. This allows chaining members and member-access operators to create variables.

    Assignment

    a = 10
    

    An assignment to a variable occurs via the assignment operator, "=". The variable to the left receives the data from the right. Both raw data (such as integers) and functions can be assigned to a variable. All assignments from one variable to another are copy actions, whereby the object-function and all of its members are copied from the giving variable into the receiving variable. The same action takes place when an object-function or data is returned from a function call.

    Assigning data directly to a variable results in the function body being given a direct-return value. Whenever the function is called, this value is returned immediately. Consequently, it is faster than the equivalent function body.

    Pointer Assignment and Usage

    b ~ a
    b = 12
    print(a:) # Prints 12 #
    

    Pointer assignment occurs via the pointer-assignment operator, "~". The variable to the left points to the data on the right. However, if the pointer assignment is to data, that data is stashed into a new, ownerless function which is then owned by the variable it is assigned to.

    Member Creation

    a.b = 0
    

    When a function is pointed to by a variable, the members of that function can all be accessed via the member-access operator. Members can also be created this way. The function's direct return can also be set this way.

    Data

    # boolean #
    true
    
    # integer #
    10
    
    # number with decimal #
    0.981
    
    # string #
    "string blarg"
    
    # list #
    list(false 7 6.5 "4" some_func)
    

    There are five basic data types in Copper apart from object-functions:

    Booleans are represented with true and false.

    Integers are created by writing any number without a decimal. Negative are allowed but cannot be created this way.

    Numbers with decimal are created by writing any number with a decimal. However, the decimal must not be the first character in the number. (A zero can be used first.) Negative values are allowed but cannot be created this way.

    Byte strings are created by using double-quotes around text. Escape characters are permitted and can be created using the usual slash. Permitted escape character sequences include: - \n - \r - \t

    Lists are created using the built-in list function. (See the Copper Lang API for details.) They can contain both object-functions and data. The list implementation in the virtual machine is a doubly-linked list.

    Program Termination

    The program exits when the virtual machine encounters exit.

    Global variables are not cleared upon program exit in order that they can still be used for callbacks.

    Control Structures

    The Copper language provides a couple basic control structures - if and loop - as well as a set of special ownership operators: own, is_ptr, and is_owner.

    If-structures

    if ( false ) {
        print( "False" )
    } elif ( true ) {
        print( "True" )
    } else {
        print( "What?" )
    }
    

    If-structures in Copper require their conditionals wrapped in the argument-wrapping tokens (parentheses), and the bodies of code that are executed upon true conditions must be wrapped in the execution-body tokens (curly braces).

    Technically, any code can be run inside of the if-structure conditionals, but the last result must be a boolean value in type.

    If structures can have optional elif and else components. The elif component must obviously have a conditional.

    Loop-structures

    s = true
    loop {
        if ( s: ) {
            s = false
            skip
        }
        stop
    }
    
    // Analogous C++
    bool s = true;
    while( true ) {
        if ( s ) {
            s = false;
            continue;
        }
        break;
    }
    

    Copper has no for-loops or while-loops. Instead, it has a single, infinitely-looping structure whose body of code is contained within the execution body tokens (curly braces).

    The loop is terminated whenever the "stop" token is found.

    The loop is restarted whenever the "skip" token is found.

    Ownership structures

    b.c ~ a
    print( is_ptr(b.c) ) # Prints true #
    print( is_owner(a) ) # Prints true #
    own(b.c) # Steal ownership #
    print( is_owner(b.c) ) # Prints true #
    

    The ownership structures are special operators that are called like functions but can each only receive a variable (not data). They are used to provide information about the variable itself or to modify ownership.

    The is_owner operator indicates if the given variable is the owner of the object-function it points to, whereas the is_ptr operator indicates if a variable is merely a pointer.

    The own operator changes the ownership of the function pointed to by the variable to that variable itself, regardless of the stack lifetime of that variable. It is used primary for changing pointer ownership.

    By default, this feature is disabled and can only be enabled by passing true to Engine::setOwnershipChangingEnabled().


    Copper Language API

    The following is a list of functions built into the virtual machine.

    Where "..." appears, these functions can accept any number of arguments.

    ret( function_return_value )

    Terminates the current function, popping it from the stack and returing the function_return_value.

    are_same( ... )

    Returns true if all of the given arguments point to the same function.

    member( object, member_name )

    a.b = 10
    c ~ member(a "b")
    print(c:) # Prints 10 #
    

    Returns a pointer to the member of the object-function object whose name is member_name. If the member does not exist, it will be created.

    member_count( object )

    Returns the number of members in the object function object.

    is_member( object, member_name )

    Returns true if member_name is the name of a member in the object-function object.

    set_member( object, member_name, value )

    Sets the member of object-function object named member_name to value.

    member_list( object ... )

    Returns as a list the names of all of the members of the given object-functions.

    union( ... )

    Returns a new object-function whose persistent scope is populated by copies of the persistent scopes off all of the given object-functions.

    type_of( arg )

    Returns the type of the arg as a type_value object.

    are_same_type( ... )

    Returns true if all of the given arguments have the same type.

    are_type( type_value ... )

    Accepts a type_value and any number of other objects. Returns true if all of successive arguments are of the type given by the first type_value.

    equal_type_value( type_value [type_value ...] )

    Accepts only type_value objects. Returns true if all of the type_value objects have the same value.

    typename_of( object )

    Returns the typename of the given object as a string.

    have_same_typename( object ... )

    Returns true if all of the given objects have the same typename.

    are_fn( ... )

    Returns true if all of the given arguments are of type object-function.

    are_empty( ... )

    Returns true if all of the given arguments are both of type object-function and have neither members in their persistent scope nor execution bodies.

    are_bool( ... )

    Returns true if all of the given arguments are of type boolean.

    are_string( ... )

    Returns true if all of the given arguments are of type string.

    are_list( ... )

    Returns true if all of the given arguments are of type list.

    are_number( ... )

    Returns true if all of the given arguments are of a numeric type (integer or number with decimal).

    are_int( ... )

    Returns true if all of the given arguments are of type integer.

    are_dcml( ... )

    Returns true if all of the given arguments are of type number with decimal.

    assert( condition )

    Throws an error if condition does not resolve to true.

    copy_of( arg )

    Returns an independent copy of the given argument.

    xwsv( super_variable, call_variable, call_args ... )

    Calls call_variable with the extra arguments and sets the "super" variable to super_variable.

    not( boolean_arg )

    Returns the opposite boolean value of boolean_arg.

    all( boolean_arg ... )

    Returns true if all of the given arguments are boolean true.

    any( boolean_arg ... )

    Returns true if any of the given arguments are boolean true.

    nall( boolean_arg ... )

    Returns true only if all of the given arguments are boolean false.

    none( boolean_arg ... )

    Returns true if none of the given arguments are boolean true.

    xall( boolean_arg ... )

    Returns true only if all of the arguments are of the same boolean value (that is, if all of them are true or all of them are false.

    list( ... )

    Creates and returns a list object made from the given arguments.

    length( list_object )

    Returns the length of the list list_object.

    append( list_object, item )

    Appends item to the list list_object.

    prepend( list_object, item )

    Prepends item to the list list_object.

    insert( list_object, index, item )

    Inserts item into the list list_object at the index index. Both integers and numbers with decimal are accepted as indexes.

    item_at( list_object, index )

    Returns a pointer to the object in list list_object at the index index. Element indexes start at zero.

    erase( list_object, index )

    Removes a element from the list list_object at the index index.

    dump( list_object )

    Removes all of the elements in the list list_object.

    swap( list_object, index_1, index_2 )

    Swaps the locations of two elements in the list list_object at the indexes index_1 and index_2.

    replace( list_object, index, item )

    Replaces the element at the index index in the list list_object with item.

    sublist( list_object [, start_index [, end_index]] )

    Returns the sub-list of the list list_object. Optionally, start_index can be given. If given, optionally end_index can be given, where end_index is the last excluded index.

    matching( string_arg ... )

    Returns true if all of the given arguments are matching strings.

    concat( ... )

    Returns a string resulting from the concetenation of the given arguments. The Cu::Object::writeToString() method is employed for obtaining a string value from every argument.

    equal( number ... )

    Returns true if all of the given numbers are equal to the first argument.

    gt( number ... )

    Returns true if all of the given numbers are greater than the first argument.

    gte( number ... )

    Returns true if all of the given numbers are greater than or equal to the first argument.

    lt( number ... )

    Returns true if all of the given numbers are less than the first argument.

    lte( number ... )

    Returns true if all of the given numbers are less than or equal to the first argument.

    abs( number )

    Returns the absolute value of the given argument.

    +( number ... ) , add( number ... )

    Returns the sum of the given numbers. The name of this function depends on if the extended nameset flag is enabled during compilation.

    -( number ... ) , sbtr( number ... )

    Returns the result of subtracting from the first argument all of the remaining given numbers. The name of this function depends on if the extended nameset flag is enabled during compilation.

    *( number ... ) , mult( number ... )

    Returns the product of all of the given numbers. The name of this function depends on if the extended nameset flag is enabled during compilation.

    /( number ... ) , divd( number ... )

    Returns the result of dividing the first argument by all of the remaining given numbers. The name of this function depends on if the extended nameset flag is enabled during compilation.

    %( number ... ) , mod( number ... )

    Returns the result of the modulus of the first argument by all of the remaining given numbers. The name of this function depends on if the extended nameset flag is enabled during compilation.

    ++( number [, step_size] ) , incr( number [, step_size] )

    Increments the given number by 1 or by the step_size if that is given. The name of this function depends on if the extended nameset flag is enabled during compilation.

    --( number [, step_size] ) , decr( number [, step_size] )

    Decrements the given number by 1 or by the step_size if that is given. The name of this function depends on if the extended nameset flag is enabled during compilation.

    Debugging Copper

    The engine comes built-in with a number of error messages for debugging Copper code. When stack-trace printing is enabled, both the current stack and the queued operations ("Task Stack") are printed. Unfortunately, because object-functions are nameless, it can be difficult to figure out exactly what caused the problem, so you will need to examine both the stack trace and the printing of the queue operations.


    Copper in 5 Minutes

    # Comments are between \#'s and are multiline
    but a slash escapes characters. #
    
    # Variables have stack-based lifetimes and only one type: object-function.
    Declaring a name makes a variable. #
    a
    
    # Assignment (copies data and members) #
    a = 5
    
    # Getting what's stored is done by calling the function in the variable. #
    a()
    
    # Pointers (left hand side becomes pointer to right hand side) #
    b ~ a
    
    # The included standard library has a print() function. Sorry, no built-in. #
    print( b() ) # Prints 5 #
    
    # Lost pointers cause no memory errors or leaks. A new function is created instead. #
    a = 1
    print( b() ) # Prints "{fn}". #
    
    # Strings are between double-quotes only. #
    c = "hello world"
    
    # Members are automatically created upon usage. #
    a.child = "woot"
    
    # Shortcut zero-argument function calls using ":" #
    print( a.child: )
    
    # Getting and setting members can be done using their names. #
    name = "five" set_member(d name: 5)
    print( type_of( member(d name:) ) ) # Prints "number" #
    
    # Creating objects with members... #
    e = [ my_member = 0 other_member = 1 ]
    
    # Create functions using {} #
    f = {
        x = false
        # if structures and loops require brackets #
        loop {
            # not() takes only 1 parameter,
            but all(), any(), nall(), and none()
            can take any number of parameters. #
            if ( not(x:) ) {
                x = true
                skip    # Restart the loop #
    
            # An elif could go here too #
            } else {
                stop    # Escape the loop #
            }
        }
    }
    
    # Object-funtions have members and a body of executable code. #
    g = [ a b=2 ] {
        # Access members inside a function via "this" pointer #
        ret( [a=a, b=this.b] )
    }
    
    # Commas are optional for separating expressions,
    parameters, or arguments. #
    h = g(3), print( h.a:, " ", h.b: ) # Prints "3 2" #
    
    # The parent of the variable whose function is called
    can be accessed with "super" #
    i = {
        super.kid = 2
    }
    # ... and it doesn't matter who that parent is... #
    j.a = i
    j.a:
    print(j.kid:) # Prints 2 #
    
    # Combine object member sets with union #
    k = union(h j [b=1])
    print( k.a:, " ", k.kid:, " ", k.b:) # Prints "3 2 1" #
    
    # Creating and accessing lists... #
    l = list(1 2 [p]{ print("p == ", p:) })
    append(l: 4)
    prepend(l: 0)
    insert(l: 2 "arf")
    m ~ item_at(l: 4)
    m("hi")
    erase(l: 0)
    swap(l: 1 2)
    replace(l: 0 "front")
    
    # Function pointers are saved in lists... #
    n = { print("hey") }
    o = list(n)
    p ~ item_at(o: 0)
    p: # Prints "hey" #
    n = {}
    p: # Prints warning of empty function container #
    
    # ... unless copied #
    n = { print("hey") }
    o = list( copy_of(n) )
    p ~ item_at(o: 0)
    n = {}
    p: # Prints "hey" #
    
    # Sub-lists have to be made from valid indexes
    that map to the range 0 to list size. #
    q = sublist(o: 0 length(o:))
    
    # Sub-lists are linked to the items in the original object. #
    r ~ item_at(q: 0)
    r: # Prints "hey" #
    o = {}
    r: # Prints warning of empty function container #
    
    # Strings can be checked for matching value and concatenated #
    if ( matching("fn", type_of(s:)) ) {
        print( concat( "some ", "string " ) )
    }
    

    See the side bar. Make sure to have the "copper" tab active.

    Note: A centered-text version is available in the old documentation.


    The Virtual Machine

    For information about how to incorporate the virtual machine in a project, see the next section.

    If you wish to modify the virtual machine, you will want to read about how it works.


    Getting Setup

    The virtual machine is composed of a few files located in the Copper/src directory. These files can be dropped right into your project. They are:

    To use the virtual machine, include Copper.h in the C++ file where you with to use Copper.

    The main components of the virtual machine are in the namespace Cu. The string, list, and robin-hood hash table classes are in the namespace util.

    #include <Copper/src/Copper.h>
    #include <Copper/stdlib/Printer.h>
    #include <Copper/stdlib/InStreamLogger.h>
    #include <exts/Math/basicmath.h>
    #include <exts/Time/systime.h>
    
    int main()
        Cu::Engine engine;
    
        // Example incorporation of extras
        CuStd::Printer printer;
        CuStd::InStreamLogger streamLogger; // dual-role class
        engine.setLogger(&streamLogger);
        engine.addForeignFunction(util::String("print"), &printer);
    
        // Example of setting engine feature
        engine.setStackTracePrintingEnabled(true);
    
        // Quick incorporation of offered extensions
        Cu::Numeric::addFunctionsToEngine(engine);
        Cu::Time::addFunctionsToEngine(engine);
    
        // Running the engine
        Cu::EngineResult::Value  result;
        do {
            result = engine.run( streamLogger ); 
        } while ( result == Cu::EngineResult::Ok );
    
        return 0;
    }
    

    Within your code, create an instance of the Engine. The Engine is the main component of the virtual machine. It performs all of the parsing and execution of operations. The Engine can be started with the function run(), which returns a value of the enum Cu::EngineResult::Value. A return value of Cu::EngineResult::Ok means processing is proceeding normally and more input can be given. A return value of Cu::Engine::Done means that the engine is finished (either because it encountered the end of the input stream or an exit token) and all but the global stack has been deleted. A return value of Cu::Engine::Error means an error occured and all but the global stack has been deleted.

    The Engine run function must be provided with an input source of bytes. All input sources must inherit from Cu::ByteStream - an interface with two virtual methods that must be implemented. These are:

    The Engine has a built-in filter for determining if certain sets of bytes compose valid names, but a custom filter can be added using the Engine method setNameFilter(). See the API for more information.

    An instance of an engine isn't useful on its own. You will need to add foreign functions to it. Some have been provided. See the provided extensions section for more information.

    Compiling the Virtual Machine

    If you wish to test the virtual machine, a premake5 project file has been provided. Premake can generate both GCC make files and Visual Studio project files.

    Premake can be downloaded from here.

    Messages: Info, Warnings, Errors

    The virtual machine produces four types of messages: Info, Warning, Error, and Debug.

    Information messages provide general information about the state of the virtual machine.

    Warning messages indicate abnormal activity found in the code. Such activity can result in unintended consequences, and thus warnings should never be ignored. However, the virtual machine will proceed to run and not crash.

    Error messages indicate a corrupted and possibly fatal state of the engine. Syntax errors are considered fatal. For why Copper has no exceptions system, see the FAQ. Foreign functions may also throw errors, which may be non-fatal but would likely result in undesirable consequences.

    The virtual machine may also throw errors identified as "system errors", in which case the virtual machine code has a bug. If you encounter this, please report it by opening a ticket on the project's Github bug page and providing the necessary info to reproduce the bug.

    Debug messages are those that provide information useful for debugging the engine itself. They can only be enabled with certain compiler flags near the top of Copper.h.


    Foreign Functions

    Adding foreign functions to the virtual machine requires creating or using some class that descends from class Cu::ForeignFunc and implements the method virtual ForeignFunc::Result call( FFIServices& ). The method call() must return ForeignFunc::FINISHED if there is no error. For errors, it can return ForeignFunc::NONFATAL or ForeignFunc::FATAL. Finally, the foreign function can end the virtual machine processing by returning ForeignFunc::EXIT. FFIServices is an interface providing useful methods for interacting with the engine. These methods can be found in the API.

    Once given to the engine, the ForeignFunc classes will have their reference-counts incremented until the destruction of the engine.

    A number of helper classes have been provided for both adding foreign functions (both standard functions and class methods) without the need to create the wrapper yourself. These are given in the following table.

    class MyFF : public Cu::ForeignFunc {
    public:
        virtual Result call( FFIServices& ffi ) {
            ffi.setNewResult( new BoolObject(true) );
            return FINISHED;
        }
    };
    
    // Convenient ways of adding it to an instance of Engine.
    Engine engine;
    // Option 1:
    addForeignFuncInstance<MyFF>(engine, String("myff"));
    // Option 2:
    addNewForeignFunc(engine, String("myff"), new MyFF());
    // Option 3:
    MyFF* myff = new MyFF();
    addForeignMethodInstance(engine, String("myff"), myff, myff::call);
    
    Function Description
    addForeignFuncInstance<T>( Engine&, const String& ): void Adds a ForeignFunc descendent instance to the engine using the given string as its address/name.
    addNewForeignFunc( Engine&, pName: String&, ForeignFunc* ): void Adds a ForeignFunc descendent to the engine and dereferences it. It assumes you are passing it a new object allocated on the heap.
    addForeignFuncInstance( Engine&, pName: const String&, ForeignFunc::Result (*pFunction)( FFIServices& ) ): void Adds a function with the call(FFIServices&):ForeignFunc::Result prototype to the engine as a foreign function.
    addForeignMethodInstance( pEngine: Engine&, pName: const String&, pBase: BaseClass*, bool (BaseClass::*pMethod)( FFIServices& ) ): void Adds a method from an instance of BaseClass to the engine as a foreign function.

    Callbacks

    Copper allows for callbacks that can be run after the engine finishes normal execution. Functions will be inside of FunctionObjects when passed to foreign functions. There is no need to extract the function. However, since objects in Copper have limited lifetimes, you will need to either copy the function to a new FunctionObject or call the method FunctionObject::changeOwnerTo() and provide a new owner (whose lifetime you control). Copying is simpler.

    Foreign Objects

    In implementation, all objects in the Copper language are C++ objects that extend the class Cu::Object. Anything extending this class can be used as an object. However, a few things are required.

    1. All objects must set their object type upon instantiation by passing it to the parent Cu::Object constructor. This type is of the enumeration ObjectType::Value. You may cast other values to this enumeration and make your own extended enum starting with the value Cu::ObjectType::UnknownData + 1.
    2. All objects must implement the following virtual methods:
      • copy(): Object*

    Optionally, objects may have an implementation of virtual void writeToString(String& out) to set the data to be returned when the object appears in string-requiring contexts.

    Optionally, objects may have an implementation of virtual const char* typeName() const for better printing information and type comparison via have_same_typename().

    Foreign objects intended to work as numbers should inherit the NumericObject class and implement its virtual methods. Objects inheriting this type must implement virtual bool supportsInterface( ObjectType::Value ) and return true for ObjectType::Numeric.

    Complex Type Support

    Sometimes objects inherit multiple interfaces and all are Copper Objects. Rather that invent new types that encompass and indicate certain mixed interfaces, Copper provides two virtual methods to enable multiple interface support. Either method can be optionally overridden to suit the need.

    isSameData() returns true when the data for both objects is the same. For example, it may return true when two numeric types use the same representation and have the same value. It could also be used for checking if pointer addresses to data are the same. The primary usage of this method is for equality but is not the same as numeric equality.

    supportsInterface() returns true if the object can be safely casted to the class type represented by the given ObjectType::Value value.


    Provided Extensions

    There are two sets of extensions. The first set is in the Copper/stdlib directory. This set contains files whose primary purpose is basic input and printing to allow for debugging of the engine. The second set is in the exts directory. It contains all other extensions, especially those for time and math operations.

    stdlib Directory Extensions

    Class Header File Description
    InStreamLogger InStreamLogger.h Class for using a File object (defaulted to stdin) as the input for an instance of the engine. It requires EngMsgToStr.
    EngMsgToStr EngMsgToStr.h Class for converting engine error flags to English messages.
    Printer Printer.h Class for printing the writeToString values of Cu::Object descendents to string.
    FileInStream FileInStream.h Class for opening a file from a given string and feeding it to Engine::run() as a ByteStream.
    StringInStream StringInStream.h Class for feeding a given string to Engine::run() as a ByteStream.

    exts Directory Extensions

    Some extensions have an addFunctionsToEngine(Cu::Engine&). This function will add all of the foreign functions associated with the extension to the given Cu::Engine instance using default names. Currently, the extensions with this feature are:

    The default names of functions provided by these extensions are given in the following table.

    Extension Default Function Names
    cu_info sys_lang_version sys_vm_version sys_vm_branch
    cu_basicmath pow floor ceil sin cos tan
    cu_systime get_time get_seconds get_milliseconds get_nanoseconds
    cu_stringmap string_map
    cu_stringbasics str_overwrite str_byte_length str_byte_at
    t = get_time:
    print( "nanoseconds = ", get_nanoseconds(t:)
    

    With the exception of get_time, the systime functions are designed to extract information from the data returned from get_time. Internally, get_time uses clock_gettime.


    API

    Types

    Alias Type File
    Cu::UInteger typedef unsigned int Copper.h
    Cu::Integer typedef long int Copper.h
    Cu::Decimal typedef double Copper.h
    uint define unsigned int RHHash.h

    util::List

    File: utilList.h

    Basic doubly-linked list used internally by the virtual machine.

    Member Description
    List() Constructor
    List( const List& ) Copy Constructor
    ~List() Deconstructor
    operator: T& Access element by index.
    getFromBack( pReverseIndex: uint ): T& Access element by reverse index.
    getFirst(): T& Returns the first element.
    getLast(): T& Returns the last element.
    getConstFirst(): const T& Returns the first address as immutable.
    operator= ( const List& ): List& Assignment
    size(): uint Returns the number of nodes in the list.
    has(): bool Returns true if the list has at least one node.
    clear(): void Removes all of the elements in the list.
    remove( pIndex: const uint ): void Removes the element at the given index.
    removeUpTo( stopIter: const Iter& ): void Removes all of the elements up to the given iterator.
    push_back( pItem: const T& ): void Appends an item to the back of the list.
    push_front( pItem: const T& ): void Appends an item to the front of the list.
    pop(): void Removes the last element in the list.
    pop_front(): void Removes the first element in the list.
    append( const List& ): void Copies the given list to the end of this one.
    start(): Iter Returns an iterator at the start of the list.
    constStart(): ConstIter Returns an iterator at the start of the list.
    end(): Iter Returns an iterator at the end of the list.
    indexOf( const Iter& ): uint Returns the index of the given operator.
    validate(): void Verifies that this list is internally correct.

    util::List::Iter

    Iterator class for the utility list.

    Member Description
    Iter( List& ) Constructor
    Iter( const Iter& ) Copy Constructor
    operator*(): T& Returns the address of the data.
    getItem(): T& Returns the address of the data.
    operator->(): T* Returns a pointer to the data.
    setItem( pItem: T ): void Sets the data.
    operator==( const Iter& ): bool Checks to see if the given iterators point to the same node.
    operator=( Iter ): Iter& Shared assignment. This and given iterator now point to the same node.
    set( Iter ): void Shared assignment. This and given iterator now point to the same node.
    prev(): bool Moves iterator to the previous node, returning true if the move was made.
    next(): bool Moves iterator to the following node, returning true if the move was made.
    peek(): T& Returns the address of the node following the node where this iterator is located. (UNSAFE)
    reset(): void Sets this iterator to the first node in the list.
    makeLast(): void Sets this iterator to the last node in the list.
    atStart(): bool Returns true if this iterator is at the first node in the list.
    atEnd(): bool Returns true if this iterator is at the last node in the list.
    has(): bool Returns true if there is at least one element in the list this iterator uses.
    insertBefore( pItem: const T& ): void Inserts the given item into a new node prior to the one pointed to by this iterator.
    insertAfter( pItem: const T& ): void Inserts the given item into a new node following the one pointed to by this iterator.
    destroy(): void Deletes the node pointed to by this iterator. Iterated is invalidated.

    util::List::ConstIter

    Const version of Iter for situations where the data in the list must be immutable. All methods are the same with the exception of Iter::destroy(), for which there is no replicate in this class.

    util::CharList

    File: Strings.h

    String-builder class used internally by the virtual machine.

    Member Description
    CharList() Constructor
    explicit CharList( const char* ) Constructor
    CharList( pString: const char*, pLength: uint ) Constructor
    CharList( pString: const String& ) Constructor
    ~CharList() Deconstructor
    operator= ( const CharList& ): CharList& Assignment
    append( const CharList& ): CharList& Appends the given list onto this one.
    append( const String& ): CharList& Appends the given string onto this one.
    equals( const char* pString ): bool Checks to see if the list and C-style string contain exactly the same characters.
    equals( const CharList& pOther ): bool Checks to see if the two lists contain exactly the same characters.
    equalsIgnoreCase( const CharList& pOther ): bool Checks to see if the two lists contain the same letters.
    appendULong( const unsigned long pValue ): void Converts the unsigned long value to characters and appends it to the list.

    util::String

    File: Strings.h

    Basic string class used internally by the virtual machine.

    Member Description
    String() Constructor
    String( const char* ) Constructor
    String( const String& ) Copy Constructor
    String( const CharList& ) Constructor
    String( const char ) Constructor
    virtual ~String() Deconstructor
    steal( String& ): void Steals the data pointer from the given string unless they point to the same array.
    operator= ( const String& ): String& Assignment
    operator= ( const char* ): String& Assignment
    operator= ( const CharList& ): String& Assignment
    operator+= ( const String& ): String& Append a string to this one
    operator: char Character access
    c_str(): const char* C-style string return
    size(): uint Length of the string
    equals( const String& ): bool Checks to see if strings match.
    equals( const CharList& ): bool Chects to see if this string and list match in characters.
    equals( const char* ): bool Checks to see if strings match.
    equalsIgnoreCase( const String& ): bool Checks to see if the lower case versions of these strings match.
    toInt(): int Returns the numeric reading of the string as an integer.
    toUnsignedLong(): unsigned long Returns the numeric reading of the string as an unsigned long.
    toFloat(): float Returns the numeric reading of the string as a float.
    toDouble(): double Returns the numeric reading of the string as a double.
    purgeNonPrintableASCII(): void Removes invisible characters (newlines, tabs,..).
    contains( char ): bool Returns true if the string contains the given character.
    numberType(): unsigned char Returns the best numeric type for representing the string. 0 for no type, 1 for integer, 2 for a decimal type.
    keyValue(): uint Returns a unique value representing this string that is usable in a hash map.

    util::RHHash<T>

    File: RHHash.h

    Robin-Hood Hash Table implementation.

    Member Description
    RHHash( uint ) Constructor
    RobinHoodHash( const RobinHoodHash<T&>& ) Copy constructor
    ~RobinHoodHash() Destructor
    appendCopyOf( RobinHoodHash<T>& ): void Copies the given hash table to this one.
    get( pIndex: uint ): Bucket* Returns the bucket at the given index.
    getBucketData( pName: const String& ): BucketData* Returns the data stored in the given bucket.
    insert( pName: const String&, pItem: T ): T* Inserts the item into the table with the given name.
    getSize(): uint Returns the allocated size in memory of the table.
    getOccupancy(): uint Returns the number of elements in the table.
    erase( pName: const String& ): void Removes an element from the table.

    util::RHHash::BucketData<T>

    File: RHHash.h

    Member Description
    name: const String Bucket name
    item: T Element data
    delay: uint Number slots the bucket is located away from the slot its name's hash value gives
    BucketData( pName: const String&, pItem: const T& ) Constructor
    BucketData( pOther: const BucketData& ) Copy Constructor

    util::RHHash::Bucket<T>

    File: RHHash.h

    Member Description
    data: BucketData* Pointer to data
    wasOccupied: bool Indicates if this data had been full once before
    Bucket() Constructor
    ~Bucket() Deconstructor
    clear(): void Data removal

    Cu::Ref

    Reference-counted objects. Used as a base for other objects.

    Member Description
    ref(): void Increments the reference count.
    deref(): void Decrements the reference count.
    getRefCount(): int Returns the reference count.

    Cu::RefPtr<T>

    Reference-counting pointer for objects descending from Cu::Ref.

    Member Description
    RefPtr() Constructor
    RefPtr( const RefPtr& ) Copy Constructor
    ~RefPtr() Deconstructor
    set( pObject: T* ): void Sets the pointer.
    setWithoutRef( pObject: T* ): void Sets the pointer and calls deref() without calling ref().
    operator= ( pOther: const RefPtr ): RefPtr Shared assignment.
    obtain( pStorage: T*& ): bool Sets the given pointer with the object stored here and returns true if there is an object.
    raw(): T* Directly returns the address stored here (unsafe).

    Cu::Object

    Base class for all objects (object-functions and data) in Copper.

    Member Description
    Object( ObjectType::Value ) Constructor
    virtual ~Object() Destructor
    getType(): ObjectType::Value Returns the object type (Function, Bool, String, List, Integer, Decimal, or UnknownData).
    operator Object*() Implicit cast.
    virtual copy(): Object* Meant to be overridden. Returns a copy of this object.
    virtual writeToString( out: String& ): void Meant to be overridden. Sets the given string to a string representation of this object's value.
    virtual typeName(): const char* Meant to be overridden. Returns a C-style string name of this object's type.

    Cu::Object Descendents

    Class Name Description
    FunctionObject Object-function container component. This contains Function, the class containing all of the function data.
    BoolObject Basic boolean class.
    StringObject Basic string class wrapping util::String.
    NumericObject Parent class for all number types.
    IntegerObject Basic integer class wrapping Cu::Integer. (Descendant of NumericObject.)
    DecimalNumObject Basic decimal class wrapping Cu::Decimal. (Descendant of NumericObject.)
    ListObject Doubly-linked list class with a special Node subclass for owning FunctionObject.
    ObjectTypeObject A wrapper class for ObjectType::Value values.


    Cu::LogMessage

    Struct for passing logging information to the logger. All engine messages are passed as LogMessage instances via Logger::print( LogMessage& ). The set fields depend on the messadeId.

    Member Type Description
    level LogLevel::Value Category of the message ( info, warning, error, or debug ).
    messageId EngineMessage::Value Internal message ID associated with certain strings. Use getStringFromEngineMessage() in EngMsgToStr.h to get the text.
    functionName String Name of the foreign function in which the message originated.
    systemFunctionId SystemFunction::Value If the message originated from a built-in function, this will be set.
    argIndex UInteger Index of the argument causing the message if applicable.
    argCount UInteger Number of arguments given to the function.
    givenArgType ObjectType::Value The type of the object given as the argument.
    expectedArgType ObjectType::Value The expected type of object the argument was supposed to be.
    givenArgTypeName String The name of the type of the object given as the argument.
    expectedArgTypeName String The name of the expected type of the object the argument was supposed to be.
    customCode UInteger Use this for your own purposes. If using this, set messageId to EngineMessage::CustomMessage.

    Cu::ForeignFunc

    Ancestor class to be inherited by all classes that need to interact with the virtual machine.

    Member Description
    virtual call( FFIServices& ): ForeignFunc::Result Meant to be implemented. Called by the Engine. Returns ForeignFunc::FINISHED when complete.

    Cu::FFIServices

    Interface for foreign functions to interact with the engine.

    Member Description
    FFIServices( Engine&, ArgsList, String ) Constructor (DO NOT USE)
    ~FFIServices() Destructor (DO NOT USE)
    getArgCount() const: UInteger Returns the number of arguments sent to the function.
    demandArgCount( UInteger, bool ): bool Returns true if the number of arguments matches the given value.
    demandArgCountRange( UInteger, UInteger, bool ): bool Returns true if the number of given arguments is within the given range.
    demandMinArgCount( UInteger, bool ): bool Returns true if the number of given arguments is at least equal to the given value.
    demandArgType( UInteger, ObjectType::Value ): bool Returns true if the argument at the given index was of the given type.
    demandAllArgsType( ObjectType::Value, bool ): bool Returns true if all of the given arguments are of the given type.
    arg( UInteger ): Object& Returns a pointer to the argument at the given index.
    printInfo( message: const char* ): void Print information to the logger.
    printWarning( message: const char* ): void Print a warning message to the logger.
    printError( message: const char* ): void Print an error message to the logger.
    printCustomInfoCode( UInteger ): void Prints a custom information code to the logger.
    printCustomWarningCode( UInteger ): void Prints a custom information code to the logger.
    printCustomErrorCode( UInteger ): void Prints a custom information code to the logger.
    setResult( Object* ): void Sets the foreign function return. (Unset, it will be an empty function.)
    setNewResult( Object* ): void Sets the foreign function return and dereferences it. (Same purpose as setResult().)

    Cu::Engine

    Main interpreter component; parser, lexer, processor.

    Member Description
    Engine() Constructor
    setLogger( Logger* ): void Sets the interface used for printing engine messages such as errors.
    print( logLevel: const LogLevel::Value, msg: const char* ): void Prints a message to the logger.
    print( logLevel: const LogLevel::Value, msg: const EngineMessage::Value ): void Prints a message to the logger.
    setEndofProcessingCallback( callback: EngineEndProcCallback* ): void Sets the interface used for responding when the processing has ended.
    setIgnoreBadForeignFunctionCalls( yes: bool ): void Sets whether to allow bad calls to foreign functions to proceed by returning empty function rather than terminating processing with an error.
    setOwnershipChangingEnabled( yes: bool ): void Sets whether to allow the own structure to work.
    setStackTracePrintingEnabled( yes: bool ): void Sets whether to show the stack trace upon error.
    setNameFilter( bool(*filter)(const String&) ): void Sets the function used for filtering the characters permitted in names.
    addForeignFunction( pName: const String&, pFunction: ForeignFunc* ): void Adds a foreign function for use in Copper.
    run( ByteStream& ): EngineResult::Value Initiates processing in the virtual machine.
    clearGlobals(): void Clears all global variables. (Globals are not clear when processing completes, so this method is provided.)
    parse( context: ParserContext&, srcDone: bool ): ParseResult::Value Proceeds parsing with the given context. (You should not need to use this function directly.)
    execute(): EngineResult::Value Continues processing in the virtual machine. (You should not need to use this function directly.)
    runFunctionObject( FunctionObject* ): EngineResult::Value Executes the body of code of the given object-function. (Used for callbacks.)


    How the Virtual Machine Works

    First and foremost, most objects in the virtual machine are reference-counted using the dedicated classes Ref and RefPtr. The class RefPtr has two methods that must be used for setting it: set() and setWithoutRef(). The former should be used when the data is shared. The latter does not increment the reference counting; it is intended for directly passing newly-created heap objects to the pointer.

    The Copper virtual machine Engine class is the main component and the bulk of the virtual machine. To run the virtual machine, a bytestream is passed to the method Engine::run(). The engine will then attempt to exhaust the byte source and convert its contents into tokens. Standard ASCII whitespace (simple space, newlines, tab) is treated as a deliminator. When the engine encounters a set of bytes that it cannot lex, it considers it a name and checks it for validity. Optionally, a name-filter can be given to the engine, and this filter will be passed the strand of unknown bytes as a string.

    Once tokenized, the engine proceeds to interpret the strand of tokens using a state machine. When an operation is found to be incomplete, the machine checks for more input. If more input is available (as is the case with console input), the state machine will wait. If not, the machine will terminate in error.

    The parsing aspect of the machine has been made publically available for the sake of nested code bodies. The interpreter will not parse code bodies until they are activated as part of a function call. However, lexing occurs immediately upon received input, so invalid tokens are identified immediately upon entry into the virtual machine.

    The parsing system is a large state-machine. Rather than waiting for a syntactically-complete input and possibly using a regex, the parsing machine accepts any input and simply tracks its progress through parsing by using "parse tasks". The reason for this system is partially due to the grammar of the language; no explicit statement termination token means interrupted console input should (in many cases) default to termination.

    The final result of parsing is a strand of "Opcode" instances that contain both instructions and some of the data necessary to complete those instructions. Opcodes can contain the addresses of variables, bodies of code, simple data (numbers and strings), or pointers to other parts of the strand (for use in if-structures and loops).

    The addresses of variables are special structures that contain lists of byte strings, which are the components of the address. An address represents the full call path of the object they represent from within the scope where they are from. Built-in functions, foreign functions, and user-created functions are all represented by variable addresses.

    Bodies of code, as represented in both instances of Function and in instances of Opcode, are containers for both a list of unparsed tokens and a list of Opcode instances. The former is converted to the latter via an Engine::parse() call that utilizes its parser context. Bodies of code are shared by instances of Function and remain constant after their conversion.

    The engine makes extensive use of the class FunctionObject. The class Function holds the persistent scope of the object-function as well as the body of executable code, but the class FunctionObject acts as the "Copper object" wrapper for Function. FunctionObjects can be "owned" by a class inheriting from class Owner. Once owned, the lifetime of the Function class inside of FunctionObject is tied to lifetime of the class inheriting Owner.

    Variables are one of the descendents of class Owner.

    When processing the opcodes, the engine first checks the type and the attempts to access the associated data from inside the opcode.

    Some opcodes create tasks inside of the engine when the engine processes them. In some cases, opcodes are dependent on each other and therefore must follow each other in the correct order or they can crash the engine. In some cases, certain opcodes can be run as shortcuts for tasks.

    For example, creating an object-function definition requires opcodes for initiating the task - either starting from the object body component or from the execution body component - and terminating the building. Among the object body component opcodes, there is an opcode for adding names to the list of parameters to the function as well as an opcode for adding names to the persistent scope. The object body component opcodes are all optional.

    Function calls in the Engine can only be made when appended to addresses. The address for the call is first checked against a list of built-in function names. If it matches, the built-in function is called. If not, foreign function names are checked for a match. If the address matches a foreign function, the arguments are checked to be sure they match the required parameters of the foreign function.

    If no match is found among foreign functions, it is assumed to be a user function. The engine first searches the local scope for a variable whose existence matches the full address. If no match is found, the global scope is searched. If the base variable of the path does not exist in either scope, all of the members of its address are created in the local scope.

    The engine transmits Copper data from place to place using the RefPtr lastObject. This special pointer is set by almost everything from function calls to data creation. Even the foreign function interface (FFIServices) methods set this object. Once set, this data can be used in variable assignments and pointer assignments both inside and outside function building.

    The engine uses a number of enumerations for its processes. Most of these enums are struct enums, meaning that they are enumerations that are each wrapped in struct. A small number are regular enums and not meant to be used or invoked outside of the engine.

    The engine uses a set of flags (from an enumeration) for identifying current state of processing and tell the main processing loop whether or not it should proceed. These have an enormous effect on the state of the engine, so care should be used in passing them. For example, the main engine loop accepts return flags that exit the engine (when the exit token is found), pop the opcode operations stack (which occurs at the end of a user-function call), continue processing, and throw an error (which destroys most of the stack).

    Debugging

    The virtual machine is equipped with a number of features for debugging it. At the top of the Copper.h file are a set of enumerations that will trigger the sending of messages to the logger. These messages reveal the function calls inside particular components of the virtual machine. Some of these components are isolated and cannot print to the logger. These require a std::printf() be available, so to use them, uncomment the #include <cstdio> near the top of the Copper.h file.

    Speed Profiling

    Speed profiling code has been made available for testing certain components in the engine. However, if you wish to check the overall speed of Copper, it is recommended that you use the Time/systime extension.

    FAQ

    What is the parse tree?

    The parse tree is available in an article on the official blog.

    What numeric operations are available?

    Only basic operations are available: absolute value, addition, subtraction, multiplication, division, increment/increase, decrement/decrease, and the comparisons of equality, less than, less than or equal, greater than, and greater than or equal. The NumericObject parent class allows you to implement these operations for descendent classes so you can create wrappers for other math classes such as GNU MP.

    Why is there no privacy model?

    Because it would be absurd.

    See this blog post for a more detailed answer.


    Why is there no exceptions system?

    They generally aren't needed (except in tragic cases where they can't be handled). If an error occurs, in most cases an empty function is returned, and the system function are_empty() can be used to check this. To halt the program, you can use the system function assert(). The engine can also be set to halt (rather than warn) when foreign functions fail.


    Why have own if it's unsafe?

    Some programmers might prefer a model whereby they use a designated constructor function for producing objects before passing them off to the rest of the program. However, certain ways of doing this require changing ownership to prevent the destruction of those objects when the stack frame closes. This practice, when done correctly, is still memory-safe.

    Technically, any program can be written in such a way that avoids this practice, but rather than force programmers into a particular model, the virtual machine leaves it up to user discretion.

    By default, the feature is disabled and can only be enabled by passing true to Engine::setOwnershipChangingEnabled().

    Benchmarks? How fast is Copper?

    Tests run on an HP Pavilion AMD-64 Dual-core rated at 2.1~2.3GHz using the Time/sysclock and Math/basicmath extensions.

    Primes Less Than 5000

    Primes Less Than 5000

    # Primes Less Than 5000 #
    isprime = [n] {
        i = 2
        loop {
            if ( gte(i: n:) ) { stop }
            if ( equal(%(n: i:) 0) ) { ret(false) }
            ++(i:)
        }
        ret(true)
    }
    primes = [n] {
        count = 0
        i = 2
        loop {
            if ( gte(i: n:) ) { stop }
            if ( isprime(i) ) {
                ++(count:)
            }
            ++(i:)
        }
        ret(count:)
    }
    benchmark = {
     st = get_time:
     print("primes: " primes(5000))
     et = get_time:
     st_ms = get_milliseconds(st:)
     et_ms = get_milliseconds(et:)
     print("\nStart time = " st_ms: "\nEnd time = " et_ms:
        "\nTotal time = " -(et_ms: st_ms:))
    }
    

    Sample run times are in milliseconds.

    Time Run 1 Run 2 Run 3
    Start 5 16534 32879
    End 16534 32879 49199
    Total 16529 16345 16320

    Roughly 16 seconds per run.

    First 1000 Fibonacci Numbers

    First 1000 Fibonacci Numbers

    # First 1000 Fibonacci Numbers Test #
    benchmark = {
     st = get_time:
     fib_gen = {
      ret([a=1. , b=0]{
       c = +(this.a: this.b:)
       this.b = this.a
       this.a = c
       ret(this.b:)
     })}
    
     f = fib_gen:
     i = 0
     loop {
      if ( equal(i: 1000) ) { stop }
      ++(i:)
      print("fib " i: " = " f: "\n")
     }
     et = get_time:
     print("i == " i: "\n")
     st_ms = get_milliseconds(st:)
     et_ms = get_milliseconds(et:)
     print("\nStart time = " st_ms: "\nEnd time = " et_ms:
        "\nTotal time = " -(et_ms: st_ms:) "\n")
    }
    

    Sample run times are in milliseconds.

    Time Run 1 Run 2 Run 3 Run 4
    Start 5 108 200 296
    End 108 200 296 401
    Total 103 92 96 105

    Can I do builder notation?

    No. Even though returning a pointer from a function is still safe in Copper, the grammar of Copper dictates that function calls end a statement. This allows for relaxed programming because statement-ending tokens are not required.

    Can I use it with C?

    Sorry, nothing is wrapped in extern "C", but since there are only a small handful of files, it shouldn't be hard for you to do this yourself.

    Comparison to AngelScript?

    Intepreter Comparison

    The following table summarizes the notable differences.

    AngelScript Copper
    Module setup required No modules
    Reads from files Reads from input class
    Compiles to binary Uses simple opcodes
    Expects to be on application top level Can be created anywhere in an application

    Note: File readers are available for Copper in the standard library.

    Language Comparison

    The following table summarizes the notable differences.

    AngelScript Copper
    Variables store multiple types Variables store 1 type
    Variables directly return their values based on context A variable's object-function must be called to get its value
    Pointers are part of the variable's type Pointers are created using pointer-assignment operator
    Permits null but throws exceptions No null but has assert function

    Comparison to ChaiScript?

    Intepreter Comparison

    The following table summarizes the notable differences.

    ChaiScript Copper
    Requires C++11 C++ version agnostic (98+)
    C++11 hooks can be created with lambda Hooks are created via inheritance

    Language Comparison

    The following table summarizes the notable differences.

    ChaiScript Copper
    Mathematical operations are built-in Basic operations are built-in, extras in extensions
    Dynamic objects must be created with special constructor Dynamic objects are default
    Null pointers allowed No null

    Comparison to Lua?

    Lua Copper
    C, requiring wrappers for C++ C++
    Direct stack-control Private, self-managed stacks; FFI provided
    Tables-based variables Object-oriented
    Whitespace-sensitive White-space is irrelevant
    Nil allowed locally but nil deletes globals No null/nil

    Is Copper Thread-Safe?

    The Copper interpreter is designed to run on one thread. However, within the interpreter itself, it may be feasible to add multi-threading capabilities in the future.