Metalthea
A universal metaprogramming language to build it yourself!
Features
- Metalthea is a metaprogramming language designed to generates code in-line using text manipulation and substitution at compile-time, like a macro system, for any programming language.
- Metalthea has no built-in function, nor arithmetic operations, nor control structure, you have to implement that yourself, as you want it to work, in Javascript.
- Metalthea has the parenthesized syntax of S-expressions (like the Lisp programming language).
Basic API Usage
const metalthea = require("metalthea");
const { value: output, syntaxTree, errorLogs } = metalthea.compileSourceCode('(add 1 2 3)', {
add: (...args) => args.reduce((a, b) => `${a} + ${b}`),
});
// output == "1 + 2 + 3"
Program examples
- A hello world (in the middle of a text):
// in source code:
"you can write anything before Metalthea..."
(log (str.join "Hello" "World" "!"))
"...and after too"
// in language implementation
const execCtx = {
log: function(str) {
return `console.log(${ str });`;
},
str: {
join: function(...args) {
return args.join(' ');
}
}
}
// the result in the output file:
"you can write anything before Metalthea..."
console.log("Hello World!");
"...and after too"
- Other examples in
test/
:
Why?
- To add macro features in your usual language.
- To use cool language features you want to work with (strong typing, pattern matching, tuples, etc.).
- To transpile one source code to multiple programming language.
- To use as a codegolfing tool.
Syntax
The Metalthea syntax is close to Lisp S-expressions:
- an Expression is either an Atom or a List of Expressions.
- a List is a matched pair of parentheses, with zero or more Expressions inside it.
- an Atom is either:
- a base 10 Number
- i.e.
42
,3.14
,-1.6
- i.e.
- a base 16 Number
- starting with
0x
, can have a decimal part after a dot - i.e.
0x2A
,0x3.2f
,-0xCC.c
- starting with
- a String
- between double quotes
"
- i.e.
""
,"0"
,"Hello World!"
- between double quotes
- a Boolean
- in lowercase
- i.e.
true
,false
- a Key
- starting with colon
:
- i.e.
:i
,:myVar
,:$_21
- starting with colon
- a Body
- either between braces
{}
or between grave accents`
- i.e.
{1 + 1}
,{n => n * n}, {)}
, or`\n`
,`substring to complete in js`
- either between braces
- a Function
- i.e.
add
,str2Int
,__$log
- i.e.
- a Lazy Node
- a node starting with a sharp sign
#
- i.e
#
,#(mul 2)
- a node starting with a sharp sign
- a base 10 Number
you can check Grammar RegExp there
To separate Expressions you can use either the space character (
) or a comma (,
) for the same result, in example:
(Map :a 12, :b 36, :c 42)
You can also chain expressions with the character >
, in example:
((add 1 1) > sqrt)
is the same thing as:
(sqrt (add 1 1))
Installation
You need Node.js to use Metalthea, so I suggest you to setup a package.json
in your project, regardless of what are the other programming language(s), with npm:
npm init
npm install --save-dev metalthea
or with yarn:
yarn init
yarn add --dev metalthea
Configuration
- in
package.json
:
add commands in "scripts"
:
"metalthea": "metalthea <PATH/TO/EXEC_CTX_FILES> <PATH/TO/INPUT_FILES> <PATH/TO/OUTPUT_DIR>",
"build": "npm run metalthea --verbose && <YOUR BUILD COMMAND>",
"watch": "metalthea --verbose --watch"
- in
metalthea.json
:
TODO (dependency management and custom setup for execution context from multiple sources)
CLI Usage
command syntax:
metalthea <CTX> <SRC> <TARGET>
Options
--verbose, -v add logs to stdout
--syntax-tree, -t logs the intermediate syntax tree in stdout
--watch, -w watch context and source files and re-compile if any change is detected
Examples
If you have configured package.json
as recommended:
npm run metalthea --verbose --watch
or included in your build flow:
npm run build
or using a shell with Metalthea installed globally (npm install -g methathea
):
metalthea src/metalthea src/js dist --verbose --watch
or use the Node API from your Node.js project.
FAQ
- How to use loops and conditions? (control structure)
- Implement it yourself! The easiest way is to craft expression based ones, but you can implement statements as well.
- i.e.
(if (check :test) "yes!" "no")
,(for 0,99,1 #(log))
- There are missing types (list, array, character, etc.)!
- Implement it yourself!
- i.e.
(Map :x 21, :y -42)
- How to handle null / undefined ?
- You can test values in javascript, you can event add a custom Null type if you wish.
- i.e.
(def :x (if (isNull :a) 0, :a))
- How to add a comment?
- For the moment you have to implement it yourself.
- i.e.
(comment "blaba")
- How to use arithmetic operations *(+, -, , /, %)?
- Implement it yourself!
- i.e.
(mul 3 (add 5 4) > sqrt)
- I need strong typing!
- Implement it yourself!
- i.e.
(defTyped (int :n) 42)
- How to declare variables?
- For the global scope you can implement it yourself, outside of function scope in javascript.
- i.e. (in the execution context javascript file)
const state = {};
- i.e. (in the execution context javascript file)
- For the local scope you can use the keyword
this
in javascript functions.- i.e.
function def(varName, value) { this[varName] = value; }
- i.e.
- For the global scope you can implement it yourself, outside of function scope in javascript.
- How to use Lazy Node (
#
)?- as a function from javascript.
- you can store it like any variable, i.e.
(def :double #(mul 2))
. - you can pass arguments while executing a Lazy Node, like for other nodes. i.e.:
// Metalthea program: (for 0,3,1 #(js.log) > join)
// javascript context:
const execCtx = {
for: function(from, to, step, bodyLambda) {
let result = [];
for (let i = from; i < to; i += step) {
result = result.concat(bodyLambda(i, from, to));
}
return result;
},
log: function(...args) {
return `console.log(${args.join(",")})`;
},
join: function(arr) {
return arr.join("; ");
}
};
// output == `console.log(0,0,3); console.log(1,0,3); console.log(args: 2,0,3)`
Git repository
License
MIT © Lucien Boudy