|Home > Technical articles||e-mail: firstname.lastname@example.org github: ltratt twitter: @laurencetratt|
March 21 2007
Recently I've made a previously lop-sided part of Converge much more internally consistent, and in so doing realised a useful new connection between two language features. Since it's to do with a unique part of Converge, it makes an interesting little study. In essence, Converge has a macro-esque system that is inverted from the traditional LISP/Scheme style. LISP macros are special constructs, with some ordinary looking function calls actually being macro calls. In Converge (as in Template Haskell), macros are normal functions or expressions; macro calls however are explicitly identified. In the following example,
func m(): return [| Sys::println("m") |] func main(): $<m()>The splice operator was the first implemented in Converge and can be considered to be the
traditionalsplice operator. It became obvious quite quickly to me that the following idiom has two practical problems when embedding a DSL:
func my_dsl(): return [| ... |] $<my_dsl("""... ... ...""")>The first problem is an aesthetic one. Passing a big string, typically split over multiple lines, to the
Therefore Converge soon grew a second splice operator which I subsequently named the DSL splice operator. It is used as follows:
func my_dsl(dsl_block, src_info): ... $<<my_dsl>>: ... ... ...Basically, the DSL splice operator forgoes the need to wrap up DSL input as a big string. It simply takes the indented block of code underneath the operator as the DSL input, and passes it
rawto the DSL implementation function
src infosto the AST that is created (the src infos that are created are relative to the
So that was where it was left for a long time. The traditional splice operator spliced unmodified ASTs in, and the DSL splice operator spliced in ASTs with extra src infos.
Recently it occurred to me that something was missing. Consider the following code:
func f(): return [| 1.foo |] func main(): $<f()>Since integers don't have a
Traceback (most recent call at bottom): 1: File "test.cv", line 2, column 12 Slot_Exception: No such slot 'foo' in instance of 'Int'.where line 2 refers to the line within
This might sound quite limited, but is, at worst, on a par with any existing macro system I've yet come across. Many macro systems don't record any error information when creating or splicing in ASTs. About the best that I've seen is some Scheme variants which record the splice location of any error; however if a complex AST was spliced in, the user is given no clue as to which part of the AST led to the error.
At this point, the comparison with the DSL splice operator should be obvious, although it escaped me for quite some time: the DSL implementation function called by a DSL splice can customise its error reporting based on the input DSL block. However what we want for the above example isn't manual customisation of the error reporting: we want it to be created automatically. I therefore recently merged a patch into Converge which means that when a traditional splice is performed, the spliced-in AST automatically has added to it the src info for the splice location. For the above example one now gets the following run-time exception:
Traceback (most recent call at bottom): 1: File "test.cv", line 2, column 12 File "test.cv", line 5, column 12 Slot_Exception: No such slot 'foo' in instance of 'Int'.What this means is that the single entry in call stack is associated with two source file locations. Larger examples show what this means more clearly. For example the following exception (created by injecting the same error from above into some real Converge code) shows a backtrace with 3 entries, where 2 of the entries are associated with more than one source location:
Traceback (most recent call at bottom): 1: File "test.cv", line 125, column 2 2: File "test.cv", line 54, column 4 File "test.cv", line 112, column 2 3: File "test.cv", line 78, column 118 File "test.cv", line 113, column 3 Slot_Exception: No such slot 'foo' in instance of 'Int'
So it became obvious to me while I was implementing this new functionality that I had created a symmetry - in the sense of a mirror image - of sorts between the two types of splice. Traditional splices automatically add source information about the splice location, whereas DSL splices don't. If I can think of shorter names to capture this, I may well retrospectively rename the two splice operators, as this concept is the one that most usefully captures the difference between them. More importantly this exercise gave me - if no one else - a more profound insight into splicing. I find this sort of insight, which is a relatively rare event, deeply satisfying: and it all comes from solving a little problem, filling a little gap, and then making connections after the fact.
|Home > Technical Articles||e-mail: email@example.com github: ltratt twitter: @laurencetratt|
|Copyright © 1995-2012 Laurence Tratt|