Parenthephobia


GPP: A pre-processor

Posted on Tue, Dec 22, 2015

GPP is a simple macro processor designed with Go in mind.

It is fashioned in the style of the C pre-processor, with adjustments to make it suit Go better (IMO).

Installation

It’s on GitHub, and also on rubygems: gem install gpp.

Usage

$ gpp [file...]

If no input files are specified, stdin is used.

Real-world example

A macro to reduce error-handling boiler-plate in Go.

1
2
3
4
5
6
#define try ... {
  @...
  if err != nil {
    return
  }
}

The @try macro returns from the function if err is non-nil after whatever statement follows the try is executed.

It should generally be used with a function with an error return value named err.

1
2
3
4
5
6
7
func parse(text string) (act *Action, err error) {
  @try u, err := genURL(text)
  @try req, err := genRequest(u)
  @try out, err := doRequest(req)
  act, err = outToAct(out)
  return
}

Language

Syntax

Like CPP, directives start at the beginning of a line, with a #.

Macro invocations start anywhere and begin with a @.

Both support two syntaxes for their arguments: a conventional CPP-style comma-separated list surrounded by parentheses, and a whitespace separated list of arguments terminated with a newline. In the latter syntax, quote marks and balanced braces may be used to escape newlines.

Two syntaxes are offered to allow for both statement-like and function-like macro invocation, as appropriate.

Directives

Recognized directives are #import and #define.

Import

#import FILENAME

Reads and processes a gpp source file and imports its definitions into the current environment. Its output is discarded.

Source files are searched for in the working directory.

Define variable

#define NAME VALUE

Defines a macro variable named NAME. When invoked, it will be replaced with VALUE. VALUE is evaluated at invocation time.

Define function

#define NAME ARGS... BODY

Defines a macro function named NAME. The ARGS are the names of arguments, and the BODY is what the macro will be replaced with when invoked. The body is always the last argument, so you’ll need to quote it if it should contain spaces.

When invoked, the actual arguments are temporarily assigned to macro variables with the corresponding argument names. A special final variable named ..., if it exists, will receive all remaining arguments.

The body can contain further imports and definitions. These will be scoped to within the invocation of the body.

Scope

Variables and functions are dynamically scoped. This means you can temporarily override a globally-defined macro inside another macro. You must make sure you don’t do this accidentally.

However, macro bindings don’t out-last the block they were in, which means you can’t write a macro which defines a macro that you can use outside that macro.

Output

GPP outputs non-macro non-directive input as-is, and macro input after macro substitution.

GPP does not output any line markers, nor does it strip comments.

Rationale

@ is used as the macro prefix so that it is clear from just looking the code whether something is a macro or a normal function.

@ specifically is used because it isn’t used in Go (outside strings or runes), so it rarely interferes with existing code.

#import is not called #include to help hint that it is different from CPP #include. Namely, that imports are only processed for their macro definitions, and not to produce output.

#import doesn’t produce output to dissuade people from using GPP for code modularity.


© Nathan Baum 2015