This project provides a generic framework for writing your own languages in pure lua. Grammars are expressed in a manner similar to eBNF to improve readability.
This is achieved using combinator parsers & Lua metamethods.
Example language implementations can be found in Languages/
.
To create a new language, you will need:
- A file for your language's token matchers (i.e. String, Comment, the keyword "while", the symbol "#").
- Your language's grammar, written using the fpg eBNF notation
- A bootstrap file to orchestrate the loading of your language.
The representation of eBNF that is used by eLu differs from the standard in the following ways:
Usage | Standard notation | fpg notation |
---|---|---|
Definition | = | = |
Concatenation | , | , |
Termination | ; | ; |
Alternation | | | / |
Optional | [ ... ] | c{ ... } |
Zero or more | { ... } | cm{ ... } |
One or more | ... { ... } | m{ ... } |
Grouping | ( ... ) | d{ ... } |
Terminal string | " ... " | " ... " |
Terminal string | ' ... ' | " ... " |
Comment | (* ... *) | --[[ ... ]] |
Exception | - | <Not used> |
Due to limitations of Lua, concatenation can only be carried out within a definition. For example the eBNF statement
handwear = "Red", "Glove";
Must be written as follows
handwear = d{ "Red, "Glove" };
Futhermore, alternations cannot begin on string literals, as that would involve overriding Lua's global string metatable. Thusly, the eBNF statement
footwear = "Boots" / "Shoes";
Must be written as follows
footwear = null / "Boots" / "Shoes";
Nested definitions in the grammar are automatically inlined when ran against a token stream. This means that grammar definitions can be complex without impacting the complexity of the output stream. For example the eLu eBNF statement
gloves = d { d { "Thumb" / "Missing" },
d { "Index" / "Missing" },
d { "Middle" / "Missing" },
d { "Ring" / "Missing" },
d { "Pinky" / "Missing" }};
Would, when ran, return a token stream similar to this:
{
name = "grammar",
tokens = {
{ "type" = "Finger", "content" = "Thumb" },
{ "type" = "Finger", "content" = "Missing" },
{ "type" = "Finger", "content" = "Middle" },
{ "type" = "Finger", "content" = "Ring" },
{ "type" = "Finger", "content" = "Missing" }
}
}
However, as nesting often simplifies evaluation, it can still be achieved by naming a definition. This can be done as follows
gloves = d { d"Opposables"{ "Thumb" / "Missing" },
d { "Index" / "Missing" },
d { "Middle" / "Missing" },
d"Expendables"{ d { "Ring" / "Missing" }, d { "Pinky" / "Missing" }}};
Which would return
{
name = "grammar",
tokens = {
{
name = "Opposables",
tokens = {
{ "type" = "Finger", "content" = "Thumb" }
}
},
{ "type" = "Finger", "content" = "Missing" },
{ "type" = "Finger", "content" = "Middle" },
{
name = "Expendables",
tokens = {
{ "type" = "Finger", "content" = "Ring" },
{ "type" = "Finger", "content" = "Missing" }
}
}
}
}
fpg does not have any dependencies (besides Lua version >= 5.2) However, your LUA_PATH environment variable should be altered to allow the files to see one another. For example
#!/usr/bin/env bash
# Set LUA_PATH before running a test file.
cd /home/lua/fpg/Test/Languages/Lua
(export LUA_PATH='/home/lua/?.lua;;' \
lua53 test.lua)
You can implement a language yourself by loading its bootstrap file. For example
local luaLang = require "fpg.Languages.Lua.bootstrap"