Need a New Language - Macros are the Answer

VERSION 2 Published

Created on:Sep 27, 2007 9:54 AM by curl - Last Modified:  Sep 27, 2007 5:27 PM by curl

Posted: Sun Jan 01, 2006 3:01 pm
Friedger-----

With Macros, the Curl language offers a powerful construct that allows you to extend the language in almost any way you want -- you can even go so far as to create an entirely new language, to suit some specific project or customer needs. This article shows you how to take advantage of these Curl Code Factories. - by Friedger Müffke

Table of Contents:

  • - 1. Intro to Macros
  • - 2. Building Your Own Macros
  • - - - a. Creating new CurlCode
  • - - - b. Returning Curl Code
  • - 3. Macro Gallery
  • - 4. Macro Abuse
  • - 5. Advanced Macros

Is this something that might happen to one of your users?

"SyntaxError[2]: Expected ',' found 'value'... -- Arrgh. Not Again...!", Peter has been trying for the last half an hour to get the new level for his latest Curl game to function properly. It looked like it only needed a few simple lines of Curl code, that almost read like plain English -- he thought that he could handle that, even though he's quite new to programming with Curl -- but as always, the devil is in the details. So Peter downloaded the documentation and is now trying to figure out why a comma needs to go somewhere...

If you were the developer of the Game environment that Peter is trying to get to function properly, you could make use of Curl's wickedly powerful Macro constructs to simplify things for your users, for instance, Peter. Since version 2.0 of the Curl API, Macros have been available to all of us Curl developers -- they have the potential to solve a myriad of problems that are not solvable in other languages. In the previous example you could write a completely new game language, that offered your users simplified and flexible constructs that made sense for the games they are creating or modifying.

Intro to Macros -- The Curl Code Factories

In general terms, you can think of Macros as highly flexible Procedures that automatically generate Curl code. Macros are usually used to simplify the use of repetitive code in your own projects (such is the case with Curl's {on Event do ...} Macro) or to allow the creation of completely new syntax for special uses -- for instance a simplified scripting language that was intended to allow a non-technical user to create sophisticated games.

Macros are a bit difficult to explain -- a simple example of a Macro will leave you wondering why wouldn't one just write a simple procedure to do the same thing (without all the fuss of figuring out Macro syntax) and a complex example will leave you scratching your head and quickly dropping the whole Macro thing in favor of options that you already clearly understand. One of the problems in presenting a good example, is that most of the really good reasons to use Macros for your day to day work, are already implemented in the core Curl language (great for us when working with Curl, but makes it harder to explain those pesky Macros!). Internal Macros such as {on Event do ...}, {with-render-properties ...} or {with-busy-cursor} should be familiar to all of you who have worked with Curl. So, "why should I go through the effort of learning and writing Macros in the first place?"

Macros have a crucial advantage over plain old procedures -- you have complete control of the arguments of a Macro. The parameters don't have to be separated by commas, you can use spaces, place them logically into square brackets, enter strange symbols or some text of your choosing. Special characters can take on the roll of a procedure's commas, keywords can influence the functionality of the Macro and you can achieve all of this at the same time. Using Macros it becomes possible to circumvent the standard Curl language syntax, in effect creating a new language element of your own. This can be advantageous for making your code easier to understand and its meaning more clear (the holy grail of programming). The other use for Macros is in creating a new language, intended for use by others -- for instance in writing mathematical formulas or a collection of functions for use in a spread sheet application. These users wouldn't have to know anything about the Curl language itself to use these constructs, which makes Macros an incredibly powerful feature of Curl.

Building your own Macros

An important point to keep in mind, when programming Macros, is that a Macro must be defined in a different package than the one it's being used in.

A Macro is defined in a similar way to standard Curl procedures, beginning with the define-macro keyword. Macros don't have return types -- they always return Curl code. The start of a new Macro looks something like this:


Code:
{define-macro public {do-nothing ... } ...}

Macros need to be defined as public, as they are for use outside of the package they are created in.

When passing parameters (arguments) to a Macro, you will have to get used to a different syntax than you have grown accustomed to using with procedures. Parameter names must start with ? (question mark). For example: ?my-param (you can also just use a plain ? as well). You also need to assign the parameter a type in the form of a CurlSource type (identifier, expression, statement, token, text or verbatim). Note that for Macros the listing of parameters and their separators is called a pattern-template and not a "argument/parameter list" (as with procedures).

This difference needs to be understood clearly before you can start to create your own Macros. With a procedure, the type of a parameter (argument) is a Data type, such as an int or a VBox. Macros use a pattern template -- where the "type" of a "parameter" is actually what kind of pattern it must match. Each one of these "patterns" is called an Input Pattern. Input Patterns can be simple, such as identifier, or complex such as {bounded-sequence ...}. Following are some examples of available text types for a Macro's parameters. Generally, you are defining input patterns in your Macro that must then be matched exactly (except for white space) when the Macro is being used.
* ?str:{literal String} \\ For a string \\ * ?index:{literal int} \\ For a number \\ * ?x:identifier \\ For a variable \\ * ?list:{comma-sequence ?:{literal int}} \\ For a list of numbers, separated by comma \\ * ?head:{literal int} ?tail:{sequence ?:{pattern ; ?:{literal int}}} \\ For a list of numbers separated by semicolons, with at least one element
Note that the "outer" parameters can use names, for instance ?list, whereas parameters inside of {sequence ...}, {comma-sequence ...}, etc. can only use a lone ? without a name.

The most interesting aspect of creating Macro input parameters (input patterns) is that you can add any text you desire between the parameters. For instance, you might want to separate a list of numbers with semi-colons instead of commas, or add the text "concerning topic" -- the only limit to what you can use is your imagination! You aren't required to use commas between parameters, for instance, the Sum Macro from the above example, in a table calculation requires that you separate the Row and Column with a ":" and that the beginning and end of the area is separated by a semi-colon. So you can call the macro like {sum A 1:B 5} to calculate the sum of row A and B and column 1 to 5.

The beginning of the macro definition looks like the following.

Code:
{define-macro public 
{sum 
?A:identifier ?n:{literal int} 
: 
?B:identifier ?m:{literal int} 
} ..... 
} 

An example of selecting cells in a spreadsheet for the {sum ...} macro.

The colon has been placed on its own line to make clear that it isn't part of a new parameter definition, but rather just a plain old colon. To use a question mark in text you would need to use ?? (the ? is an escape character for Macros).

For a quick test of your understanding, can you figure out what the Macro definition would be for the following Macro?

{topic title: ....}

The names that are used in the parameter list to a Macro (for instance A, B, str, index, ?a-list, x, head) can be used within the body of the Macro just like normal variables. These variables are all of type CurlSource or one of its sub-classes Literal, Identifier, etc. Often you will find that you can output the parameter unchanged, in this case you won't have to worry about the CurlSource type. If you need to actually use the value being stored in the parameter, you will have to access the fields of the CurlSource class (or Literal, etc). This is done most easily using the method {get-text} that just returns the Curl Code as plain text. For instance to retrieve the numbers from the list parameter ?a-list, you can simply use the method {?a-list.get-text}. The values for simple types of Literal data, such as numbers or strings, can be obtained with value, for instance, A.name returns the identifiers name as a String. Note that you can't actually use the contents of a variable, as the "Identifier" only represents CurlCode and not the actual object or data contained within. Once you click on this point, you can start with the actual programming of your Macro.
1,2,3 ?list:{comma- sequence ?:{literal int}} {list.get-text} = "1,2,3"
5 ?index:{literal int} index.value = 5
A ?row:identifier row.name = "A"

Creating new CurlCode

The Macros you create must return new CurlCode. This isn't Text but rather an Object of type CurlSource. This can be comfortably achieved by using (you guessed it!) a Macro -- {expand-template}. You simply write the code to generate as "Parameters" to the expand-template macro:


Code:
{expand-template 
{Frame "Hello World"} 
} 

This returns CurlCode that will create a Frame with the contents "Hello World" at runtime.

When using the parameters inside of the expand-template Macro, you need to precede them with the ? escape character. During expansion, this is then replaced with the code stored in the variable. For example...

Code:
{expand-template 
{output ?A} 
} 

...returns curl code, that if executed will output the value of A.

The escape character ? marks the location of a CurlSource object and replaces it with the corresponding code when it is expanded. The statement doesn't always have to be a variable that was previously defined. You can create a Literal (or similar object) on the fly. For example: ?{Literal my-data.size} returns the size of the my-data Array. Using this construct can save you from having to define extra variables in your Macro body code.

To explicitly use the Objects of type Literal or CurlSource, you first need to import the Curl package CURL.LANGUAGE.SOURCE. It is usually easier to just use {expand-template} inside of the initial {expand-template} Macro to create Curl code. The following

Code:
{expand-template 
{let ?{Identifier foo}:int = 0}} 

and

Code:
{expand-template 
{let ?{expand-template foo}:int = 0}} 

are identical and both return Code that defines the variable foo.

Returning Curl Code

Before the Macro's {return} statement, you can still manipulate the Curl Code. For instance in the Macro Sum, the parameters A,n, B,m are used to calculate the sum of the tabular data.

The Macro {define-struct} analyzes the List called fields and creates the corresponding Curl Code. The statement {syntax-switch} is used to analyze the fields List. This is a switch construct that matches patterns of Curl Code. Between the case and do is a Curl Code pattern that needs to be matched. {syntax-switch} is very similar to a normal {switch} statement and is used to insulate you from needing to know the inner workings of the CurlSource classes. The patterns that are given in each case block can define new variables. In the example of {define-struct}, all the elements of the List that are defined by ?fieldname:?fieldtype are separated into the variables fieldname and fieldtype. Using this technique it is then possible to access elements in the parameters list that are defined with a ? and not names.

See the example define-struct (in file macros.scurl)

Code:
{syntax-switch entry, must-match? = true 
case {pattern 
?field-name:identifier 
: 
?field-type:expression 
} 
do 
{init-list.append 
{expand-template 
set self.?field-name = ?field-name 
} 
} 
} 

When you have finished building your Macro, you will need to test it to see if it functions correctly. The Curl language has a special construct that helps greatly in debugging Macros -- {expand-to-string}. When you call {expand-to-string} it is recommended that you wrap it in {pre {expand-to-string}} so that the displayed output is more readable ({pr ...} preserves the whitespace). Calling {expand-to-string} returns the resulting Macro's code as a String. A common mistake in creating Macros is forgetting the needed ?'s, so that the Macro accepts the parameters that you want it to.

Macro Gallery

Here are some examples of Macro calls that are not possible to reproduce with standard procedures.

Code:
{import-mysql-dll 
"mysql_get_client_info":{proc-type {}:int} = 
{define-proc {mysql-get-info {m:MySql}:String {return m.result}}, 
"mysql_get_server_info":{proc-type {}:int} = 
{define-proc {mysql-get-info {m:MySql}:String {return m.result}}, 
... 
} 

|| Solve a Rule of Three 
{rule-of-three 2l Milk = 1.89EUR, 1EUR = ?, 10l Milk = ?} 

|| Solve an algebraic Equation 
{solve x^2 + 2x = 10} 

|| Define a Matrix 
{define-matrix city-town-river:{Matrix String} = 
("Aachen", "Australia", "Alster"), 
("Basel", "Belgum"), 
("Chemnitz") 
} 
The call {value city-town-river[0,2]} would return the value "Alster". 

|| Using Curl Code without straight quotes. 
let s:String = {code A String without |""| but with {Parenthesis}} 

Macro Abuse

Macros have several limitations to them, that have been imposed due to security restraints. It isn't possible to use global variables, for instance {get-the-applet}. It is also not possible to use public variables from other packages, inside a Macro -- as these could possibly be used by several Applets and the runtime would not be able to decide which variable would be valid. Further, it is not possible to import packages or to read files in a Macro.

Due to these limitations, it is unfortunately not possible to create a Macro that would automatically import a package when you passed it the just the package file's location, as the Macro would require the ability to read the package data durring the execution of the Macro.

Advanced Macros

This section takes a look at Macros that create new variables in the Curl Code they return.

Code:
{return 
{expand-template 
{let ?{expand-template x}:int = 0} 
} 
} 

The variable x is then available in the Applet or Package where the Macro is being called from. Normally it is not possible to use this variable outside of the Macro or during the next call to the same Macro. This greatly simplifies the creation of Macros -- otherwise you would have to make sure that variable x wasn't defined already. There are actually some occasions where you will want to have access to a specific variable in various Macros. In this case you need to use expand-template with ?= instead of the normal ?.

To make this point clear we will dig out our Sum Macro again. It would make more sense to have a user defined table instead of just one predefined table my-data defined in the package of the macro. This can be achieved by the previously described method.

The Sum Macro somehow needs to know which Table it should use. You could pass the table as a parameter to Sum, but if you are doing a lot of sum calculations this can quickly get tedious. Instead of passing the table as a parameter, a variable called actual-table is defined in the package of the macro. The macro new-table just sets this variable and in the macro sum one can use it and access the actual valid table. In order to make this inter-macro use of variables it is necessary to prefix variable names with ?=, i.e. ?=var-name instead of just ?var-name. Note that with the macro new-table, the table is also initialized -- that means the macro returns code for this initialization.

With the following code one can use the sum macro with the user-defined table:

{new-table table-1} {set table-1{color}{color:#008000}= .....} {sum A 1: B 2}

Once you grasp the basics of how Macros function in Curl, you will quickly see that there are almost no limits to what you can achieve with them. Let me know if you find this article interesting and if you would like to see more articles on some of the more advanced features of Curl.

{author of article: Friedger Müffke, location = "Berlin, Germany"}

Average User Rating
(0 ratings)




There are no comments on this document