Thursday 6 March 2014

How to write macros

I wrote this as part of a learn exercise about macros. I am no expert; if you spot errors please do tell me and I will correct them. You should have some understanding of objects in JavaScript before reading this!

Introduction

Macros are used in passages like this:

<<macroname param1 param2 param3 ... >>

When Twine/Twee hits a section of text like this, it takes a look at the macros object. This object has a whole bunch of properties, and Twine/Twee tries to match macroname to one of those properties. if successful, it then calls the handler function. That may sound incredible obscure, but if we have a go at making our own macro, it may become more clear.

This is going to go in a script passage, because it is done in JavaScript. Here is our first macro, called first:

  macros.first = {
    handler: function(place, macroName, params, parser) {
    },
  };


It does not actually do anything if you have <<first>> in the passage, but it does exist in Twee as a macro.

It is just an object

Let us look at that in detail. If you read the [url=http://twinery.org/forum/index.php/topic,1516.0.html]Objects are Your Friends[/url] thread at Twinery.org, you will have seen this in the OP (I have removed the <<set>> for clarity):

$foo= {
  property1: "bar",
  property2: 894,
  property3: $baz,
}


It is doing the same thing! It is creating an object, and setting properties on the object. In one case the object is $foo, in the other it is macros.first. The $%foo object has three properties, called property1, property2 and property3; while macros.first just has one, called handler.

Why macros.first? Well, that attaches our new object first to the existing macros object. That means that Twee will find it when it looks for a macro.

Why handler? Because that is what Twee will try to use when it runs your macro.

The properties assigned to $foo are simple values; for a macro, you need to assign a function, and not just any function. Twee is going to call your function with four parameters. You can call those functions anything you like, but it is easier to stick with those names so you know what Twee will be sending.

Alternative format

You can also define your macro like this:

  macros['second!'] = {
    handler: function(place, macroName, params, parser) {
    },
  };


I do not think that that is as neat, but it does allow you more flexibility in what you name your macro - you can include various punctuation marks in the name. Personally, I would stick to only using letters, numbers and underscores, and use the first form.

Doing stuff

Here is a third macro, and this one actually does stuff.

  macros.third = {
    handler: function(place, macroName, params, parser) {
      new Wikifier(place, " macroName=" + macroName + " params=" + params + "");
    },
  };


This lets us see what some of those parameters are. The first is a representation of your passage (actually a HTMLDivElement object), and you can use that to add content to the page. Do that through the Wikifier. In brief, what that does is take the existing text (place) and append your new text. The basic use is like this:

  new Wikifier(place, mystring);
 
And you can change mystring however you like. You can have several lines like that in one macro, each one adding its own bit to the page.

In the example above, I am adding macroName and params. As you might guess, macroName is just the name of the macro. Params is an array of what comes after the name. For example:

<<third>>
<<third one two three>>


In the first instance, params would be an array of length zero, as there is no further text after the name. In the second instance, the array is:

['one', 'two', 'three']

Here is what is actually output:

macroName=third params=
macroName=third params=one,two,three


Macros in macros

You can use a macro inside a macro. The Wikifier will sort out running the inner macro. In this example, macro third is put in the text.

  macros.fourth = {
    handler: function(place, macroName, params, parser) {
      new Wikifier(place, "Using third: <<third " + params[0] + ">>");
    },
  };


Now we are assuming that there is a parameter. Never wise! Let us modify that macro to check that the parameter exists.

  macros.fourth = {
    handler: function(place, macroName, params, parser) {
      if (params.length === 0) {

        throwError(place, "<<" + macroName + ">>: no parameter given");
        return;
      }
      new Wikifier(place, "Using third: <<third " + params[0] + ">>");
    },
  };


You will see an extra line of code. First it checks if the number of parameters is zero. If it is, take evasive action! It throws an error with the given message, which immediately stops the rest of the macro doing anything.

Accessing variables

You cannot access variables in the normal way, but there is a way to do it. All variables you use in your passages are actually just properties of an object, state.history[0].variables (or
state.active.variables in Sugarcube). This macro puts the value of the variable $myname into the text.

  macros.fifth = {
    handler: function(place, macroName, params, parser) {
      new Wikifier(place, state.active.variables.myname);
    },
  };


The passage might look like this:

<<set $myname = 'Bob'>>
<<fifth>>


No comments:

Post a Comment