Writing a DSL in Lua

download Writing a DSL in Lua

of 14

Transcript of Writing a DSL in Lua

  • 7/26/2019 Writing a DSL in Lua

    1/14

    Writing a DSL in LuaPostedAugust 08, 2015by leafo(@moonscript) Tags: lua

    22 points Tweet

    DSLs, or domain specific languages, are programming

    languages that are designed to implement a set of features

    specific to a particular problem or field. An example could

    be Make, the build tool, which is a specially designed

    language for combining commands and files while managing

    dependencies.

    Contents

    Dropping the parenthesis

    Chaining

    Using function environments

    Implementing the HTML builder

    Closing

    A lot of modern programming languages have so much

    https://www.gnu.org/software/make/http://www.reddit.com/r/lua/comments/3g9pny/writing_a_dsl_in_lua/https://twitter.com/intent/tweet?hashtags=lualang&original_referer=http%3A%2F%2Fleafo.net%2Fguides%2Fdsl-in-lua.html&ref_src=twsrc%5Etfw&text=Writing%20a%20DSL%20in%20Lua&tw_p=tweetbutton&url=http%3A%2F%2Fleafo.net%2Fguides%2Fdsl-in-lua.html&via=moonscripthttp://leafo.net/https://www.gnu.org/software/make/https://twitter.com/intent/tweet?hashtags=lualang&original_referer=http%3A%2F%2Fleafo.net%2Fguides%2Fdsl-in-lua.html&ref_src=twsrc%5Etfw&text=Writing%20a%20DSL%20in%20Lua&tw_p=tweetbutton&url=http%3A%2F%2Fleafo.net%2Fguides%2Fdsl-in-lua.html&via=moonscripthttp://www.reddit.com/r/lua/comments/3g9pny/writing_a_dsl_in_lua/http://www.reddit.com/r/lua/comments/3g9pny/writing_a_dsl_in_lua/http://www.reddit.com/r/lua/comments/3g9pny/writing_a_dsl_in_lua/http://www.reddit.com/r/lua/comments/3g9pny/writing_a_dsl_in_lua/https://twitter.com/moonscripthttp://leafo.net/
  • 7/26/2019 Writing a DSL in Lua

    2/14

    flexibility in their syntax that its possible to build libraries

    that expose their own mini-languages within the host

    language. The definition of DSL has broadened to include

    these kinds of libraries.

    In this guide we'll build a DSL for generating HTML. It looks

    like this:

    html { body {

    h1 "Welcome to my Lua site", a { href

    =

    "http://leafo.net", "Go home" } }}

    Before jumping in, here are some DSL building techniques:

    Dropping the parenthesis

    One of the cases for Lua as described in its initial public

    release(1996) is that it makes a good configuration language.

    Thats still true to this day, and Lua is friendly to building

    DSLs.

    A unique part about Luas syntax is parenthesis are optional

    http://www.lua.org/spe.html
  • 7/26/2019 Writing a DSL in Lua

    3/14

    in some scenarios when calling functions. Terseness is

    important when building a DSL, and removing superfluous

    characters is a good way to do that.

    When calling a function that has a single argument of eithera table literal or a string literal, the parenthesis are optional.

    print"hello"--> print("hello")my_function { 1,2,3} --> my_function({1,2,3})

    -- whitespace isn't needed, these also work:

    print"hello"--> print("hello")my_function{ 1,2,3} --> my_function({1,2,3})

    This syntax has very high precedence, the same as if you

    were using parenthesis:

    tonumber"1234" 5-- > tonumber("1234") + 5

    Chaining

    Parenthesis-less invocation can be chained as long as each

    expression from the left evaluates to a function (or a callable

    table). Heres some example syntax for a hypothetical web

    routing framework:

  • 7/26/2019 Writing a DSL in Lua

    4/14

  • 7/26/2019 Writing a DSL in Lua

    5/14

    chaining w ork for any length.

    Using function environments

    When interacting with a Lua module you regularly have to

    bring any functions or values into scope using require . When

    working with a DSL, its nice to have all the functionality

    available w ithout having to manually load anything.

    One option would be to make all the functions and values

    global variables, but its not recommended as it might

    interfere with other libraries.

    A function environmentcan be used to change how a

    function resolves global variable references within its scope.

    This can be used to automatically expose a DSLs

    functionality without polluting the regular global scope.

    For the sake of this guide I'll assume that setfenv exists in

    the version of Lua we're using. If you're using 5.2 or above

    you'll need to provide you own implementation:

    Implementing setfenv in Lua 5.2, 5.3, and above

    Heres a function run_with_env that runs another function

    with a particular environment.

    http://leafo.net/guides/setfenv-in-lua52-and-above.html
  • 7/26/2019 Writing a DSL in Lua

    6/14

  • 7/26/2019 Writing a DSL in Lua

    7/14

    programmatically created. This is how the HTML builder

    DSL will be created.

    Implementing the HTML builder

    Our goal is to make the following syntax work:

    html { body { h1 "Welcome to my Lua site",

    a { href

    =

    "http://leafo.net", "Go home" } }}

    Each HTML tag is represented by a Lua function that willreturn the HTML string representing that tag with the correct

    attribute and content if necessary.

    Although it would be possible to w rite code to generate all

    the HTML tag builder functions ahead of time, a function

    __index metamethod will be used to generate them on the fly.

    In order to run code in the context of our DSL, it must be

    packaged into a function. The render_html function will take

    that function and convert it to a HTML string:

  • 7/26/2019 Writing a DSL in Lua

    8/14

    Note:

    Note:

    render_html(function

    ()

    return

    div { img { src

    =

    "http://leafo.net/hi"}

    }

    end

    ) -- >

    The img tag is self-closing, it has no separate close tag.

    HTML calls these void elements. These will be treated

    differently in the implementation.

    render_html might be implemented like this:

    local

    function

    render_html(fn) setfenv(fn, setmetatable({}, { __index

    =

    function

    (self, tag_name)

    return

    function

    (opts)

    return

    build_tag(tag_name, opts)

    end

    end

    }))

    return

    fn()

    end

    The build_tag function is where all actual work is done. It

    takes the name of the tag, and the attributes and content as a

    single table.

    This function could be optimized by caching the

    http://www.w3.org/TR/html-markup/syntax.html#void-elements
  • 7/26/2019 Writing a DSL in Lua

    9/14

    Note:

    generated functions in the environment table.

    Thevoid elements, as mentioned above, are defined as a

    simple set:

    local

    void_tags=

    { img

    =

    true, -- etc...}

    The most efficient way to concatenate strings in regular Lua

    is to accumulate them into a table then call table.concat .

    Many calls to table.insert could be used to append to this

    buffer table, but I prefer the following function to allow

    multiple values to be appended at once:

    local

    function

    append_all(buffer, )

    for

    i=

    1,select("#", )do

    table.insert(buffer, (select(i, )))

    end

    end

    -- example:

    -- local buffer = {}-- append_all(buffer, "a", "b", c)-- buffer now is {"a", "b", "c"}

    append_all uses Luas built in function select to avoid

    any extra allocations by querying the varargs object instead of

    http://www.w3.org/TR/html-markup/syntax.html#void-elements
  • 7/26/2019 Writing a DSL in Lua

    10/14

    creating a new table.

    Now the implementation of build_tag :

    local

    function

    build_tag(tag_name, opts)

    local

    buffer=

    {"")

    else

    append_all(buffer, ">")

    if

    type(opts)==

    "table"then

    append_all(buffer, unpack(opts))

    else

    append_all(buffer, opts)

    end

    append_all(buffer, "")

    end

    return

    table.concat(buffer)

    end

    There are a couple interesting things here:

    The opts argument can either be a string literal or a table.

    When its a table it takes advantage of the fact that Lua tables

    are both hash tables and arrays at the same time. The hash

    table portion holds the attributes of the HTML element, and

  • 7/26/2019 Writing a DSL in Lua

    11/14

    Note:

    the array portion holds the contents of the element.

    Checking if the key in a pairs iteration is numeric is a quick

    way to approximate isolating array like elements. Its not

    perfect, but will w ork for this case.

    for

    k,vin

    pairs(opts)do

    if

    type(k)~=

    "number"then

    -- access hash table key and values

    end

    end

    When the content of the tag is inserted into the buffer for the

    table based opts , the following line is used:

    append_all(buffer, unpack(opts))

    Luas built in function unpack converts the array values in a

    table to varargs. This fits perfectly into the append_all

    function defined above.

    unpack is table.unpack in Lua 5.2 and above.

    Closing

  • 7/26/2019 Writing a DSL in Lua

    12/14

    This simple implementation of an HTML builder that should

    give you a good introduction to building your own DSLs in

    Lua.

    The HTML builder provided performs no HTML escaping. Itsnot suitable for rendering untrusted input. If you're looking

    for a way to enhance the builder then try adding html

    escaping. For example:

    local

    unsafe_text=

    [[a

    render_html(function

    ()

    return

    div(unsafe_text)

    end

    )

    -- should not return a functional script tag:--