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

Overview

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;
}));
?>

Installation

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

Usage

Once installed, you should have access to the mammouth command, which can execute scripts, compile .mammouth files into .php. The mammouth command takes the following options:

-c, --compile Compile a .mammouth 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:

Language Reference

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.

Functions

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

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, Unless, and Conditional Assignment

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;
?>

Loops and Comprehensions

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.

Array Slicing with Ranges

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);
?>

Everything is an Expression (at least, as much as possible)

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.

Operators and Aliases

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

MammouthPHP
is===
isnt!==
not!
and&&
or||
@, this$this
++, . (only + in strict mode)
~~. (concat strings)
.->
.., ::::
inno 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;
?>

Classes, interfaces, inheritance...

{{
# 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;
?>

Namespaces and qualified names...

{{
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();
?>

Heredoc strings

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;
?>

Strict and default mode

{{
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);
?>

Change Log

3.0.1 Oct 25, 2015 Support for many PHP and PHP modules predefined functions, classes and consts including MySQL and PostgreSQL...

3.0.0 Sep 14, 2015 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 Aug 15, 2014 Rewriting the project using JISON, support many php structure, better context system, in short large changes

0.1.0 Jul 12, 2013 Initial Mammouth release.