Return to Renoir
Contents
Renoir manual

Introduction

Renoir is a tool for generating code to be used by a programmer for coding tools that generate formated text. It works by specifying the format of the file in a specialised language, and then compiles this code to target language code (currently only Java), which will allow a programmer to easily output text in that format.

It is currently in a rough state, and I'm just dumping it out there in case someone has a use for it. It is currently used in other Scratchy Labs projects, so will receive ongoing development. If a particular direction is desired by any potential users, feedback will gladly be accepted.

Running Renoir

Renoir is provided as a jar (or source code for compiling if you are brave), and as such, needs to be run with a command similar to:

 
java -jar Renoir.jar  [ options ] files

If no files are specified, then the simple graphical user interface will appear. Otherwise, if "-help" is specified as an option then the following is outputted, and explains the available options:

Renoir help (Renoir --help)

Usage: Renoir [OPTION]... [FILES]...
Renoir is an tool for generating code for generating text in a particular format.

Options
	--help	- Show this message

	-user [user path]	- Sets alternative path for user files
	-output [output path]	- Sets alternative path for the output
	-library [library path]	- Sets alternative path for library files
	-clean	- clear target directories before outputing build results

The user must specify every file that is to produce output. Other Renoir files may be accessed (via the "import" option - see below), but unless specified, those files will not result in any generated java code).

The "user path" is the default path for searching for files specified by "import". The "output path" is the path where all generated code is stored. The "library path" is where files that are shared across projects are stored.

The GUI interface

If no files or options are specified (or the jar is started simply by double-clicking), then a simple GUI interface will appear. This looks similar to the following:

Operation should be reasonably straightforward. The main task is that three folders ought to be specified (two at the minimum).

The first is the "library" folder, which contains Renoir files that are shared amongst projects. The second, the "source" folder, is the folder where all the Renoir files for a project sit. The vital difference between the two folders is that in a build, only files from the "source" folder will acutally result in the creation of any generated code. The library code must be generated seperately, by using it as the source in another project.

The third and final folder is the "target" folder, which is the base folder for the generated code. It ideally should be a folder that is set aside entirely for code generated by Renoir.

Once the "source" folder has been selected, the files in that folder should appear in the "Files" list. Renoir files should end in ".renoir", ".renoirx", or ".ren" (any is fine).

The buttons on the right allow the compiling and building. Compiling is just for testing. The Renoir files will be read and interpreted, and any areas can be noted, but no code will be produced, even if there are no errors.

The two build options do all that "Compile" does, but also produce code, if there are no errors. The difference between the two is that "Build" overwrites files in the target folder, and leaves any generated files that were there previously, but are not part of the new set of files. This can happen as a project changes, as Renoir produces a number of non-user files that are named in a sometimes inconsistent way. Using "Clean Build" will result in the target folder being cleared of all files first (use wisely!).

The Log

The Log informs the user of the compilation/build progress (sorry, no fancy progress bars yet). The output will be self-explanatory, though sometimes the errors given may be a bit strange (the error logging system needs a bit of work), they should direct the user to problems reasonably well. Use the "Clear" button to clear previous output.

Projects

There is support for a simple form of project. Once folders have been set up, they can be saved as a project by using the "Save"/"Save As" option in the "File" menu. As expected, use "Open" to open a previously saved project, and the "Recent" menu to quick load previously saved projects.

Renoir Language Basics

Renoir works by taking files in the "Renoir" language, and through interpretation of that file producing programmer usable code for personal projects. This section explains the components of such input files.

File basics

Files follow a simple format. Basically it is a list of "block" definitions, with "output" declrartions at suitable locations. The "output" declarations work the same as with AustenX output declarations.

An example file will look something like the follow:

output example.moo;

Block1(int x) {
	subs* = SubBlock();
	output {
		"Block:" $x "{" nl 
			
		++ 
			$subs / ", "
		-- "}"
	}
}

SubBlock(int y, string z) {
	ouptut {
		"Sub:" $y " and " $z nl 
	}
}

The above Renoir file has two blocks and one output declaration.

The output declaration

The output declaration is simple. It directs the compiler to where the output code should end up. In Java in basically reflects the package where the code will be generated. More than one output declaration can occur in one file. An output declaration will affect all the blocks defined after it, until another output declaration is found. The format is "output package ;"

The block declaration (class)

Block declarations (classes) are the guts of a renoir file. They are written in a "c-like" language style, with curly brackets delimiting everything. A block takes a number of primitive arguments, as well as having Block based children. They also include at least one "output" block (very different to the root level output declaration), which describes how the attributes and children of a Block node is outputed. In general there is just the default output block, but there may also be named output blocks, which will be discussed later.

Block attributes

A block is defined by a name, followed by a list of attributes, which are of "primitive" types (currently, "int", "double", "boolean", and "String/string"). They are provided just as in Java/C, as a comma seperated list with type then name, in between two round brackets. Eg, in the above example, block "SubBlock" has two attributes: the first is "y" of type int, and the second is "z" of type string.

Block children

Blocks have children, which are blocks. The block declaration lists the possible children, and their cardinality. In the above example "Block" has one type of child, which is called "subs", and is of the block type "SubBlock". The "*" means zero or more, so "Block" may have zero or more children, lumped together as "subs", and of type "SubBlock".

Cardinality

There are three types of cardinality. The first is zero or more, marked by an "*". The second is zero or one (maybe), marked by "?". The last is singleton, which is unmarked, and means exactly one. For example:

Example() {
	zeroOrMore* = Foo();
	zeroOrOne? = Goo();
	one = Zoo();
	output { }
}

Child attributes

In the above declaration of child types you may notice that the block type is followed by two round brackets, looking suspiciously like a function call. You can in fact pass attribute values, which are used when the actual block instances are added at run time (as will be obvious in the section on generated code). To do this, follow the following example:

Example() {
	one = Child(x = 4);
	output { }
}
Child(int x) {
	output {}
}

More explanation will be given later, when generated code is discussed, but for now two more points. The first is that for the singleton/fixed cardinality, all attributes for the child must be specified, unless that child is a group (this is explained later). For other cardinalities (where the children are created by the user code), zero or more of the attributes can be given values, being all or just some of the total attributes of the child (*actually, of the current version, you must also supply all values for zero or one children as well*). Note also that supplied values can be attributes of the parent block, so the following is valid:

Example(int y) {
	one = Child(x = $y);
	output { }
}
Child(int x) {
	output {}
}

Output Blocks

The grunt work of Renoir is done in the output blocks. There are two types of output block. The first is the default output, which is the unnamed output block. The second type is the named one, and is used for greater flexibility. For now we will just discuss the default output block.

General form

The general form of an output block is the output keyword followed by an opening and closing curly bracket. In between the brackets are a series of output elements. Elements in general are seperated by white space.

An output block is just a collection of output elements. Some elements are very basic, such as direct text, while others are a little more involved (such as conditional output).

Basic Output elements

The most basic output elements are the primitive ones. This includes text strings, numbers, and booleans. Text must be enclosed within double brackets. So for example, the following is valid:

	output {
		"One plus one is " 2 
		"This statement is " true
	}

In the above example, when this is used to output text later, the result would actually be: "One plus one is 2This statement is true". That is, all text just runs on. The line break in the Renoir source file has no meaning. To insert a line break into the output, one needs to use the nl, or new line element. So, to rewrite the above example: output { "One plus one is " 2 nl "This statement is " true }

In addition to nl, there are also a few other basic elements that relate to specific text output. These include: sq or single, which outputs a single quote, and dq or double, which outputs a double quote.

Output indentation

As Renoir is meant for producing pretty printed output, support for indentation is provided. This occurs through the use of the ++, and -- elements. The first, ++, increased the indentation by one level, and the second, -- decreases it by. The effect is only apparent when a line break is encountered (so "++ --" does nothing effective). For example:

	output {
		"function moo() { " ++ nl
			"print(" dq "statement" dq ");" nl
		-- "}" nl	
	}

In the above example the final output would be:

function moo() { 
	print("statement");
}

Variables

With the basic elements there is nothing dynamic. The end result of Renoir is to create code that can be used by user code to generate output in certain format. That output can be dynamically altered by the user code (if it couldn't Renoir would be useless, and you could just output a static text file). The interaction is provided by the attributes of an output block, and the blocks children, all of which, as will be seen later, are dictated by the user code.

Attributes and children are treated in a similar way, semantically, referenced as variables. To signify a variable use the attribute or child name is prefixed with "$". So to refer to the attribute "x", the reference is stated by "$x". For example, consider the following block with one attribute:

WithAttribute(int x) {
	output {
		"The value of x is:" $x nl
	}
}

This works with children too. So for example:

Child() {
	output { "MOO " }
}
Parent() {
	myKids* = Child(); 
	output {
		"The children say:" $myKids
	}
}

The user code will add zero or more "Child" blocks to a Parent block (under the name "myKids" — more on this later). Assuming three "myKids" Child nodes have been added, the output from the above would be "The children say:MOO MOO MOO".

Outputing Zero-or-one Children

If a variable is a zero-or-one (optional) child (eg "child?=Child()"), then nothing is outputed if that child has not been set. (It does not cause an error, in other words).

Divider Elements

When outputing a set of elements (specified by a variable) it may be useful to be able to output divider elements between each element of the main set. This can be done in Renoir by using the "/" (divider) operator. For example, reconsider the previous example as follows:

Child() {
	output { "MOO " }
}
Parent() {
	myKids* = Child(); 
	output {
		"The children say:" $myKids / ", "
	}
}

Again, if the user code addes three children then the output would be "The children say:MOO, MOO, MOO".

General division

Alternatively, one can use the divider option over a set of given output elements. The set of elements must be surrounded by "{}". For example:

Simple() {
	output {
		{ "A" "B" "C" } / ", "
	}
}

The above will always output "A, B, C".

The If Statement

Sometimes output will be conditional on the user provided values. In such cases, the "if" statement can be used, which follows a similar form to c-like languages. So for example:

CatOrDog(boolean isCat) {
	output {
		if($isCat) {
			"A cat!"
		} else "A Dog!" 
	}
}

Note the use of "{}" to create a blank, or the single statement form (which I personally dislike but it's there if you need it).

Valid conditionals

As of the current version the conditionals must be very simple expressions, with only variable references being allowed. In general you should only reference two types of variables. The first is boolean attributes, which act as expected. The second is optional or zero-or-more children variables. In such cases, zero children (or no child), is like false, and one or more children is like true.

IfThereThenBoom() {
	child = Bomb();
	output {
		if($child) {
			"BOOOOM! " $child
		} else {
			"No child :("
		}
	}
}

Named Output Blocks

As mentioned earlier, there are two types of output block. The default block previously described, and the named output block. They work essentially the same, except for the addition of a name in the later case (later versions of Renoir will also support variable passing). For example, to declare a named output block, use the following example as a guide:

WithNamed() {
	output {
		"This is the default output"
	}
	output special {
		"This is the special named output"
	}
}

To use a named output, one has to specify this when refering to a variable. For example, consider the following:

WithNamed() {
	output {
		"This is the default output"
	}
	output special {
		"This is the special named output"
	}
}
UsingNamed() {
	$named* = UsingNamed();
	output {
		"The default is " $named nl
		"The special is " $named.special nl
	}
}

Grouping

A useful feature of Renoir is the grouping of types. This allows you to define a set of objects that can be used for a pariticular variable. For example, you may have a "statements" variable to represent the statements of a language that is being outputed, and then use the grouping feature to group blocks together as "statements".

Groups are in ways comparable to classes or interfaces in other languages.

Defining a Group

The first step is to define a group. This is done as follows:

group Statement {
}

That is the basics of it. The "{}" are required, and as will be seen later, things can go between them.

Putting a Block in a Group

There are two ways to put a block in a group. The first is use the "group" keyword again, but this time in a similar way to which "extends" or "implements" might be used in Java. So for example:

PrintStatement(String text) group Statement {
	output { "print(" dq $text dq ");" nl
}
IntDeclStatement(String variableName, int value) group Statement {
	output { "int " $variableName " = " $value ";" nl
}

A block can belong to more than one grouping. This is done by listing them together separated by commas, as multiple interface implementation might be done with "implements" in Java (eg, "group A, B, C").

The second way is just to include a block definition within the group definition. So, for example:

group Statement {
	PrintStatement(String text)  {
		output { "print(" dq $text dq ");" nl
	}
	IntDeclStatement(String variableName, int value) {
		output { "int " $variableName " = " $value ";" nl
	}
}

(Blocks within a group statement can, probably, also be part of other groups with the "group" declaration).

Using a Group

A group is used just like a block, with regards to variables and output. So for example, where as you might refer to the "PrintStatement" block type, you could refer instead to the "Statement" type. So for example, the following is valid:

group Statement {
	PrintStatement(String text)  {
		output { "print(" dq $text dq ");" nl
	}
	IntDeclStatement(String variableName, int value) {
		output { "int " $variableName " = " $value ";" nl
	}
}
Function(String name) {
	statements* = Statement();
	output {
		"function " $name "() {" ++ nl
			$statements
		-- "}" nl
	}
}

Note the use of "()" after the "Statement" reference (just like with block variables). Do not quote me on this, but if you do supply argument values, it will be passed on to the related block children. To make that make sense, consider the following:

group Thing {
	A(int x) {
		output "A:" $x
	}
	B(int y) {
		output "B:" $y
	}
}
ThingUser {
	things* = Thing(x=5, y=3);
	output { "Things:" $things }
}

This should work to supply "x=5" to "A" children, and "y=3" to "B" children (for more explanation, see the code generated section).

Code Generation

Now we come to the important part of Renoir — what code is produced for a given input file. At the moment only Java code is produced.

PrettyPrinter

The first thing to understand is that in general Renoir takes advantage of the "PrettyPrinter" framework, which exists within the Solar library. It is a simple framework for pretty printing text output.

At the heart of the PrettyPrinter framework is the org.ffd2.solar.pretty.PrettyPrinter interface. It looks something like the following:

package org.ffd2.solar.pretty;

public interface PrettyPrinter  {

	public PrettyPrinter breakPoint();
	public PrettyPrinter increaseIndent();

	public PrettyPrinter decreaseIndent();

	public PrettyPrinter print(String  text);
	public PrettyPrinter println(String text);

	public PrettyPrinter pjava(String label, boolean capitalise);

	public PrettyPrinter pdouble(double v);
	public PrettyPrinter pdoubleln(double v);

	public PrettyPrinter pint(int v);
	public PrettyPrinter pintln(int v);

	public PrettyPrinter pchar(char v);
	public PrettyPrinter pcharln(char v);

	public PrettyPrinter pboolean(boolean v);
	public PrettyPrinter pbooleanln(boolean v);

	public PrettyPrinter jbug(String text);

	public PrettyPrinter space();
	public PrettyPrinter tab();
	public PrettyPrinter newLine();
}

The exact details are not important as I'm sure it is self explanatory. All PrettyPrinter implementations should return themselves from function calls (to chain calls). In general, one should not have to implement a PrettyPrinter object, as some implementations are supplied with Solar.

Built-in implementations

The supplied implementations are as follows:

Conclusion

Renoir is an ongoing project guided by personal needs. Undoubtedly with greater use in other projects its abilities will greatly expand in future versions. Again, if there is a direction you would like it to take, please feel free to contact me. If it is of no use to anyone else, that's all good with me.