By Julien McArdle
MPL – Minimalist/Mobile Programming Language 1
Hello World 2
General Syntax 2
Filename Convention 2
Variable Substitution 4
Mathematical Operations 5
Concurrent Operations with Lists 8
Anonymous Functions 12
Overloaded Functions 12
Flow Control 13
Breaking Long Lines of Code 16
Importing Code 17
Threads & Multi-processing 18
Network, File, and Resource Input/Output 18
Native Code 21
Reserved compiler/interpreter keywords 21
MPL is a multi-paradigm dynamically typed procedural programming language, with very minimal syntax.
The purpose of the MPL is to provide a programming language that has minimalistic syntax, suitable for use on devices where the platform may have poor typing facilities and limited screen real estate. The restricted nature of the keyboard on these devices also discourages the adoption by this language of symbols, particularly brackets. It also seeks to remove theoretical obstacles to the exercise of programming, such as rather low integer limits, at the expense of operational efficiency.
As a simple introduction to the language, “Hello World” can be rendered as such. Any statement which consists of but a single variable (string, or otherwise) that's already been declared is seen as to be output to the terminal window.
MPL is not sensitive to indentation. Any amount of whitespace can precede a statement. However, the statement termination character is a newline, akin to Python and Visual Basic. As such, the programmer is restricted to a single statement per line. This is in lieu of an explicit statement termination character, such as the semi-colon (“;”) in C/C++/C#. MPL variables names and functions are not case-sensitive.
MPL files have as a standard, the file extension “.m”. The use of a file extension on source files is not mandatory.
The language supports single-line or multi-line comments. Single-line comments are those that follow two periods, one after another. Take the example below:
“hello again” .. This is a comment.
Multi-line comments are triggered when three periods are placed sequentially on a line, and terminate on the line where the last three non-whitespace characters are periods. Periods found in quotation marks (for strings) do not count. The multi-line comment can be terminated on the same line that it started on.
This is a multi-line comment
MPL is a dynamically typed language. Defining a variable is done by use of the equal sign, or use of the restricted keyword “is”.
name is Julien
phone_number = “(613)555-5555”
age = 25
When a variable is requested that does not yet exist, a warning is produced, and that variable is created on-the-fly with a value of “null”. Variables can not be named any of the restricted keywords. Variables are not case sensitive. Variables must be only be made up of alphanumeric characters and the undercarriage symbol (“_”). The MPL compiler/interpreter assumes any word in a statement that is followed by the variable assignment specifier (“=” or “is”) and without a function specifier (“:”) to be a new or existing variable. Note that unlike many other languages, variables in MPL do not require a keyword to declare them as such.
Internally, values are either treated as integers (64 bits by default; transparently expands to BigNum if necessary), complex doubles (two doubles, the second one being for i), boolean, streams, UTF-16 character strings, or raw data. They are stored as objects (instances in MPL), meaning that like Python, one can call methods (sub-functions in MPL) to do additional tasks such as text parsing.
The use of BigNum integers as opposed to the traditional sort means that the integers can be of a size limited to system memory, and not the traditional values represented by what could be stored in 32 or 64 bits.
Strings are stored internally in little endian UTF-16 encoding. In code, strings of text are represented as being in-between single or double quotation marks. The starting and ending quotation marks do not have to be on the same line (see the example below.)
String1 = “This is a line of text.”
String2 = “This is a second line of text
and it is longer.”
If there's a single new-line character in the string in the code, that newline is not stored in the string itself. It is there for the esthetics of the code. If the developer wants to incorporate a newline in the string, they must put two newlines back to back.
String3 = “This is the third line of text.
This is the fourth line of text.”
If the developer wants to put in quotation marks in a string, they must double them.
String4 = “This is a fourth line of “”text”” as my friends call them.”
The escape character in MPL is the percent mark. To insert a percentage mark in the code, one puts in double-percentage marks. To insert a dollar sign, one puts in two dollar signs back to back (otherwise it's interpreted to be used for variable substitution.) It is through the use of these escape characters that one can insert newlines (“%n”), tabs (“%t”), and raw hexadecimal values (“%xFFFF”).
String5 = “I agree %%100.%n
%t Dogs are great.%n
%t Cats suck.
%t And I love this character: %x35”
Variables are inserted into strings by way of variable substitution, covered in the following section.
The MPL language provides for the substitution of variables through the use of the dollar-sign symbol. This allows for the creation of dynamically named variables, and also, for the incorporation of variable values into strings. In the following example, the value of the age is substituted into the string.
age is 25
string = “Julien is $age years old.”
For the creation of dynamically named variables, one incorporates an existing variable into another variable name. Take the following example:
name = “Word1”
string$name is “hello”
name = “Word2”
string$name = “there”
In the first line, we stored the value “Word1” to the variable called “name”. In the second line, we had a variable with a variable in its name. So the actual variable name, according to the interpreter or compiler, is in fact after substitution “stringWord1”. We stored the value “hello” in the variable “stringWord1” Then in the third line we changed the value of the variable “name” to be “Word2”, such that the next variable is now called “stringWord2”, and has value “there”. So when we use the print function to put this all together, we call the values of both variables, which is “hello there”.
More complex operations can also be accomplished by employing parentheses after the dollar sign. Inside the mathematical operation is a statement, the result of which is output into the string.
Message = “You have $(money_in_bank – withdrawal) dollars left.”
The MPL compiler/interpreter has built-in support to evaluate mathematical operations, without necessitating the user to resort to calling specific functions. MPL also has integrated support for complex numbers. The operators are:
A + B
Adding A and B.
A - B
Subtracting B from A.
A * B
Multiplying A and B.
A / B
Dividing A and B.
A ^ B
A to the power of B. B can be negative and/or a floating point value.
Equivalent to A x 10B
A | B
Root of B to the A.
Absolute value of A.
Where A is a number, the result is A multiplied by the square root of negative one.
(A - B) * C
Parentheses can be used to explicitly determine the order of operation in statements where more than a single mathematical operation is involved.
If multiple operations are put on a single statement, the MPL compiler/interpreter will follow the PEDMAS rule (Parentheses, exponential, division, multiplication, addition, subtraction – in that order.)
The MPL math library provides the functions log(), ln(), cos(), sin(), tan(), max(), min(), e(), pi() and statistical functions such as stdev(). Due to the lack of necessity of parentheses following functions in MPL, e() and pi() can be used syntactically like variables:
radius = 5
area_of_a_circle = (pi * radius) ^ 2
Lists are the array equivalent in MPL. All variables can become lists, they do not need to be initially declared as such. They can be multi-dimensional. An item in the list is defined when an “at” symbol (“@”) follows the variable. After the at symbol is a list identifier. It can be either named or numerical. Numerical lists do not have to start at zero.
In the example below, the print out would be “Affordable Expensive Expensive”. In this example, the lists are all named, and each is treated like an independent variable. Thus items in lists can have their own lists as well. Note that in the example below, one can have multiple lists identified by the use of the comma.
car is Affordable
car@bmw,mercedes is Expensive
“$car $car@mercedes $car@bmw”
Lists can be numbered as well. You do not need to declare or initialize these items before using them to store values. If, however, an item in a list is called before any value has been stored to it, it will return “null”. So in the example below, the print out would be “null”.
Asset@0 = “Train”
Asset@5 = “Plane”
Asset@6 = “Automobiles”
MPL can also treat multiple item lists in a single go with the use of the colon (“:”). This is only for numbered lists. If named lists are used this way, then the colon is treated the same as a comma (“,”). For instance, in the following example, the variable “Name” has a list of ten items, all initialized to John Doe.
Name@0:10 = John Doe
The use of the colon (“:”) means that all items from the value to its left (0 above) to and not including the value to its right (10 above) are evaluated. Similar to Fortran, the arrays in MPL can have negative values, so this works as well:
x_values@-10:10 = 0.
In the example above, all x_values in the list from -10 to 10 are set to zero. Multi-dimensional lists are declared by inserting a question mark after the list name. In the example below, the variable depth contains two lists. One representing the x axis, one for the y axis. Each point on this grid has had its value initialized to ten.
depth@0:100@0:100 = 10
The print out from the function above is for the coordinates (13, 37), with a value of 10. If the ends of the array have already been defined, then one can skip specifying the values before or after the colon. The interpreter/compiler will substitute the absent value with the corresponding extremity of the list. If both are missing, all sub-lists are presented, including named lists.
bottles@0:100 = “Full”
In the previous section, you saw that you can initialize multiple values in one line. You can also apply operations to multiple values in one line as well. Just specify a list in the statement for the variable assignment.
final_price@: = item_price@: * 1.15
In the code above, the compiler/interpreter will go through each item of item_price. It will get the current price, and multiply it by 1.15. Expanded, a part of the statement above would look as such:
final_price@soda = item_price@soda * 1.15
final_price@bread = item_price@bread * 1.15
final_price@butter = item_price@butter * 1.15
You can also specify actual list items, if you wish to get the item before or after. If the lists are of different sizes, the smallest list size is the one that will be used.
item_price@1:500 = item_price@0:499 * 1.15
which expands to: (only a part shown)
item_price@242 = item_price@241 * 1.15
item_price@243 = item_price@242 * 1.15
item_price@244 = item_price@243 * 1.15
Functions in MPL have different properties than those of other languages. They can take on the role of functions, sub-routines, classes and/or structures. They can have multiple input arguments and return values. Functions in MPL can contain more functions.
Functions are declared by having the function name, followed by a colon (“:”) and then the input argument(s), should there be any. The function returns on the line where the first non-whitespace character is a colon, and that close-function identifier is for that scope. Function names can be alphanumeric, and cannot contain symbols except for the underscore character (“_”). Function names are not case-sensitive. A function can only be declared once per scope.
As a simple example, the following function “HelloWorld” outputs the string “hello world!”. Indentation in functions, like the rest of MPL, is optional.
To call the HelloWorld function, we would simply put the function name on a line by itself:
Other languages (Python, C, C++, PHP) will require that a call to a function include parentheses. The use of parentheses is optional in MPL. Their placement is inferred by the compiler/interpreter. However, there can be situations where ambiguity exists, in which case the use of parentheses is preferable.
.. Calling “HelloWorld” with parentheses
If we wanted a function with input arguments and three output values, we would do something akin to this example, which takes in two words, and outputs the combined word plus the two original input words.
combine : word1 word2
combined_word = “$word1$word2”
: combined_word word1 word2
Functions in MPL can also have aliases and instances. Function aliases are simply alternative names for the same function. This is for those developers who wish to provide an alternative short form for their function names, which could be valuable during mobile programming. Instances can be thought of as “objects” in C++, they provide persistence to the variables stored in the top scope of the function.
Aliases follow the function name but precede the input arguments. Instances follow the function and its aliases, if present, as well as a question mark symbol (“?”) and precede the input arguments. White space separates aliases and instances.
.. This function has the alias “up”
update_database up : string
.. These two calls do the same thing
update_database hello world
up hello world
The following shows an example of an instance of a function being created and called. In this case, we have function “car” with sub-functions “opendoor” and “closedoor.” Sub-functions are called by putting a period after the function name, with the name of the sub-function. The instance is named
car ? my_car : noPassengers
pass = noPassengers
door = false
car.door = true
car.door = false
.. CALLING FUNCTION
The output in the above case will be “False” then “True”. Instances mean persistence. If we did the calls in code below instead, for instance, the output would be “False” and “False.” This is because the variables wouldn't be saved.
Instances can be made of sub-functions, and also do not have to be done from the function declaration. Take the following example:
foo.bar ? always_returns_true
In the example above, we created a function “foo” with sub-function “bar”. “foo.bar” always returns true. So we made an instance of foo.bar, and call it, it will spit out “true”. When instances of sub-functions are made, they lose any linkages to their parent function. They are independent. The print function forces the value to be output.
Note that you do not have to declare the variables inside a function, if they are to be called later on. The MPL compiler/interpreter will know from the calls what variables ought to be in a function. If you look at the following example, you'll see that we create a function train. It will include three variables, which the compiler/interpreter knows because they are called later.
train.direction = North
train.speed = 5
In the code above, the “print train.status” call will output “null”, because we never specified what was in the train.status variable.
An important keyword with functions is “return” (short form: “ret”). When used, the output variables specified at the bottom of the function are immediately outputted and the function aborted. If variables follow the return statement, then they are outputted instead.
add: a b
c = a + b
subtract: a b
c = a * b
foobar = math.add 5 math.subtract(2 1)
In MPL, you can also have anonymous functions, which are used to create instances if there's no need to call the function that prototyped it. This becomes useful, if, for instance, one wants to create the MPL equivalent of a C structure with substructures. To define an anonymous function with an instance, do not name the function, just begin the line with a question mark (“?”) and follow with the instance name.
telephone_book_entries?10?name.first = Justin
telephone_book_entries?10?name.last = Wallace
telephone_book_entries?10.phone_number = “(555) 555-5555”
If you recall from the section on functions, persistence only applies to the top-level scope. If we create instances at that top level, then their contents become persistent as well. Note that in the example above, we didn’t have to declare what the variables in the functions were. Those are automatically populated from the calls that were made (first and last name, phone number).
MPL supports having functions with multiple numbers of input arguments. Because of the ambiguity of overloaded functions, called overloaded functions must use parentheses to identify the input arguments. If no parentheses are found in the function call, the compiler/interpreter spits out an error.
To set up an overloaded function, redeclare an existing function but with a different number of input variables. Take the log() function in the math library of MPL. If the user doesn't specify a base and uses only a single input argument when calling log(), then the second function is called and log10 is assumed. If they do specify a base, the first function is called. Sub-functions can also be overloaded.
log : base number
log : number
base = 10
log(5) .. equivalent to log105
log(2,5) .. equivalent to log25
There are three flow control statements in MPL: “if” and “for.” These keywords, like all those in MPL, are not case sensitive.
The “if” statement is similar to its functionality in other languages, that is to say, “if [something is true] do [something else.]” The statement to be evaluated (what follows the “if”) does not need to be at the top of the block beside the “if”, but can be placed at the bottom with the “end” statement that closes the block. The different types of statements that can follow an “if” statement are: if A is true, if A equals B, if A does not equal B, if A is equal or greater than B, if A is equal or less than B, if A is greater than B, if A is less than B. Both A and B must be variables that have already been declared. If statements must be concluded with the “end” keyword.
. If A equals B
If y is x
“y and x are equal”
Below is a keyword table which lists the various statements you can use in MPL for if/for loops. Note that there is a written way to do these statements, and one using the symbols. The written way exists for those who use keyboards where symbols may be time-consuming or impossible to obtain, whereas the symbols method exists out of familiarity.
STATEMENTS YOU CAN USE IN MPL
A equals B
A is B
A = B
A == B
A does not equal B
A not B
A != B
A is greater than B
A gt B
A > B
A is less than B
A lt B
A < B
A is greater or equal than B
A get B
A >= B
A => B
A is less or equal than B
A let B
A <= B
A =< B
If statements also allows for the use of “else if”, “elif” and “else” keywords. “Else if” and “elif” are synonymous. If the condition for the “if” block is not met, than the condition that follows the “elif” keyword is evaluated. There can be an unlimited number of “elif” statements. The “else” statement means that if the conditions of the “if” and/or “elif” blocks were unmet, the block that follows the “else” statement is executed.
If television.price * 1.15 get money_in_bank
print “You cannot afford this television.”
elif television.price get money_in_bank
“You can afford the television. Just not the taxes.”
“You can afford the television!”
If statements can be compounded with the “or” and “and” keywords. Parentheses can be used to group multiple evaluations as well.
If (A = B and A = C) or A = D
If you replace the “end” with “loop” at the end of an If block, then the if statement is re-evaluated. If it evaluates to true, the whole block is repeated. If not, the program continues with what follows the code block. The following code prints the numbers 0 to 5.
I = 0
if I let 5
MPL allows you to pack multiple statements following an “if” statement, separated by commas. The first statement is executed before the block. The middle statement is the one that is evaluated to see whether the block ought to be executed. The third is evaluated at the end of the block. The code above could be rewritten:
if i is 0, i let 5, i++
You can also go through items in a list, akin to the “foreach” keyword of other programming languages. You do this using the “for” and “in” keywords, following the formula “for <temporary variable for item list> in <list>”.
If word in dictionary
Here is an example with multiple if blocks. Only the outer block is looped.
if status is “OK”
status is “NOT OK”
Two other keywords can be used in if blocks: “break” and “continue.” The “break” statement, when reached, ignores the rest of the loop block and pretends like the block ended right there. A number following the break keyword specifies how many nested blocks of loops to break out of. This number is optional. If not present, it is assumed that we are breaking only of the inner-most block. The “continue” keyword restarts the code from the top of the block. A number which follows specifies how many nested loops to continue from. The short form of continue is “cont”, and can be used instead in the code.
In the code below, once we reach 10, the loop exits.
i = 1
If i > 0
if i is 10
Here is another example. This time, we break from a very nested loop.
Long lines of code can be broken into multiple lines. In order to do so, the last non-whitespace character of a line to be cut must be a comma. Here's an example, with the “if” statement line being broken down into two lines of code.
If (car.speed) > speed_limit and ,
(car.price) > exhorbitant
“You must have a small dick.”
Strings can be broken up into multiple lines as well. String constructs are naturally multi-line, similar to multi-line comments. To define them, simply open a string on one line, and close it on another. The newline that's used to define a new line in the code will not be saved to the string.
“This is the first line of a statement.
This is the second line of a statement”
printed out, the above produces:
This is the first line of a statement. This is the second line of a statement.
To insert a newline into the string, put in two newlines one after the other when you're writing it. Here's the same code as above, but now we have two newlines after the first line.
“This is the first line of a statement.
This is the second line of a statement.”
printed out, the above produces: (note the double newline this time)
This is the first line of a statement.
This is the second line of a statement.
External source code can be imported into a file by the use of the pound symbol (“#”). However, importing libraries is not always necessary. If a function is called that is not defined in the source file, the compiler/interpreter will look for a matching function name in the MPL library directory. Should that fail, it will look for the code in the directory of the file to be compiled.
print “The “”print”” command is actually from the io library.”
It may be the case that multiple imported files have the same function name. If this occurs, an error is generated. The format is “#<file name in MPL library directory>” (not case sensitive) or “#<path to file>” (case sensitivity is according to OS). The path is according not to the current working directory, but to the file selected to be compiled. Note that a conflict can arise if a file in the current directory shares a name with a file name in the MPL library. If that's the case, a warning is generated, and the compiler/interpreter defaults to the MPL library.
In the example above, a source file in a sub-directory is being imported. If a exclamation mark is placed in front of a library name, this tells the compiler/interpreter to skip searching that file when it's trying to match function names to a source file.
.. Force the compiler/interpreter not to search through the io
.. library for matching called functions.
Unlike other languages in which threading is relegated to the use of libraries, in MPL, it's a core component of the language. To thread a function, one simply follows the function name with an exclamation mark (“!”) when it is called.
In the example below, the sub-function “handle_incomming request” of the function “server_daemon” with the input variable “ip_address” will be run in a separate thread, in concurrence with the main program flow.
There are no semaphores in MPL. To prevent race conditions, the developer can set aside a variable before the thread was called that the thread can access that serves the same purpose.
Not just functions, but any statement can be multi-threaded. Any line terminating with the exclamation mark will be executed in a separate thread. Let's say you want to initialize a large part of memory, as in the following example:
item_prices@0:1024000 = item_price% + 10 !
In MPL, access to the network and files is seen as so integral that it merits an easier handling than the current system of calling objects/functions in other programming languages. In MPL, it is abstracted to the point where writing to a file, and dealing with devices on networks, is as easy as assigning a variable.
The assignment of a variable defines the type that it is recognized internally as streams. This assignment is done in the first line of the example below, with the calling of the “file” function. Once the variable is assigned, we can then use stream writes (“<”, “<<”) and stream reads (“>”) to interact with the device.
foo = file(“/home/foo.txt” text) .. setup the file
foo < “Hello world” .. append “Hello world” to the file
The “<” writes to a stream, appending the contents to the previous ones there, if applicable. The “<<” writes to a stream, clearing the previous contents in the device, if applicable. This is equivalent to the “append” and “write” modes in C.
foo = file(“/home/foo.txt” text) .. setup the file
foo << “Hello world” .. replace the contents of the file with the string
To read from a stream, one uses the “>” keyword, such as the example below.
foo = file(“/home/foo.txt” text) .. setup the file
foo > bar .. dump the entire file to a variable “bar”
Alternatively, one can call the stream variable itself to get its contents. One can call it as a list to grab the lines of a file if in text mode, or the bytes of a file if in binary mode. This is defined in the calling of the “file” function.
foo = file “/home/configuration” text .. setup the file
lines = foo@0:5 .. read lines 0 to 5 and store to “lines”
foo = file “/dev/mem5” binary .. setup access
bar = foo@256:512 .. load bytes 256 to 512 into “bar”
if bar.size < 256 .. confirm that 256 bytes were loaded.
“Error: $(256 – bar.size) Bytes Missing!”
Interactions with devices on the network are also dealt with in a high-level manner in MPL. The language does not provide for the management of the NIC, much like file handling in other languages doesn't require the developer to handle inodes.
search = net “google.com:80” post .. setup a connection.
search < postRequest .. send a POST request.
search > response .. get the response.
The protocols in MPL are TCP, UDP, HTTP with GET and POST handled separately to minimize code overhead.
server = net “127.0.0.1:80” http .. setup the server
if server not null .. when the buffer isn't empty, process
server > request .. store the request to a variable
loop .. loop the entire process
Serial devices are interacted with much in the same manner. The response is handled in plastic buffers to a maximum of 200ms (modifiable by an overloaded function to the serial/net calls), to enable responses to be handled in chunks of data that hopefully contain the entire answer. This does not work with continuous data streams, which are recognized by the compiler/interpreter, and then outputted without waiting to load up the buffer. This behaviour, again, is adjustable from the initial function call to file/net/serial/etc.
gps = serial 8n1 4800 .. setup the serial device; a GPS unit
gps < “p” .. send the letter “p” to retrieve a position
gps > response .. get the response from the GPS device
nmeaParser(response) .. parse the response
If the variable containing the stream is assigned anything else, the variable becomes of the newly assigned type. If that new type is not a stream, the “<” and “>” keywords will output nulls. Take the following code, in which the developer mistakenly used the assignment character to make the variable GPS hold the character “P”, instead of sending “P” to the serial device through the use of the “<” keyword.
gps = serial 8n1 4800 .. setup the serial device
gps = “p” .. the variable now holds string “p”. It is not a stream.
gps > response .. response is now “null”.
Keep in mind that the calls to the stream functions of file(), net(), serial(), etc. return a stream variable. Therefore, those functions can also be used in the stead of the variables that we assign them to. This can result in more compact code, as evidenced by the examples below.
file “/home/foo.txt” < “Data to write to a file”
file (“/home/bar.bin” binary)@ 0:64 > variableContainingFirst64Bytes
The code that the compiler converts the source to, or the native code of the interpreter (if itself runs on a second interpreter, such as Python), can be inserted into MPL by the use of the double-exclamation mark. This is a multi-line indicator: the first line's first non-whitespace characters must be a double-exclamation mark, and the last line's first non-whistepace character must be a double-exclamation mark.
<Native C code>
<More Native C code>
How the native code interfaces with MPL variables and functions is dependent upon the specific compiler/interpreter.
is, loop, for, if, in, end, gt, get, lt, let, not, continue, cont, break, or, and, else, elif, return, ret, null.
MPL Programming Language Specifications // 21