Quasiliterals

Quasiliterals, or QLs, are an important part of Monte syntax which allows us to embed arbitrary DSLs into Monte. With the power of QLs, Monte can be extended into new territory in a very neat way.

What’s a Quasiliteral?

This is a quasiliteral:

`Backticks start and end quasiliterals`

A quasiliteral can have values mixed into it with $. A value can be a name:

def name :Str := "Todd"
`Hello, $name!`

A value can also be an expression, using brackets:

`2 + 2 = ${2 + 2}`

Quasiliterals can be used as patterns:

# Equivalent to: def =="self" := "self"
def `self` := "self"

Quasiliteral patterns also permit pattern-matching with @ to retrieve single names:

def `(@first, @second)` := "(42, 5)"

And any pattern can be used with brackets:

def `x := @{var x}` := "x := 7"
x += "-11" # What? I like slushies!

Finally, there are different quasiparsers, or QPs, which each have different behavior:

# `` makes strings
`def x := 42` :Str
# b`` makes bytestrings
b`def x := 42` :Bytes
# m`` makes Monte AST objects
m`def x := 42` :(astBuilder.getAstGuard())

How to Use QLs

A quasiliteral expression starts with the name of a quasiparser (which can be empty) followed by a backtick. Then, a mixture of strings and holes are allowed, followed by a final backtick. The holes can either be expression-holes, with $, or pattern-holes, with @.

Warning

Pattern-holes cannot be used in QL expressions, only in QL patterns. Using a pattern-hole in a QL expression is a syntax error!

Builtin Quasiparsers

There are three common QPs included in Monte’s safe scope.

Simple

The simple or empty QP builds strings:

`string` == "string" # true

It can mix any value into a string, even values that don’t pass Str:

`${7}` == "7" # true

The simple QP does this by calling M.toString/1 on the values. Correspondingly, the value’s _printOn/1 is called, and can be customized:

object shirt { to _printOn(out) { out.print("tye-dye shirt") } }
def description :Str := `I am wearing a $shirt.`

When used as a pattern, the simple QP performs very simple but straightforward and powerful string parsing:

def container := "glass"
def `a $container of @drink` := "a glass of lemonade"

Bytes

The bytes QP builds bytestrings:

b`asdf`

The encoding of characters is unconditionally Latin-1. Non-Latin-1 characters cause errors to be thrown at runtime:

b`ErrorRaiser™`

Other than that quirk, the bytes QP behaves much like the simple QP, including parsing:

def b`@header:@value` := b`x:12`

Monte

Finally, the Monte QP builds Monte ASTs from literal Monte source:

m`def x := 42`

The Monte QP can be used for code generation, since it evaluates to objects usable with eval/2:

eval(m`2 + 2`, [].asMap())

Custom Quasiparsers

Anybody can write their own quasiparser.

Parsing with Values

The first half of the QP API deals with building the initial structure and including values.

.valueHole(index :Int) should create a value marker which can be used in place of some value which will be included later. .valueMaker(pieces :List) will be called with a list of pieces, which can be either strings or value markers, and it should return a partial structure. That structure can be completed with its .substitute(values :List), which provides a list of values that can be swapped with the value markers.

To see how this API all comes together, let’s look at the kernel expansion of a simple QP call:

`Just another $day for this humble $string.`

What Monte actually does is call .valueMaker/1, like so:

::"``".valueMaker(["Just another ", ::"``".valueHole(0),
                   " for this humble ", ::"``".valueHole(1),
                   "."]).substitute([day, string])

Parsing Patterns

The pattern API is similar and builds upon the expression API.

First, the .patternHole/1 method allows pattern hole markers to be built, just like with value holes. Then, the structure is built with .matchMaker/1 instead of .valueMaker/1. This structure should have a completion method, .matchBind(values :List, specimen, ej) which attempts to unify the specimen with the structure completed by the values or eject on failure.

Here’s a simple pattern:

def `how ${hard} could it be to match @this?` := "not hard, just complex"

And its expansion:

def via (_quasiMatcher.run(::"``".matchMaker(["how ", ::"``".valueHole(0),
                                              " could it be to match ",
                                              ::"``".patternHole(0),
                                              "?"]),
                           [hard])) [this] := "not hard, just complex"

Note how the _quasiMatcher helper in the safe scope takes care of the extra runtime plumbing.