Mammouth is a small language that compiles into PHP, inspired by CoffeeScript. It's compiled to PHP code/files that you can run on your PHP server.
We are now in the 3.0s versions, in this version we implement the golden rule of CoffeeScript "Everything is an expression", so you can use if, for and while as expressions or arguments, in addition this version has an improved scoping system who can read from others PHP or mammouth files, so mammouth will know where he must put $.
Mammouth is created and developed by Wael Boutglay.
Latest Version: 3.0.1
npm install -g mammouth
Mammouth on the left, compiled PHP output on the right.
{{
# Assignement:
number = 42
opposite = false
# Condition:
number = if opposite then -42 else 13
# Existence:
echo "I knew it!" if elvis?
### look where it will put '$' ###
square = func (x) -> x * x
square(2) # 4
func Hello ->
echo 'Hello Mammouth'
Hello()
# Array:
num_list = [1, 2, 3, 4, 5]
# Keyed array:
obj = [
'square': square
'cube': func (x) -> x * square(x)
]
# Array comprehensions:
squares = (square(num) for num in num_list)
}}
<?php
// mammouth helpers function will be added here
// PHP look generally saller and compressed
$number = 42;
$opposite = false;
$number = $opposite ? -42 : 13;
if(isset($elvis)) {
echo "I knew it!";
}
// look where it will put '$'
$square = function($x) {
return $x * $x;
};
$square(2);
function Hello() {
echo 'Hello Mammouth';
}
Hello();
$num_list = array(1, 2, 3, 4, 5);
$obj = array(
'square' => $square,
'cube' => function($x) {
return $x * $square($x);
}
);
$squares = (call_user_func(function() {
$result = array();
for($i = 0, $len = mammouth('length', $num_list); $i < $len; $i++) {
$num = $num_list[$i];
array_push($result, $square($num));
}
return $result;
}));
?>
The Mammouth compiler is written in Javascript, using the Jison parser generator. The command-line version of mammouth is available as a Node.js utility. The core compiler however, does not depend on Node, and can be run in any JavaScript environment, or in the browser (see "Try Mammouth", above), We recommend to use mammouth in node for performance reason, it's very slow in browser.
To install, first make sure you have a working copy of the latest stable version of Node.js, and npm (the Node Package Manager). You can then install Mammouth with npm:
npm install -g mammouth
(Leave off the -g if you don't wish to install globally.)
If you'd prefer to install the latest master version of Mammouth, you can clone the Mammouth source repository from GitHub, or download the source directly. To install the lastest master Mammouth compiler with npm:
npm install -g http://github.com/btwael/mammouth/tarball/master
Once installed, you should have access to the mammouth command, which can execute scripts, compile .mammouth or .mmt files into .php. The mammouth command takes the following options:
-c, --compile |
Compile a .mammouth or .mmt script into a .php PHP file of the same name. |
-o, --output [DIR] |
Write out all compiled PHP files into the specified directory. Use in conjunction with --compile. |
Examples:
This reference is structured so that it can be read from top to bottom, if you like. Later sections use ideas and syntax previously introduced. Familiarity with PHP is assumed. In all of the following examples, the source Mammouth is provided on the left, and the direct compilation into PHP is on the right.
First, the basics: Mammouth uses significant whitespace (indents) to delimit blocks of code.
You don't need to use semicolons ;
to terminate expressions,
ending the line will do just as well.
Instead of using curly braces
{ }
to surround blocks of code in functions,
if-statements,
switch, and try/catch,
use indentation.
Unlike Coffeescript, you must use parentheses to invoke a function.
You can too use anonymous functions, if-statements and loops (for/while) as assignable expressions or arguments for an invocation.
Unamed and anonymous functions are defined by the func keyword an optional list of parameters in parentheses, an arrow, and the function body. The empty function looks like this: func ->
{{
square = func (x) -> x * x
func cube(x) ->
square(x) * x
# don' worry, mammouth will look after '$'
}}
<?php
$square = function($x) {
return $x * $x;
};
function cube($x) {
return $square($x) * $x;
}
?>
Functions may also have default values for arguments and you can pass an argument as reference using use keyword, you can too use them anonymouslly for callback as example.
{{
func fill(use container, liquid = "coffee") ->
"Filling the " ~~ container ~~ " with " ~~ liquid
# Mammouth support also anonymous function
echo preg_replace_callback('~-([a-z])~', func (match)->
strtoupper(match[1])
, 'hello-world')
}}
<?php
function fill(&$container, $liquid = "coffee") {
return "Filling the ".$container." with ".$liquid;
}
echo preg_replace_callback('~-([a-z])~', function($match) {
return strtoupper($match[1]);
}, 'hello-world');
?>
Arrays in Mammouth can be defined between bracker [ and ] just like in Javascript.
{{
song = ["do", "re", "mi", "fa", "so"]
# an array with propeties
singers = ["Jagger": "Rock", "Elvis": "Roll"]
bitlist = [
1, 0, 1
0, 0, 1
1, 1, 0
]
}}
<?php
$song = array("do", "re", "mi", "fa", "so");
$singers = array(
"Jagger" => "Rock",
"Elvis" => "Roll"
);
$bitlist = array(1, 0, 1, 0, 0, 1, 1, 1, 0);
?>
If/else statements can be written without the use of parentheses and curly brackets. As with functions and other block expressions, multi-line conditionals are delimited by indentation. There's also a handy postfix form, with the if or unless at the end.
Mammouth can compile if statements into PHP expressions, using the ternary operator when possible, and closure wrapping otherwise. There is no explicit ternary statement in Mammmouth — you simply use a regular if statement on a single line.
{{
mood = greatlyImproved if singing
working = true unless saturday or sunday
if happy and knowsIt
clapsHands()
chaChaCha()
else
showIt()
date = if friday then sue else jill
}}
<?php
if($singing) {
$mood = $greatlyImproved;
}
if(!($saturday || $sunday)) {
$working = true;
}
if($happy && $knowsIt) {
$clapsHands();
$chaChaCha();
} else {
$showIt();
}
date = $friday ? $sue : $jill;
?>
Most of the loops you'll write in Mammouth will be comprehensions over arrays, objects, and ranges. Comprehensions replace (and compile into) for loops, with optional guard clauses and the value of the current array index. Unlike for loops, array comprehensions are expressions, and can be returned and assigned.
{{
# Eat lunch.
eat(food) for food in ['toast', 'cheese', 'wine']
# Fine five course dining.
courses = ['greens', 'caviar', 'truffles', 'roast', 'cake']
menu(i + 1, dish) for dish, i in courses
# Health conscious meal.
foods = ['broccoli', 'spinach', 'chocolate']
eat(food) for food in foods when food isnt 'chocolate'
}}
<?php
// mammouth helper function is defined normally here
$ref = array('toast', 'cheese', 'wine');
for($i = 0, $len = mammouth('length', $ref); $i < $len; $i++) {
$food = $ref[$i];
$eat($food);
}
$courses = array('greens', 'caviar', 'truffles', 'roast', 'cake');
for($i = $j = 0, $len1 = mammouth('length', $courses); $j < $len1; $i = $j++) {
$dish = $courses[$i];
$menu(mammouth("+", $i, 1), $dish);
}
$foods = array('broccoli', 'spinach', 'chocolate');
for($k = 0, $len2 = mammouth('length', $foods); $k < $len2; $k++) {
$food = $foods[$k];
if($food != 'chocolate') {
$eat($food);
}
}
?>
Comprehensions should be able to handle most places where you otherwise would use a loop, each/forEach, map, or select/filter, for example: shortNames = (name for name in names when strlen(name) < 5)
If you know the start and end of your loop, or would like to step through in fixed-size increments, you can use a range to specify the start and end of your comprehension.
{{
countdown = (num for num in [10...1])
countup = (num for [1...10] as num)
}}
<?php
// mammouth helper function is defined normally here
$countdown = (call_user_func(function() {
$result = array();
$ref = array(10, 9, 8, 7, 6, 5, 4, 3, 2, 1);
for($i = 0, $len = mammouth('length', $ref); $i < $len; $i++) {
$num = $ref[$i];
array_push($result, $num);
}
return $result;
}));
$countup = (call_user_func(function() {
$result = array();
for($num = 1; $num <= 10; $num++) {
array_push($result, $num);
}
return $result;
}));
?>
Remember always in ranges that the equivalent of Coffeescript .. is ... in Mammouth and the equivalent of ... is ....
Note how because we are assigning the value of the comprehensions to a variable in the example above, Mammouth is collecting the result of each iteration into an array. Sometimes functions end with loops that are intended to run only for their side-effects. Be careful that you're not accidentally returning the results of the comprehension in these cases.
To step through a range comprehension in fixed-size chunks,
use by, for example:
evens = (x for x in [0...10] by 2)
If you don't need the current iteration value you may omit it:
closeCurrentTab() for [0....count]
Comprehensions can also be used to iterate over the keys and values in an object. Use of to signal comprehension over the properties of an object instead of the values in an array.
{{
yearsOld = ["max": 10, "ida": 9, "tim": 11]
ages = for child, age of yearsOld
child ~~ " is " ~~ age
}}
<?php
$yearsOld = array("max" => 10, "ida" => 9, "tim" => 11);
$ages = call_user_func(function() {
$result = array();
foreach($yearsOld as $child => $age) {
array_push($result, $child." is ".$age);
}
return $result;
});
?>
The only low-level loop that Mammouth provides is the while loop. The main difference from PHP is that the while loop can be used as an expression, returning an array containing the result of each iteration through the loop.
{{
# Econ 101
if this.studyingEconomics
buy() while supply > demand
sell() until supply > demand
# Nursery Rhyme
num = 6
lyrics = while num -= 1
num ~~ " little monkeys, jumping on the bed One fell out"
}}
<?php
if($this->studyingEconomics) {
while($supply > $demand) {
$buy();
}
while(!$supply > $demand) {
$sell();
}
}
$num = 6;
$lyrics = call_user_func(function() {
$result = array();
while($num -= 1) {
array_push($result, $num." little monkeys, jumping on the bed One fell out");
}
return $result;
});
?>
For readability, the until keyword is equivalent to while not, and the loop keyword is equivalent to while true.
Ranges can also be used to extract slices of arrays. With three dots (3...6), the range is inclusive (3, 4, 5, 6); with four dots (3....6), the range excludes the end (3, 4, 5). Slices indices have useful defaults. An omitted first index defaults to zero and an omitted second index defaults to the size of the array.
{{
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]
start = numbers[0...2]
end = numbers[-2...]
copy = numbers[...]
}}
<?php
// mammouth helper function is defined normally here
$numbers = array(1, 2, 3, 4, 5, 6, 7, 8, 9);
$start = mammouth("slice", $numbers, 0, mammouth("+", 2, 1));
$end = mammouth("slice", $numbers, -2);
$copy = mammouth("slice", $numbers, 0);
?>
You might have noticed how even though we don't add return statements to Mammouth functions, they nonetheless return their final value. The Mammouth compiler tries to make sure that all statements in the language can be used as expressions. Watch how the return gets pushed down into each possible branch of execution in the function below.
{{
grade = func (student) ->
if student.excellentWork
"A+"
else if student.okayStuff
if student.triedHard then "B" else "B-"
else
"C"
eldest = if 24 > 21 then "Liz" else "Ike"
}}
<?php
$grade = function($student) {
if($student->excellentWork) {
return "A+";
} elseif($student->okayStuff) {
if($student->triedHard) {
return "B";
} else {
return "B-";
}
} else {
return "C";
}
};
$eldest = 24 > 21 ? "Liz" : "Ike";
?>
Even though functions will always return their final value, it's both possible and encouraged to return early from a function body writing out the explicit return (return value), when you know that you're done.
Assignment can be used within expressions, even for variables that haven't been seen before:
{{
six = (one = 1) + (two = 2) + (three = 3)
}}
<?php
six = (one = 1) * (two = 2) * (three = 3)
?>
Things that would otherwise be statements in PHP, when used as part of an expression in Mammouth, are converted into expressions by wrapping them in a closure. This lets you do useful things, like assign the result of a comprehension to a variable:
{{
# The first ten properties of an attay with properties
properties10 = (name for name of keyedarray)[0....10]
}}
<?php
// mammouth helper function is defined normally here
$properties10 = mammouth("slice", call_user_func(function() {
$result = array();
foreach($keyedarray as $name => $value) {
array_push($result, $name);
}
return $result;
}), 0, 10);
?>
As well as silly things, like passing a try/catch statement directly into a function call:
{{
echo (
try
nonexistent / null
catch error
"And the error is ... " ~~ error
)
}}
<?php
echo (call_user_func(function() {
try {
return $nonexistent / $undefined;
} catch(Exception $error) {
return "And the error is ... ".$error;
}
}));
?>
There are a handful of statements in PHP that can't be meaningfully converted into expressions, namely break, continue, and return. If you make use of them within a block of code, Mammmouth won't try to perform the conversion.
In Addition to PHP operators and symbols, Mammouth offers many shortform and aliases, So Mammouth compiles is into ===, and isnt into !==
You can use not as an alias for !.
For logic, and compiles to &&, and or into ||.
unless can be used as the inverse of if.
As a shortcut for $this->property, you can use @property.
You can use in to test for array presence
Mammouth | PHP |
---|---|
is | === |
isnt | !== |
not | ! |
and | && |
or | || |
@ , this | $this |
+ | + , . (only + in strict mode) |
~~ | . (concat strings) |
. | -> |
.. , :: | :: |
in | no PHP equivalent |
Mammouth's existential operator can help you to check if a variable or an object exists or not.
{{
solipsism = true if mind? and not world?
instance?.method()
echo instance?.method()
}}
<?php
if(isset($mind) && !isset($world)) {
$solipsism = true;
}
if(isset($instance)) {
$instance->method();
};
echo isset($instance) ? $instance->method() : NULL;
?>
{{
# A simple example
class A
# Mammouth support property Visibility
private vari = true
protected const CONST = "yes"
func foo() ->
if this?
echo '$this is defined ('
echo get_class(this)
echo ")\n"
else
echo "\$this is not defined.\n"
# you can also use final and static
final public static func fii ->
return true
a = new A()
a.foo()
A..foo() # you can also use A::foo()
# an example about Inheritance
class foo
public func printItem(string) ->
echo 'Foo: ' ~~ string ~~ PHP_EOL
public func printPHP ->
echo 'Mammouth is great.' ~~ PHP_EOL
class bar extends foo
public func printItem(string) ->
echo 'Bar: ' ~~ string ~~ PHP_EOL
# Abstract class example
abstract class AbstractClass
abstract protected func getValue()
abstract protected func prefixValue(prefix)
public func printOut ->
echo @getValue() ~~ "\n"
class ConcreteClass1 extends AbstractClass
protected func getValue ->
return "ConcreteClass1"
public func prefixValue(prefix) ->
return "{$prefix}ConcreteClass1"
# Interface example
interface iTemplate
public func setVariable(name, vari)
public func getHtml(template)
# Implement the interface
class Template implements iTemplate
private vars = []
public func setVariable(name, vari) ->
@vars[name] = vari
public func getHtml(template) ->
for name, value of @vars
template = '{$name}: {$value}'
return template
# Cloning objects/classes
variable = clone a
}}
<?php
class A {
private $vari = true;
protected const CONST = "yes";
function foo() {
if(isset($this)) {
echo '$this is defined (';
echo get_class($this);
echo ")\n";
} else {
echo "\$this is not defined.\n";
}
}
final public static function fii() {
return true;
}
}
$a = new A();
$a->foo();
A::foo();
class foo {
public function printItem($string) {
echo 'Foo: '.$string.PHP_EOL;
}
public function printPHP() {
echo 'Mammouth is great.'.PHP_EOL;
}
}
class bar extends foo {
public function printItem($string) {
echo 'Bar: '.$string.PHP_EOL;
}
}
abstract class AbstractClass {
abstract protected function getValue();
abstract protected function prefixValue($prefix);
public function printOut() {
echo $this->getValue()."\n";
}
}
class ConcreteClass1 extends AbstractClass {
protected function getValue() {
return "ConcreteClass1";
}
public function prefixValue($prefix) {
return "{$prefix}ConcreteClass1";
}
}
interface iTemplate {
public function setVariable($name, $vari);
public function getHtml($template);
}
class Template implements iTemplate {
private $vars = array();
public function setVariable($name, $vari) {
return $this->vars[$name] = $vari;
}
public function getHtml($template) {
foreach($this->vars as $name => $value) {
$template = '{$name}: {$value}';
}
return $template;
}
}
$variable = clone $a;
?>
{{
namespace my # Defining a namespace
# code go here
namespace q'my\name' # Defining a sub-namespace
# code go here
namespace AnotherProject
const CONNECT_OK = 1
class Connection
const host = "12"
func connect() ->
return 'you are connected'
a = q'AnotherProject\\connect'()
echo q'AnotherProject\\Connection'..start()
}}
<?php
namespace my;
namespace my\name;
namespace AnotherProject {
const CONNECT_OK = 1;
class Connection {
const host = "12";
}
function connect() {
return 'you are connected';
}
}
$a = AnotherProject\connect();
echo AnotherProject\Connection::start();
?>
You can use a multi-line string using the syntax of mammouth heredocs.
{{
str = `Example of a string
spanning multiple lines
using heredoc syntax.`
}}
<?php
$str = <<<EOT
Example of a string
spanning multiple lines
using heredoc syntax.
EOT;
?>
{{
str = 'tex1' ~~ 'text2' # in all modes
str = 'tex1' + 123
'strict mode'
str = 'tex1' + 123
'default mode'
str = 'tex1' + 123
}}
<?php
$str = 'tex1'.'text2';
$str = mammouth("+", 'tex1', 123);
$str = 'tex1' + 123; // by the way, it will throw an error
$str = mammouth("+", 'tex1', 123);
?>
3.0.1
Support for many PHP and PHP modules predefined functions, classes and consts including MySQL and PostgreSQL...3.0.0
Rewriting the project, using JISON and a handwritten lexer for better performance. Improve scoping system with ability to read from other php or mammouth files, using if, for and while as expressions or arguments. Support for range, slice and existence...2.0.0
Rewriting the project using JISON, support many php structure, better context system, in short large changes0.1.0
Initial Mammouth release.