Throughout the guide, code samples will be provided. If using a javascript-enabled browser, you may hover your pointer / tap the code to get fine details about the syntax used. For example:
@:myFunction = ::{ print(:"Hello, world!"); } myFunction();
// A comment. /* A multi-line comment. */As such, every line that isnt a comment is part of a statement. Statements may span multiple lines, but statements may optionally end in a ";" If missing, the compiler will attempt to determine the proper ending of the statement through newline detection.
Variables are declared with the @ symbol:
// Normal variable. @myVariable // Adding a colon ':' between the @ and the variable name // will make the variable immutable. // Immutable values that are modified will fail to compile. @:immutableVariable = 42You can add an inline assignment with the declaration as well:
@myOtherVariable = 3 // Okay to use myOtherVariable since it was declared. @another = 4 + myOtherVariable // Displays 7 print(:another);Variable names can contain any non-symbol character (other than _, which is allowed).
// empty is a special value. @e = empty // Values that are declared but NOT set are // given the empty value. This includes // function parameters that were not bound // by callers. @f
// Number values @a = 10; @b = 1.4; @c = (1 * 7.8) / 2; @d = (a + b) / 2; @e = (a ** 3);
// string values can take single or double quotes. a = '10'; b = "10"; c = "A String!";
// boolean values a = false; b = true; c = (1 == 2); // should be false d = (10 == 10); // should be true
// objects. They follow javascript style syntax. a = {}; b = { member : 12, 'otherMember' : 30 }; // list initializer. Equivalent to setting keys with numbers. c = ['a', b, 3];
// Functions can be made and assigned. a = ::{}; // They can also be run immediately as part of an expression. // Result would be "5" result = ::<={ @a = 2; @b = 3; return a + b; } // functions can also capture values that persist beyond // the lifetime of its original function counter = ::<={ @value = 0; return ::{ value += 1; return value; } } // returns 1.. then 2... then 3 counter(); counter(); counter();
// Types can be gleaned by calling the type query. // Introspective queries are covered later. @f = 40->type // will print "true" print(message:f == Number); // Types can be used as functions to explicitly convert between types. // will print 3 print(message:2 + Number(from:true));
// declare a new variable that is a function. It takes one argument // that can be referred to by "myString" @:myFunction = ::(myString) { print(:myString); } // run the function with one argument. // Should print Hello, world! // Remember: arguments are bound when called, so they must be named. myFunction(myString:'Hello, world!'); // If the function accepts only one argument, there the name // can be ommitted optionally. myFunction(:'Hello, world!'); // If a function doesn't have any arguments, you may // omit the function argument parantheses. // // When assigning a function, you may also optionally // omit the '=' @:myOtherFunction :: { return '123'; } // Should print 123 print(:myOtherFunction()); // For small functions that only return a simple expression, // inline function syntax can be used to make it more readable and // concise: @:sqrt ::(value) <- value**0.5 // should print 4 print(:sqrt(:16));
// The function can be called immediately using the // () like you would on a function reference. ::{ print(:"I ran immediately."); }() // The () behaves the same as if it // were a typical variable. ::(argument) { print(:'Hello, ' + argument); }(:'World!!') // Most commonly, it is convenient to // run an entire block with no arguments, like // in the first example. However, excessive () // can make code harder to read in certain // situations. Alternatively, you can use // "dash function syntax" to accomplish the same behavior: ::<={ print(:"I ran immediately, too!"); } // Using anonymous functions in conjunction with // normal ones allows for convenient methods to // store information. @:average = ::<={ @sum = 0; @count = 0; // This new function returned then becomes // "average". Because it references "sum" // and "count", those variables persist // within the average function. return :: (next) { sum += next; count += 1; print(message:total / count); } } // should print 1 average(:1); // should print 2 average(:3);
// Specifies that both a and b must be // of type Number. @:myAdder::(a => Number, b => Number) { return a + b; } // Ok! Note how the order doesnt matter // as the name binding determines which // argument is which. myAdder(b:4, a:10); // Throws an error myAdder(a:'50', b:4);
// The prepended * designates the following name (in this case args) // as the variable argument. An object is created with keys matching // the named arguments specified. @:sumFunction::(*args) { @sum = 0; foreach(args) ::(key, value) { sum += value; } return sum; } // In the sumFunction, these are packed into an object // with the given key-value pair. // Should return 40 sumFunction(a:10, another:30); // In the case of no arguments, an empty object is passed. // Should return 0 sumFunction();
// The argument list can be any Object. @:argList = { a: 1, b: 2, c: 4 } @:fn1::(a, b) { return a+b } // by using specifying the * in front of the // argument, the call will dynamically bind any arguments // that match what is expected. // In this case, it will pull a and b, but ignore c since // its not an expected argument name. // // Should return 3 fn1(*argList);
// Here an object has a function and some data. // Note how the apiFunction contains the special // $ argument. @:obj = { privateData : 42, apiFunction ::($, a) { return $.privateData + a; } } // Because we are accessing the function // from obj, $ gets assigned to obj for the call. // // Should return 72 obj.apiFunction(a:30);
// Fibonacci sequence using "when" @:fib ::(n){ when(n < 1) 0; when(n <= 2) 1; return fib(:n-1) + fib(:n-2); } print(:fib(:5)); // When assigning a function, the '=' can be ommitted optionally @:hi_low_mid ::(a, b) { when(a < b) "lo" when(a > b) "hi" // if no conditionals are true, when // continues on. return "mid"; } // should print lo print(:hi_low_mid(a:10, b:40));
@expression = 1; // The following would print "It's one" match(expression) { // The syntax is (possibility) : conclusion. (0) : print(:"It's zero"), (1) : print(:"It's one") } // match returns an expression, so it can be used like so. // note that ONLY the conslusion print(:match(expression){ (0) : '0', (1) : '1', // if multiple possibilities lead to the same conclusion, (2, 3, 4) : '2, 3, or 4!', default : "don't know!" }); // If it is desired to get the same behavior // as C-like language's "if-else", match can be used // in conjunction with anonymous function expressions // to easily create this structure. @:judgeValue ::(i) { match(true) { (i > 20): ::<={ print(:'greater than 20'); }, (i < 20): ::<={ print(:'less than 20'); }, default: ::<={ print(:'exactly 20'); } } } // should print 'greater than 20' judgeValue(:50);
// Basic for loop, iterates 10 times in a C-style manner using 0-based indices. // // Should print // // 0 // 1 // 2 // 3 // for(0, 4) ::(i){ print(:i); } // If the first value is larger than the second, then // it will loop backwards by 1. // Should print // // 4 // 3 // 2 // 1 for(4, 0) ::(i){ print(:i); }
@object = ['a', 'b', 'c']; // Should print // // 0 -> a // 1 -> b // 2 -> c // foreach(object) ::(key, value){ print(:'' + key + '->' + value); }Additionally foreach is a special query function that allows for iteration.
// Should also print // // 0 -> a // 1 -> b // 2 -> c // object->foreach(::(key, value){ print(:'' + key + '->' + value); });
@myVal = false; // if computes the proceeding expression if the // expression within parentheses computes to true. // should print false if (myVal == false) print(:"myVal is false"); // should not print if (myVal == true) print(:"myVal is true"); // if an "if" statement requires multiple statements, // a new function can be created and immediately run containing the // expressions to be run. However, in most cases, other // structures can be used to make more readable code. // should print 42 if (myVal == false) ::<={ @val = 4 * 8 + 10; print(:val); } print(:'myVal is ' + (if (myVal) 'true' else 'false'));Its important to remember that, in Matte, if is an expression; it computes to a value. Specifically, if returns the computed expression. This makes if equivalent to the ternary ?: seen in some C-like languages.
// Should print '42' print(: (-42)->abs); // Should print '3' print(: 180->asRadians->round); // Should print 'Hello, world!' print(: "%0, %1!"->format(:['Hello', 'World'])) @str = 'Hello, world!'; // Should print 'o' print(:str->charAt(:4)); // Should print '13' print(:str->length); // Should print 'world' print(:(str->scan(:'Hello, [%]!'))[0]);
// Assume we have a function that returns an Object with numbered // keys, each referring to its own Object with numbered keys // (an array of arrays), each holding a boolean. @my2DArray = make100by100(); // The Listen Function syntax runs all statements immediately. @result = {:::} { for(0, 100)::(y) { for(0, 100)::(x) { // If a true cell is found, all functions are stopped // up until the nearest listen function, carrying the expression // 'Found!' with it. when(my2DArray[x][y] == true) send(:'Found!'); } } // If send was not called, the listen function just returns the result // if its function. return 'Not found...'; }
{:::}{ for(0, 100)::(y) { for(0, 100)::(x) { // If a true cell is found, all functions are stopped // up until the nearest listen function, carrying the expression // 'Found!' with it. when(my2DArray[x][y] == true) send(:'Found!'); } } } : { onSend:::(message) { print(:'A message was received: ' + message); } };
@counter = 0; {:::} { forever ::{ // when send is called, forever() is escaped. if(counter == 10) send(); counter += 1; return true; } }
// Throws an error. error(); // The error function can take an argument of any type, like send() error(:'An error occurred'); error(:{ message : 'An error occurred!', id : 10 });Since not handling a message is a fatal error, it is often useful to specify an error catching mechanism. Error catching is similar to catching regular messages with send:
@errorExample :: { {:::} { print(:"1"); print(:"2"); error(:'Uh oh!'); print(:"3"); print(:"4"); } : { onError:::(message) { print(:"The following error was caught: " + message.data); } } } // should print: // // 1 // 2 // The following error was caught: Uh oh! // errorExample(); // this can also be used to protect // your code from external functions that // may encounter an unexpected issue. // // because "mySafeFunction" implements a // catch, errors that propogate from inner functions // will be caught here, preventing further functions // from being interrupted. // // @dangerousFunction ::{ print(:'a'); print(:'b'); error(:'Uh oh again!'); print(:'c'); print(:'d'); } @mySafeFunction ::{ {:::} { print(:1); dangerousFunction(); print(:2); } : { onError::(message){ print(:'Caught external error'); } } } // Should, in total, print: // // Starting safe operation.. // 1 // a // b // Caught external error // Ending safe operation.. // print(:'Starting safe operation..'); mySafeFunction(); print(:'Ending safe operation..');
@privateData = 'Hello, World!'; @myModule = { getData ::{ return privateData; } } return myModule;In another file which is run in the VM:
@module = import(:'module.mt'); // prints 'Hello, World!' print(:module.getData());Note: importing the same module more than once, by default, will just return the same computed result from the first import. Take care if you are using a custom import implementation.
@special = { data : '' } // throws an error special << 'a'; // setAttributes takes the object to modify // and an attributes object, keyed with strings of the // operator to specify overloading with. special->setAttributes(:{ '<<' ::(value) { special.data = special.data + value; } }); special << 'a'; special << 'b'; // prints 'ab' print(:special.data);setAttributes will overwrite the previous operator object if it is different. To retrieve the existing operator object, getAttributes can be called. Here are the list of built-in operators that can be overloaded:
@secret = { internalData : 'dontTouch', internalData2 : 100 } secret->setAttributes(:{ '[]' : { get ::(key) { return 'Bracket access with: ' + key; }, set ::(key, value) { print(:'Bracket set with: ' + key + ' = ' + value); } }, '.' : { get ::(key) { return 'Dot access with: ' + key; }, set ::(key, value) { return 'Dot set with: ' + key + ' = ' + value; } }, 'foreach' ::{ return { 'a' : 100, 'b' : 1000 } } }) // will print 'Bracket access with: test' print(:secret['test']); // will print 'Bracket set with: A = 400' secret['A'] = 400; // will print 'Bracket access with: something' print(:secret.something); // will print 'Bracket set with: otherThing = 600' secret.otherThing = 600; // will print: // a -> 100 // b -> 1000 foreach(secret)::(key, value) { print(:'' + key +' -> ' + value); }
@cool = { asNumber : 42, asString : 'Forty-two' } cool->setAttributes(:{ (String) :: { return cool.asString; }, (Number) :: { return cool.asNumber; } }) // prints 52 print(:10 + cool); // prints 'The number is: Forty-two' print(:'The number is: ' + cool);
// Creates the new Type. The newtype function can take // a special setup argument. @MyBasicType = Object.newType(); // Same as newtype(), except errors will report the name of the type. @MyType = Object.newType(name: 'MyType'); // You can use this to control inheritance @MyInheritedType = Object.newType(inherits: MyType); // Creates a new object with the give type. The type cannot be changed. @myInstance = Object.instantiate(type: MyType); myInstance.data = 'Hello!'; // You can use MyType anywhere you would a built-in Type @myFunction ::(a => MyType) { print(:a.data); }; // Throws an error because the argument // is an objec0t that is not of type "MyType" myFunction(:{data: 'Hi!'}); // prints 'Hello!', since myInstance is of type MyType myFunction(:myInstance); // prints 'Hi!', since myInheritedInstance inherits from MyType @myInheritedInstance = Object.instantiate(type:MyInheritedType); myInheritedInstance.data = 'Hi!'; myFunction(:myInheritedInstance); // should print 'true' since it is inherited. print(:myInheritedInstance->isA(:MyType)); // should print 'false' since the original instance // is not of the inherited type. print(:myInstance->isA(:MyInheritedType));
@:a = [1, 2]; // "b" now contains [1, 2, 3, 4] @:b = [...a, 3, 4]; @:c = { "first" : 1, "second" : 2 }; // "d" now contains all of c's members @:d = { ...c, "third" : 3, "fourth" : 4 };
// Retrieves the class module. // Afterwards 'class' is set to the class function, a special // function that can be used to create classes. @class = import(module:'Matte.Core.Class'); // the class function takes arguments describing the class. @Creature = class( // The name is an optional member. Internally, the class creates a new // Type for all instances of the class, so this name can be provided // to make uses of the class more straightforward if errors occur. name : 'Creature', // The define property is a required member, it is a function that // is called whenever a new instance of the class is requested. // This is where the implementation of your class goes. // 'this' refers to the new instance of the class that is being created. // It is the instance that will be worked with in user code. define :::(this) { // because these are variabels that are local to the define function, it // is common to use these variables as typical "private variables" for // the class. @selfWeight; @selfHeight; @realname; // If specified, the constructor allows for arguments from // user code. This is the actual function thats called // when a user calls "new". As such, the return value of // the constructor is what a user works with. // Most of the time, returning "this" is most appropriate. // // It is also useful to use type restrictions on the constructor to // ensure quality, error-free code. this.constructor = ::( weight => Number, height => Number, name ) { selfHeight = height; selfWeight = weight; realname = if (name != empty) name else 'No Name'; return this; }; // In the define function only, the output object can contain // a special member called 'interface'. The interface function // defines the public members of the instance as members of an object. // For most readability, this interface object is usually just an // object literal. // // Object interfaces can have 2 possible types of members: // 1) functions. If an interface property is a function, // the function will be called as-is with user-provided arguments. // This is convenient, since the interface function can // easily access the local "private" variables. // // 2) variable set/get. If an interface property is an object, the member name // refers to a property. The property is controlled by setters and getter functions // specified with 'get' and 'set'. The return value of 'get' is returned to the user // when accessing the property. the 'set' argument is given to the function when // the user requests to modify the property. this.interface = { // Example of an interface member function. printReport ::{ print(message: realname + ' weighs ' + selfWeight + 'kg and is ' + selfHeight + 'cm tall.'); }, // Another example, this time with a type-strict function. eat ::(foodAmount => Number) { selfWeight += foodAmount / 4; selfHeight += foodAmount / 8; }, // Example of an interface member property. name : { get ::{ return realname; }, set ::(value) { error(detail:'This animal has already been named!'); } }, // Another example of an interface member property. // If the 'set' property is omitted, the property is // automatically registered as read-only, causing it // to throw an error if a user tries to modify it. weight : { get ::{ return selfHeight; } } } } ) // once the class is created. instances can be made. // This is done by taking the class and calling the // new() function of the newly-created class. @hoover = Creature.new( name : 'Hoover', weight : 24, height : 80 ); @billy = Creature.new( name : 'Billy', weight : 10, height : 30 ); // should print 'Hoover weighs 24kg and is 80cm tall.' hoover.printReport(); // should print 'Hoover' print(:hoover.name); // should print 'Hoover weights 30kg and is 88cm tall' hoover.eat(foodAmount:8); hoover.printReport(); // should throw an error, as the set property // call throws an error. hoover.name = 'Sammy'; // should throw an error, since the property is read-only. hoover.weight = 1000000; // should throw an error, since the argument isn't a Number. hoover.eat(:'something'); // should throw an error, what is not part of public interface hoover.what(); @Biter = class( name : 'Biter', // Inheritance is also possible using the inherits property. // It accepts a list of inherited classes. // // When inheriting, the object goes through // inherited types' define function before this one's in order. inherits : [Creature], define :::(this) { // Since we want to initialize these instances using the same // creature constructor, // We can pull the existing constructor and use it as is. // // this.constructor = this.constructor[Creature]; this.interface = { // All classes have a type member that is the Type value for that // class' instances. bite ::(other => Creature.type) { print(message:this.name + ' has bitten ' + other.name + '. Ouch!!'); } } } ) @george = Biter.new( name : 'George', weight : 10, height : 30 ); // Because Biter inherits from Creature, it has all of // Creature's members. george.printReport(); // Should print 'George has bitten Billy. Ouch!!' george.bite(:billy);Johnathan Corkery - https://github.com/jcorks