Introduction
The figures that follow on this page were generated using the descriptions in the online book The Algorithmic Beauty of Plants by
Przemyslaw Pruskinkiewicz & Aristid Lindenmayer. This book explains how to generate the figures
but does not give program listings that do. This web page reproduces some of the figures from the book and
includes the source code, written in C++, that were used to create them. Translating from the notation used
in the book to a working program was not trivial, therefore providing this code can save others from
reinventing the wheel or make a concept in the book clearer when put in the context of computer code as
opposed to descriptions in English.
The book describes a number of different types of figures. The algorithms to create these figures
start out simple and become more complicated. I have written separate programs for some of the
algorithms presented in the book. These programs start simple and then I add extra code to handle the
more complicated algorithms.
Three programs are used to create the figures. The first performs the expansion of the initiator
using the rules provided. The second converts the instructions in the expanded string to a file
containing a number of line segments and the third takes this as input to create output in the
SVG format. These programs can
be connected using the Unix pipe mechanism to create a single program.
The generated figures are labelled with the same designations as those in the books. A large
version of the figures appear on the right and a smaller version along with the input file
that was used to generate it appear on the left inline with the text description.
Simple DOL Systems
An Input File Format
In order to generate a figure such as 1.6 (a) the L system must be specified as well as other
details like the angle and the number of iterations to use. Here is the format that I came up
with.
fig-1.6.a
Initiator -> F-F-F-F
Iterations -> 0
Angle -> 90.0
%%
F -> F-F+F+FF-F-F+F
In order to process this file it must be broken into tokens. The program
flex can be used
to build a tokenizer. The following is the file used by
flex to create a tokenizer.
lex.l
%{
#include <cstring>
#include <cstdlib>
#include "parse.h"
unsigned int line=1;
%}
%option noyywrap
%option prefix="lx"
%option outfile="lex.c"
%x gen
%x rule
lhs [-+F]+
int 0|[1-9]|[1-9][0-9]+
float [0-9]+"."[0-9]*
%%
"Initiator" {
return INITIATOR;
}
"Angle" {
return ANGLE;
}
"Iterations" {
return ITERATIONS;
}
"->" {
return ARROW;
}
{int} {
lxlval.n=atoi(yytext);
return INT;
}
{float} {
lxlval.d=atof(yytext);
return FLOAT;
}
[ ]+ {}
{lhs} {
lxlval.s=new char[strlen(yytext)+1];
strcpy(lxlval.s,yytext);
return STR;
}
^"%%"[ ]*\n {
line++;
BEGIN(gen);
}
\n {
line++;
return EOL;
}
<gen>[F] {
lxlval.c=yytext[0];
return CHR;
}
<gen>[ ]+ {}
<gen>"->" {
BEGIN(rule);
return ARROW;
}
<gen>\n {
line++;
return EOL;
}
<rule>[ ]+ {}
<rule>{lhs} {
lxlval.s=new char[strlen(yytext)+1];
strcpy(lxlval.s,yytext);
return STR;
}
<rule>\n {
line++;
BEGIN(gen);
return EOL;
}
<rule>. {
return UNK;
}
<gen>. {
return UNK;
}
. {
return UNK;
}
Each token is consumed by a parser which is implemented using the program bison. The parser
dictates the order that the tokens read from the lexer can appear.
parse.y
%{
#include <iostream>
#include "system.h"
int lxlex(void);
void lxerror(char*);
extern unsigned int line;
extern System* sys;
%}
%union {
char* s;
char c;
double d;
unsigned int n;
}
%token EOL
%token ARROW
%token INITIATOR
%token UNK
%token ANGLE
%token ITERATIONS
%token <s> STR
%token <c> CHR
%token <d> FLOAT
%token <n> INT
%%
file: header generators
header: initiator angle iterations
| initiator iterations angle
| iterations angle initiator
| iterations initiator angle
| angle iterations initiator
| angle initiator iterations
initiator: INITIATOR ARROW STR eols
{
sys->setInitiator($3);
}
iterations: ITERATIONS ARROW INT eols
{
sys->setIterations($3);
}
angle: ANGLE ARROW INT eols
{
sys->setAngle($3);
}
| ANGLE ARROW FLOAT eols
{
sys->setAngle($3);
}
generators: generator
| generators generator
generator: CHR ARROW STR eols
{
sys->setGenerator($1,$3);
}
eols: EOL
| eols EOL
%%
void lxerror(char* msg)
{
std::cerr << "ERROR: " << line << " " << msg << std::endl;
}
As parts of the input file are recognized they are stored in a class called System through
the instance variable sys.
system.h
#ifndef __SYSTEM_H__
#define __SYSTEM_H__
#include <map>
class System
{
public:
System(void);
void setInitiator(const char*);
void setAngle(double);
void setIterations(unsigned int);
void setGenerator(char,const char*);
unsigned int getIterations(void);
const char* getInitiator(void);
const char* getGenerator(char);
double getAngle(void);
void write(const char*);
private:
bool in(char);
const char* initiator;
unsigned int n;
double angle;
std::map<char,const char*> m;
};
#endif
Expanding the Initiator Using Rules
Even though there is only one letter that gets replaced by a rule, the rules and the key letter are stored
in a map. When the expansion is done it looks in the map to see if it is a key letter that should be
expanded and if so it replaces the letter with the rule. If the letter is not in the map then no replacement
is done. This allows the code that expands the string to be coded without using a switch statement and
implicit key letters.
system.c
#include <iostream>
#include "system.h"
System::System(void)
{
}
void System::setInitiator(const char* i)
{
initiator=i;
}
const char* System::getInitiator(void)
{
return initiator;
}
void System::setGenerator(char c,const char* s)
{
m[c]=s;
}
const char* System::getGenerator(char c)
{
if (!in(c)) {
return 0;
}
return m[c];
}
void System::setAngle(double a)
{
angle=a;
}
double System::getAngle(void)
{
return angle;
}
void System::setIterations(unsigned int n)
{
this->n=n;
}
unsigned int System::getIterations(void)
{
return n;
}
bool System::in(char c)
{
std::map<char,const char*>::iterator pos=m.find(c);
return (pos!=m.end());
}
void System::write(const char* s)
{
std::cout << "Angle " << angle << std::endl;
std::cout << s << std::endl;
}
The Grow class uses the map in the System class
to replace each character in a string with the rules for expansion. In the main program this class
will be called once for each iteration.
grow.h
#ifndef __GROW_H__
#define __GROW_H__
#include <string>
#include "system.h"
class Grow
{
public:
Grow(System*);
std::string get(const std::string&);
private:
std::string generator[2];
System* sys;
};
#endif
grow.c
#include <iostream>
#include <cstring>
#include "grow.h"
Grow::Grow(System* s) : sys(s)
{
}
std::string Grow::get(const std::string& a)
{
std::string o="";
const char* c=a.c_str();
for (unsigned int i=0;i<strlen(c);i++) {
if (sys->getGenerator(c[i])) {
o+=sys->getGenerator(c[i]);
} else {
o+=c[i];
}
}
return o;
}
The main program reads in the input file from standard input and stores
it is an instance of the System class. Then the Grow
class is used to build up the output in a standard string class. Finally
the information needed by the generate program, just the angle and operations
string, are written out to the standard output.
expand.c
#include <iostream>
#include <FlexLexer.h>
#include "system.h"
#include "grow.h"
extern int lxdebug;
extern int lx_flex_debug;
int lxparse(void);
System* sys;
int main(int argc,char* argv[])
{
lxdebug=0;
lx_flex_debug=0;
sys=new System;
int ret=lxparse();
if (ret!=0) {
std::cerr << "Parser failed!" << std::endl;
return 1;
}
Grow* g=new Grow(sys);
std::string s=sys->getInitiator();
for (unsigned int i=0;i<sys->getIterations();i++) {
s=g->get(s);
}
delete g;
sys->write(s.c_str());
delete sys;
return ret;
}
The following Makefile builds the expand
executable when the user types make.
Makefile
OBJS = lex.o\
parse.o\
system.o\
grow.o
all: expand
expand: expand.o $(OBJS)
g++ -o $@ expand.o $(OBJS) -lfl -lm
expand.o: expand.c parse.h system.h grow.h
g++ -c expand.c
parse.o: parse.c parse.h
g++ -c parse.c
lex.o: lex.c parse.h
g++ -c lex.c
parse.c parse.h: parse.y
bison -v --debug -p lx -d -o parse.c parse.y
lex.c: lex.l
flex -d lex.l
system.o: system.c system.h
g++ -c system.c
grow.o: grow.c grow.h
g++ -c grow.c
clean:
rm -f $(OBJS) parse.c parse.h lex.c
rm -f parse.output
rm -f expand expand.o
The command cat fig-1.6.a | expand now produces the following
output.
Angle 90
F-F-F-F
Interpreting the Operation String
Once the initiator has been expanded for the number of iterations specified the operations need
to be interpreted to get a drawing. This is done by another program called generate.
The first step is to process the standard input stream using a lexer.
lex.l
%{
#include <cstring>
#include <cstdlib>
#include "parse.h"
unsigned int line=1;
%}
%option noyywrap
%option prefix="lx"
%option outfile="lex.c"
code [-+F]+
int [0-9]+
float [0-9]+"."[0-9]*
%%
"Angle" {
return ANGLE;
}
{int} {
lxlval.n=atoi(yytext);
return INT;
}
{float} {
lxlval.d=atof(yytext);
return FLOAT;
}
[ ]+ {}
\n {
line++;
return EOL;
}
{code} {
lxlval.s=new char[strlen(yytext)+1];
strcpy(lxlval.s,yytext);
return STR;
}
. {
return UNK;
}
The tokens produced by the lexer are consumed by the parser and stored in a simpler
System class than was used in the expand
program.
parse.y
%{
#include <iostream>
#include "system.h"
int lxlex(void);
void lxerror(char*);
extern unsigned int line;
extern System* sys;
%}
%union {
char* s;
double d;
unsigned int n;
}
%token EOL
%token UNK
%token ANGLE
%token <s> STR
%token <d> FLOAT
%token <n> INT
%%
file: angle generator
angle: ANGLE INT eols
{
sys->setAngle($2);
}
| ANGLE FLOAT eols
{
sys->setAngle($2);
}
generator: STR eols
{
sys->setGenerator($1);
}
eols: EOL
| eols EOL
%%
void lxerror(char* msg)
{
std::cerr << "ERROR: " << line << " " << msg << std::endl;
}
system.h
#ifndef __SYSTEM_H__
#define __SYSTEM_H__
#include <string>
class System
{
public:
System(void);
void setAngle(double);
void setGenerator(const char*);
const char* getGenerator(void);
double getAngle(void);
private:
double angle;
std::string g;
};
#endif
system.c
#include <iostream>
#include "system.h"
System::System(void)
{
}
void System::setGenerator(const char* s)
{
g=s;
}
const char* System::getGenerator(void)
{
return g.c_str();
}
void System::setAngle(double a)
{
angle=a;
}
double System::getAngle(void)
{
return angle;
}
The Turtle class is used to interpret the operations specified by the
input string. The Turtle class uses the State
class to keep track of the turtle's state. The turtle has an angle that tells what direction it is
heading and a two-dimensional point that specifies it's location.
state.h
#ifndef __STATE_H__
#define __STATE_H__
#include "point.h"
class State
{
public:
State(const Point&,double);
State& operator=(const State&);
const Point& getPoint(void) const;
double getAngle(void) const;
void setPoint(const Point&);
void setAngle(double);
void print(void);
private:
Point point;
double angle;
};
#endif
state.c
#include <iostream>
#include "state.h"
State::State(const Point& p,double a)
{
point=p;
angle=a;
}
const Point& State::getPoint(void) const
{
return point;
}
double State::getAngle(void) const
{
return angle;
}
void State::setPoint(const Point& p)
{
point=p;
}
void State::setAngle(double a)
{
angle=a;
}
State& State::operator=(const State& s)
{
this->point=s.getPoint();
this->angle=s.getAngle();
}
The Point class used
by the State class just implements a two-dimensional point.
point.h
#ifndef __POINT_H__
#define __POINT_H__
class Point
{
public:
Point(void);
Point(double,double);
double x(void) const;
double y(void) const;
Point& operator=(const Point&);
private:
double data[2];
};
#endif
point.c
#include <cmath>
#include "point.h"
Point::Point(void)
{
data[0]=0.0;
data[1]=0.0;
}
Point::Point(double a,double b)
{
data[0]=a;
data[1]=b;
}
Point& Point::operator=(const Point& p)
{
data[0]=p.data[0];
data[1]=p.data[1];
}
double Point::x(void) const
{
return data[0];
}
double Point::y(void) const
{
return data[1];
}
The Point class allows angles to be specified in degrees but then
converted to radians so the standard math library calls to cosine and sin can be used.
angle.h
#ifndef __ANGLE_H__
#define __ANGLE_H__
double degrees2radians(double);
double radians2degrees(double);
#endif
angle.c
#include <cmath>
#include "angle.h"
// Convert an angle in degrees to radians
// so it can be passed into C math trig functions
double degrees2radians(double d)
{
return d*M_PI/180.0;
}
double radians2degrees(double r)
{
return r*180.0/M_PI;
}
The last class used by the Turtle class writes the lines generated
by the turtle to the standard output.
write.h
#ifndef __WRITE_H__
#define __WRITE_H__
#include "point.h"
class Write
{
public:
static void line(const Point&,const Point&);
};
#endif
write.c
#include <iostream>
#include "write.h"
void Write::line(const Point& from,const Point& to)
{
std::cout << "line ";
std::cout << from.x() << " " << from.y();
std::cout << " ";
std::cout << to.x() << " " << to.y();
std::cout << std::endl;
}
The Turtle class only has three public functions. The only input
needed by the constructor is the angle that the turtle should turn. The constructor initializes
the state of the turtle which is located at 0,0 and facing up 90 degrees.
The process function takes in the operation string and processes it.
For each character in the string the private function process is called. For the simplest diagrams
there are only three operations F,+ and
-.
The + and - operations are pretty simple,
they just change the angle stored in the turtle's state. The changeAngle
function changes the angle and keeps it between 0 and 360 degrees.
The F operation draws a line using the draw
function. This function uses getNewPosition to compute a new
position for the turtle using simple trigonometry. The draw function
needs to save the current position before a new position is computed so that both endpoints of
the line can be written out.
turtle.h
#ifndef __TURTLE_H__
#define __TURTLE_H__
#include "state.h"
#include "point.h"
class Turtle
{
public:
Turtle(double);
~Turtle(void);
void process(const char*);
private:
void process(char);
double changeAngle(double,double);
const Point getNewPosition(const Point&,double);
void draw(void);
Point pos;
const double reference;
State* state;
};
#endif
turtle.c
#include <iostream>
#include <cstring>
#include <cmath>
#include "angle.h"
#include "turtle.h"
#include "write.h"
Turtle::Turtle(double t) : reference(t)
{
state=new State(pos,90.0);
}
Turtle::~Turtle(void)
{
delete state;
}
void Turtle::process(const char* s)
{
for (unsigned int i=0;i<strlen(s);i++) {
process(s[i]);
}
}
void Turtle::process(char c)
{
switch (c) {
case 'F':
draw();
break;
case '+':
state->setAngle(changeAngle(state->getAngle(),reference));
break;
case '-':
state->setAngle(changeAngle(state->getAngle(),-reference));
break;
}
}
void Turtle::draw(void)
{
Point save=state->getPoint();
state->setPoint(getNewPosition(state->getPoint(),state->getAngle()));
Write::line(save,state->getPoint());
}
double Turtle::changeAngle(double a,double delta)
{
double n=a+delta;
if (n<0.0) {
return 360.0+n;
}
if (n>=360.0) {
return n-360.0;
}
return n;
}
const Point Turtle::getNewPosition(const Point& p,double angle)
{
double x=p.x()+std::cos(degrees2radians(angle));
double y=p.y()+std::sin(degrees2radians(angle));
Point r(x,y);
return r;
}
The main function is written in generate.c. It
processes the input and then calls the process function on the
Turtle class.
generate.c
#include <iostream>
#include <FlexLexer.h>
#include "system.h"
#include "turtle.h"
extern int lxdebug;
extern int lx_flex_debug;
int lxparse(void);
System* sys;
int main(int argc,char* argv[])
{
lxdebug=0;
lx_flex_debug=0;
sys=new System;
int ret=lxparse();
if (ret!=0) {
std::cerr << "Parser failed!" << std::endl;
return 1;
}
Turtle* t=new Turtle(sys->getAngle());
t->process(sys->getGenerator());
delete t;
delete sys;
return ret;
}
Here is the Makefile for the generate program.
Makefile
OBJS = lex.o\
parse.o\
system.o\
turtle.o\
point.o\
write.o\
angle.o\
state.o
all: generate
generate: generate.o $(OBJS)
g++ -o $@ generate.o $(OBJS) -lfl -lm
generate.o: generate.c parse.h system.h turtle.h point.h
g++ -c generate.c
parse.o: parse.c parse.h
g++ -c parse.c
lex.o: lex.c parse.h
g++ -c lex.c
parse.c parse.h: parse.y
bison -v --debug -p lx -d -o parse.c parse.y
lex.c: lex.l
flex -d lex.l
system.o: system.c system.h
g++ -c system.c
turtle.o: turtle.c turtle.h write.h
g++ -c turtle.c
point.o: point.c point.h
g++ -c point.c
write.o: write.c write.h point.h
g++ -c write.c
angle.o: angle.c angle.h
g++ -c angle.c
state.o: state.c state.h point.h
g++ -c state.c
clean:
rm -f $(OBJS) parse.c parse.h lex.c
rm -f parse.output
rm -f generate generate.o
Converting to an SVG image
When creating an SVG (Scaled Vector Graphics) from the line segments output by the
generate program, the main difficulty is that the final size
of the image is unknown. The size will depend on the number of iterations used to
create an image. To handle this all the points in the lines are collected to find
the bounds of the image. These bounds are used to scale the image so that it fits
in a unit square and then it is again scaled to fill the size that is used to
construct the SVG class.
The lexer returns tokens to the parser.
lex.l
%{
#include <cstring>
#include <cstdlib>
#include "parse.h"
unsigned int line=1;
%}
%option noyywrap
%option prefix="lx"
%option outfile="lex.c"
int [-]?0|[-]?[1-9]|[-]?[1-9][0-9]+
float [-]?[0-9]+"."[0-9]*(e{int})?
%%
"line" {
return LINETO;
}
{int} {
lxlval.d=atof(yytext);
return NUM;
}
{float} {
lxlval.d=atof(yytext);
return NUM;
}
[ ]+ {}
\n {
line++;
return EOL;
}
. {
return UNK;
}
The parser is set up to recognize a set of line segments. For each line, two points
are instantiated from the Point class. These two variables
are used to instantiate a variable of the Line class.
The class Ln is used to collect bounding information
from all the lines to be plotted. All of the instances of Ln
are collected in a variable of class Lns.
parse.y
%{
#include <iostream>
#include "bounds.h"
#include "point.h"
#include "lns.h"
#include "ln.h"
int lxlex(void);
void lxerror(char*);
extern unsigned int line;
extern Lns* lns;
%}
%union {
double d;
}
%token LINETO
%token EOL
%token UNK
%token <d> NUM
%%
file: lines
lines: line
| lines line
line: LINETO NUM NUM NUM NUM eols
{
Point from($2,$3);
Point to($4,$5);
Line line(from,to);
Ln* ln=new Ln(line);
lns->add(ln);
}
eols: EOL
| eols EOL
%%
void lxerror(char* msg)
{
std::cerr << "ERROR: " << line << " " << msg << std::endl;
}
The Bounds class processes all the line segments
and gathers the minimum and maximum values for the x and y coordinates.
bounds.h
#ifndef __BOUNDS_H__
#define __BOUNDS_H__
#include "point.h"
class Bounds
{
public:
static void init(void);
static void set(const Point&);
static const Point& min(void);
static const Point& max(void);
private:
static bool first;
static Point mn;
static Point mx;
};
#endif
bounds.c
#include <iostream>
#include <algorithm>
#include "bounds.h"
Point Bounds::mn;
Point Bounds::mx;
bool Bounds::first=true;
void Bounds::init(void)
{
first=true;
}
void Bounds::set(const Point& p)
{
if (first) {
mn.set(p.x(),p.y());
mx.set(p.x(),p.y());
first=false;
} else {
double xmin=std::min(mn.x(),p.x());
double xmax=std::max(mx.x(),p.x());
double ymin=std::min(mn.y(),p.y());
double ymax=std::max(mx.y(),p.y());
mn.set(xmin,ymin);
mx.set(xmax,ymax);
}
}
const Point& Bounds::min(void)
{
return mn;
}
const Point& Bounds::max(void)
{
return mx;
}
The Point class basically just stores a two
dimensional point but it also includes a number of operations that will
allow scaling and translation.
point.h
#ifndef __POINT_H__
#define __POINT_H__
class Point
{
public:
Point(void);
Point(double,double);
void set(double,double);
double x(void) const;
double y(void) const;
void operator+=(const Point&);
void operator/=(double);
private:
double data[2];
};
#endif
point.c
#include <iostream>
#include <cmath>
#include "point.h"
Point::Point(void)
{
data[0]=0.0;
data[1]=0.0;
}
Point::Point(double a,double b)
{
data[0]=a;
data[1]=b;
}
void Point::operator+=(const Point& d)
{
data[0]+=d.data[0];
data[1]+=d.data[1];
}
void Point::operator/=(double s)
{
data[0]/=s;
data[1]/=s;
}
void Point::set(double x,double y)
{
data[0]=x;
data[1]=y;
}
double Point::x(void) const
{
return data[0];
}
double Point::y(void) const
{
return data[1];
}
The Ln class just holds the instance of a
Line but the constructor is used to get
bounds information.
ln.h
#ifndef __LN_H__
#define __LN_H__
#include "line.h"
#include "svg.h"
class Ln
{
public:
Ln(const Line&);
void process(SVG*);
private:
Line ln;
};
#endif
ln.c
#include <iostream>
#include "ln.h"
#include "bounds.h"
Ln::Ln(const Line& l) : ln(l)
{
Bounds::set(ln.p1());
Bounds::set(ln.p2());
}
void Ln::process(SVG* svg)
{
svg->line(ln);
}
The Lns class just holds all the
Lns. I also has the process
method that will copy the Lines to the
SVG class.
lns.h
#ifndef __LNS_H__
#define __LNS_H__
#include <vector>
#include "ln.h"
#include "svg.h"
class Lns
{
public:
Lns(void);
void add(Ln*);
void process(SVG*);
private:
std::vector<Ln*> ops;
};
#endif
lns.c
#include "lns.h"
Lns::Lns(void)
{
}
void Lns::add(Ln* o)
{
ops.push_back(o);
}
void Lns::process(SVG* svg)
{
for (unsigned int i=0;i<ops.size();i++)
ops[i]->process(svg);
}
The Line class basically just stores the two
endpoints of a line segment but it also includes a number of operations that will
allow scaling and translation.
line.h
#ifndef __LINE_H__
#define __LINE_H__
#include "point.h"
class Line
{
public:
Line(const Point&,const Point&);
void set(const Point&,const Point&);
void translate(double,double);
void scale(double);
void verticalflip(double);
const Point& p1(void) const;
const Point& p2(void) const;
private:
Point p[2];
};
#endif
line.c
#include <iostream>
#include <algorithm>
#include "line.h"
Line::Line(const Point& p1,const Point& p2)
{
p[0]=p1;
p[1]=p2;
}
void Line::translate(double x,double y)
{
Point t(x,y);
p[0]+=t;
p[1]+=t;
}
void Line::scale(double s)
{
p[0]/=s;
p[1]/=s;
}
void Line::verticalflip(double m)
{
p[0].set(p[0].x(),m-p[0].y());
p[1].set(p[1].x(),m-p[1].y());
}
const Point& Line::p1(void) const
{
return p[0];
}
const Point& Line::p2(void) const
{
return p[1];
}
The Lines class holds all the lines that will be in
the figure. Using bounding information from the Bounds
class it can scale and translate all the lines to fit the desired size.
lines.h
#ifndef __LINES_H__
#define __LINES_H__
#include <vector>
#include "line.h"
class Lines
{
public:
void add(const Line&);
void normalize(void);
Line* get(unsigned int);
unsigned int count(void);
private:
double absdiff(double,double);
std::vector<Line*> lines;
};
#endif
lines.c
#include <iostream>
#include <algorithm>
#include <cmath>
#include "lines.h"
#include "bounds.h"
void Lines::add(const Line& l)
{
Line* ln=new Line(l);
lines.push_back(ln);
}
void Lines::normalize(void)
{
double xmax=Bounds::max().x();
double xmin=Bounds::min().x();
double ymax=Bounds::max().y();
double ymin=Bounds::min().y();
double x=absdiff(xmin,xmax);
double y=absdiff(ymin,ymax);
double dim=std::max(x,y);
for (unsigned int i=0;i<lines.size();i++) {
lines[i]->translate(-xmin,-ymin);
lines[i]->verticalflip(ymax-ymin);
lines[i]->scale(dim);
}
}
double Lines::absdiff(double mn,double mx)
{
if (mn>=0.0 && mx>=0.0)
return mx-mn;
if (mn<0.0 && mx<0.0)
return -(mx-mn);
return mx-mn;
}
Line* Lines::get(unsigned int i)
{
return lines[i];
}
unsigned int Lines::count(void)
{
return lines.size();
}
The SVG class holds all the lines that will be in
the figure. It uses the Lines class to normalize
all the line end points. The class can then write out the SVG image with
the correct header and trailer.
svg.h
#ifndef __SVG_H__
#define __SVG_H__
#include "lines.h"
class SVG
{
public:
SVG(double);
~SVG(void);
void header(void);
void trailer(void);
void line(double,double,double,double);
void line(const Line&);
void writelines(void);
void writeline(double,double,double,double);
void writeline(const Line*);
void normalize(void);
private:
double size;
Lines* lines;
};
#endif
svg.c
#include <iostream>
#include "lines.h"
#include "svg.h"
SVG::SVG(double sz) : size(sz)
{
lines=new Lines;
}
SVG::~SVG(void)
{
delete lines;
}
void SVG::header(void)
{
std::cout << "<?xml version=\"1.0\" encoding=\"iso-8859-1\" standalone=\"no\"?>" << std::endl;
std::cout << "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\" \"http://www.w3.org/TR/SVG/DTD/svg10.dtd\">" << std::endl;
std::cout << "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xml:space=\"preserve\">" << std::endl;
std::cout << " <g>" << std::endl;
}
void SVG::trailer(void)
{
std::cout << " </g>" << std::endl;
std::cout << "</svg>" << std::endl;
}
void SVG::line(const Line& ln)
{
lines->add(ln);
}
void SVG::writelines(void)
{
for (unsigned int i=0;i<lines->count();i++) {
Line* ln=lines->get(i);
writeline(ln);
}
}
void SVG::writeline(const Line* ln)
{
Point p1=ln->p1();
Point p2=ln->p2();
std::cout << " <line";
std::cout << " x1=\"";
std::cout << p1.x()*size;
std::cout << "\"";
std::cout << " y1=\"";
std::cout << p1.y()*size;
std::cout << "\"";
std::cout << " x2=\"";
std::cout << p2.x()*size;
std::cout << "\"";
std::cout << " y2=\"";
std::cout << p2.y()*size;
std::cout << "\"";
std::cout << " style=\"stroke:black; stroke-width:1px\"/>" << std::endl;
}
void SVG::normalize(void)
{
lines->normalize();
}
The main function reads in the input file and fills
an instance of the Lns class. This instance is processed
which just fills up an instance of the SVG class which
normalizes the lines and then writes out the SVG drawing.
display.c
#include <iostream>
#include <unistd.h>
#include <FlexLexer.h>
#include "svg.h"
#include "lines.h"
#include "lns.h"
#include "parse.h"
#include "bounds.h"
extern int lxdebug;
extern int lx_flex_debug;
int lxparse(void);
Lns* lns=0;
int main(int argc,char* argv[])
{
lxdebug=0;
lx_flex_debug=0;
unsigned int size=1;
int c;
while ((c=getopt(argc,argv,"s:"))!=-1)
switch (c) {
case 's':
size=atoi(optarg);
break;
}
lns=new Lns;
SVG* svg=new SVG(size);
int ret=lxparse();
if (ret!=0) {
std::cerr << "Parser failed!" << std::endl;
}
lns->process(svg);
svg->header();
svg->normalize();
svg->writelines();
svg->trailer();
delete lns;
return ret;
}
Makefile
OBJS = lex.o\
parse.o\
point.o\
svg.o\
line.o\
lines.o\
lns.o\
ln.o\
bounds.o
all: display
display: display.o $(OBJS)
g++ -o $@ display.o $(OBJS) -L/mcr/rsa/lib -lg -lfl -lm
display.o: display.c parse.h
g++ -c display.c
parse.o: parse.c parse.h
g++ -c parse.c
lex.o: lex.c parse.h
g++ -c lex.c
parse.c parse.h: parse.y
bison -v --debug -p lx -d -o parse.c parse.y
lex.c: lex.l
flex -d lex.l
system.o: system.c system.h
g++ -c system.c
point.o: point.c point.h
g++ -c point.c
line.o: line.c line.h point.h
g++ -c line.c
grow.o: grow.c grow.h
g++ -c grow.c
svg.o: svg.c svg.h
g++ -c svg.c
lines.o: lines.c lines.h
g++ -c lines.c
lns.o: lns.c lns.h
g++ -c lns.c
ln.o: ln.c ln.h line.h point.h
g++ -c ln.c
bounds.o: bounds.c bounds.h point.h
g++ -c bounds.c
clean:
rm -f $(OBJS) parse.c parse.h lex.c
rm -f parse.output
rm -f display display.o
The Whole Process
Now that all three programs have been written they can be combined to take the
simple specification file and create an SVG image.
cat fig-1.6.a | expand | generate | display -s 200 > fig-1-6-a.svg
fig-1.6.a
Initiator -> F-F-F-F
Iterations -> 0
Angle -> 90.0
%%
F -> F-F+F+FF-F-F+F
fig-1-6-a.svg
fig-1.6.b
Initiator -> F-F-F-F
Iterations -> 1
Angle -> 90.0
%%
F -> F-F+F+FF-F-F+F
fig-1-6-b.svg
fig-1.6.c
Initiator -> F-F-F-F
Iterations -> 2
Angle -> 90.0
%%
F -> F-F+F+FF-F-F+F
fig-1-6-c.svg
fig-1.6.d
Initiator -> F-F-F-F
Iterations -> 3
Angle -> 90.0
%%
F -> F-F+F+FF-F-F+F
fig-1-6-d.svg
Multiple Generators
It is possible to have multiple generators such as those in the input file for Figure 1.16 (a).
To allow this type of input only the expand and generate programs need to be modified.
The turtle in the generate program will only recognize the Fs that end
up in the final string to draw lines. The L and R characters are ignored.
fig-1.16.a
Initiator -> -L
Iterations -> 3
Angle -> 90
%%
L -> LF+RFR+FL-F-LFLFL-FRFR+
R -> -LFLF+RFRFR+F+RF-LFL-FR
fig-1-16-a.svg
fig-1.17.a
Initiator -> L
Iterations -> 2
Angle -> 90
%%
L -> LFRFL-F-RFLFR+F+LFRFL
R -> RFLFR+F+LFRFL-F-RFLFR
fig-1-17-a.svg
The lex.l file needs to be changed
so that instead of F the letters
L and R
need to be recognized.
To make the program more flexible, any capital letter was allowed
by specifying a range A-Z.
lex.l
%{
#include <cstring>
#include <cstdlib>
#include "parse.h"
unsigned int line=1;
%}
%option noyywrap
%option prefix="lx"
%option outfile="lex.c"
%x gen
%x rule
lhs [-+A-Z]+
int 0|[1-9]|[1-9][0-9]+
float [0-9]+"."[0-9]*
%%
"Initiator" {
return INITIATOR;
}
"Angle" {
return ANGLE;
}
"Iterations" {
return ITERATIONS;
}
"->" {
return ARROW;
}
{int} {
lxlval.n=atoi(yytext);
return INT;
}
{float} {
lxlval.d=atof(yytext);
return FLOAT;
}
[ ]+ {}
{lhs} {
lxlval.s=new char[strlen(yytext)+1];
strcpy(lxlval.s,yytext);
return STR;
}
^"%%"[ ]*\n {
line++;
BEGIN(gen);
}
\n {
line++;
return EOL;
}
<gen>[A-Z] {
lxlval.c=yytext[0];
return CHR;
}
<gen>[ ]+ {}
<gen>"->" {
BEGIN(rule);
return ARROW;
}
<gen>\n {
line++;
return EOL;
}
<rule>[ ]+ {}
<rule>{lhs} {
lxlval.s=new char[strlen(yytext)+1];
strcpy(lxlval.s,yytext);
return STR;
}
<rule>\n {
line++;
BEGIN(gen);
return EOL;
}
<rule>. {
return UNK;
}
<gen>. {
return UNK;
}
. {
return UNK;
}
The lex.l file in the
generate program also needs to allow more letters than
F.
lex.l
%{
#include <cstring>
#include <cstdlib>
#include "parse.h"
unsigned int line=1;
%}
%option noyywrap
%option prefix="lx"
%option outfile="lex.c"
code [-+A-Z]+
int [0-9]+
float [0-9]+"."[0-9]*
%%
"Angle" {
return ANGLE;
}
{int} {
lxlval.n=atoi(yytext);
return INT;
}
{float} {
lxlval.d=atof(yytext);
return FLOAT;
}
[ ]+ {}
\n {
line++;
return EOL;
}
{code} {
lxlval.s=new char[strlen(yytext)+1];
strcpy(lxlval.s,yytext);
return STR;
}
. {
return UNK;
}
Disconnected Drawings
It is possible to have pick up the pen and create disconnected drawings such as
Figure 1.8.
To allow this type of input only the expand and generate programs need to be modified.
A move operation is added to the turtle in the generate program.
When an f is seen in
the operation string then a move instead of a draw takes place.
fig-1.8
Initiator -> F+F+F+F
Iterations -> 2
Angle -> 90.0
%%
F -> F+f-FF+F+FF+Ff+FF-f+FF-F-FF-Ff-FFF
f -> ffffff
fig-1-8.svg
In the lexers for both the expand and
generate executables the accepted character set has to be expanded to have lower
case characters.
lex.l
%{
#include <cstring>
#include <cstdlib>
#include "parse.h"
unsigned int line=1;
%}
%option noyywrap
%option prefix="lx"
%option outfile="lex.c"
%x gen
%x rule
lhs [-+A-Za-z]+
int 0|[1-9]|[1-9][0-9]+
float [0-9]+"."[0-9]*
%%
"Initiator" {
return INITIATOR;
}
"Angle" {
return ANGLE;
}
"Iterations" {
return ITERATIONS;
}
"->" {
return ARROW;
}
{int} {
lxlval.n=atoi(yytext);
return INT;
}
{float} {
lxlval.d=atof(yytext);
return FLOAT;
}
[ ]+ {}
{lhs} {
lxlval.s=new char[strlen(yytext)+1];
strcpy(lxlval.s,yytext);
return STR;
}
^"%%"[ ]*\n {
line++;
BEGIN(gen);
}
\n {
line++;
return EOL;
}
<gen>[A-Za-z] {
lxlval.c=yytext[0];
return CHR;
}
<gen>[ ]+ {}
<gen>"->" {
BEGIN(rule);
return ARROW;
}
<gen>\n {
line++;
return EOL;
}
<rule>[ ]+ {}
<rule>{lhs} {
lxlval.s=new char[strlen(yytext)+1];
strcpy(lxlval.s,yytext);
return STR;
}
<rule>\n {
line++;
BEGIN(gen);
return EOL;
}
<rule>. {
return UNK;
}
<gen>. {
return UNK;
}
. {
return UNK;
}
lex.l
%{
#include <cstring>
#include <cstdlib>
#include "parse.h"
unsigned int line=1;
%}
%option noyywrap
%option prefix="lx"
%option outfile="lex.c"
code [-+A-Za-z]+
int [0-9]+
float [0-9]+"."[0-9]*
%%
"Angle" {
return ANGLE;
}
{int} {
lxlval.n=atoi(yytext);
return INT;
}
{float} {
lxlval.d=atof(yytext);
return FLOAT;
}
[ ]+ {}
\n {
line++;
return EOL;
}
{code} {
lxlval.s=new char[strlen(yytext)+1];
strcpy(lxlval.s,yytext);
return STR;
}
. {
return UNK;
}
The turtle has to be modified so that it can move as well as
draw.
turtle.h
#ifndef __TURTLE_H__
#define __TURTLE_H__
#include "state.h"
#include "point.h"
class Turtle
{
public:
Turtle(double);
~Turtle(void);
void process(const char*);
private:
void process(char);
double changeAngle(double,double);
const Point getNewPosition(const Point&,double);
void draw(void);
void move(void);
Point pos;
const double reference;
State* state;
};
#endif
turtle.c
#include <iostream>
#include <cstring>
#include <cmath>
#include "angle.h"
#include "turtle.h"
#include "write.h"
Turtle::Turtle(double t) : reference(t)
{
state=new State(pos,90.0);
}
Turtle::~Turtle(void)
{
delete state;
}
void Turtle::process(const char* s)
{
for (unsigned int i=0;i<strlen(s);i++) {
process(s[i]);
}
}
void Turtle::process(char c)
{
switch (c) {
case 'F':
draw();
break;
case 'f':
move();
break;
case '+':
state->setAngle(changeAngle(state->getAngle(),reference));
break;
case '-':
state->setAngle(changeAngle(state->getAngle(),-reference));
break;
}
}
void Turtle::draw(void)
{
Point save=state->getPoint();
state->setPoint(getNewPosition(state->getPoint(),state->getAngle()));
Write::line(save,state->getPoint());
}
void Turtle::move(void)
{
state->setPoint(getNewPosition(state->getPoint(),state->getAngle()));
}
double Turtle::changeAngle(double a,double delta)
{
double n=a+delta;
if (n<0.0) {
return 360.0+n;
}
if (n>=360.0) {
return n-360.0;
}
return n;
}
const Point Turtle::getNewPosition(const Point& p,double angle)
{
double x=p.x()+std::cos(degrees2radians(angle));
double y=p.y()+std::sin(degrees2radians(angle));
Point r(x,y);
return r;
}
Trees
In order to implement trees a stack has to be added to the Turtle. The lexical analyzer
also needs to have the square brackets [] added. These brackets control when the turtle's
state is pushed on and popped off the stack.
fig-1.23
Initiator -> F[+F][-F[-F]F]F[+F][-F]
Iterations -> 0
Angle -> 45.0
fig-1-23.svg
fig-1.24.a
Initiator -> F
Iterations -> 5
Angle -> 25.7
%%
F -> F[+F]F[-F]F
fig-1-24-a.svg
fig-1.24.b
Initiator -> F
Iterations -> 5
Angle -> 20
%%
F -> F[+F]F[-F][F]
fig-1-24-b.svg
fig-1.24.c
Initiator -> F
Iterations -> 4
Angle -> 22.5
%%
F -> FF-[-F+F+F]+[+F-F-F]
fig-1-24-c.svg
fig-1.24.d
Initiator -> X
Iterations -> 7
Angle -> 20
%%
X -> F[+X]F[-X]+X
F -> FF
fig-1-24-d.svg
fig-1.24.e
Initiator -> X
Iterations -> 7
Angle -> 25.7
%%
X -> F[+X][-X]FX
F -> FF
fig-1-24-e.svg
fig-1.24.f
Initiator -> X
Iterations -> 5
Angle -> 22.5
%%
X -> F-[[X]+X]+F[+FX]-X
F -> FF
fig-1-24-f.svg
lex.l
%{
#include <cstring>
#include <cstdlib>
#include "parse.h"
unsigned int line=1;
%}
%option noyywrap
%option prefix="lx"
%option outfile="lex.c"
%x gen
%x rule
lhs [-+A-Za-z\[\]]+
int 0|[1-9]|[1-9][0-9]+
float [0-9]+"."[0-9]*
%%
"Initiator" {
return INITIATOR;
}
"Angle" {
return ANGLE;
}
"Iterations" {
return ITERATIONS;
}
"->" {
return ARROW;
}
{int} {
lxlval.n=atoi(yytext);
return INT;
}
{float} {
lxlval.d=atof(yytext);
return FLOAT;
}
[ ]+ {}
{lhs} {
lxlval.s=new char[strlen(yytext)+1];
strcpy(lxlval.s,yytext);
return STR;
}
^"%%"[ ]*\n {
line++;
BEGIN(gen);
}
\n {
line++;
return EOL;
}
<gen>[A-Za-z] {
lxlval.c=yytext[0];
return CHR;
}
<gen>[ ]+ {}
<gen>"->" {
BEGIN(rule);
return ARROW;
}
<gen>\n {
line++;
return EOL;
}
<rule>[ ]+ {}
<rule>{lhs} {
lxlval.s=new char[strlen(yytext)+1];
strcpy(lxlval.s,yytext);
return STR;
}
<rule>\n {
line++;
BEGIN(gen);
return EOL;
}
<rule>. {
return UNK;
}
<gen>. {
return UNK;
}
. {
return UNK;
}
lex.l
%{
#include <cstring>
#include <cstdlib>
#include "parse.h"
unsigned int line=1;
%}
%option noyywrap
%option prefix="lx"
%option outfile="lex.c"
code [-+A-Za-z\[\]]+
int [0-9]+
float [0-9]+"."[0-9]*
%%
"Angle" {
return ANGLE;
}
{int} {
lxlval.n=atoi(yytext);
return INT;
}
{float} {
lxlval.d=atof(yytext);
return FLOAT;
}
[ ]+ {}
\n {
line++;
return EOL;
}
{code} {
lxlval.s=new char[strlen(yytext)+1];
strcpy(lxlval.s,yytext);
return STR;
}
. {
return UNK;
}
turtle.h
#ifndef __TURTLE_H__
#define __TURTLE_H__
#include <stack>
#include "state.h"
#include "point.h"
class Turtle
{
public:
Turtle(double);
~Turtle(void);
void process(const char*);
private:
void process(char);
double changeAngle(double,double);
const Point getNewPosition(const Point&,double);
void draw(void);
void move(void);
Point pos;
const double reference;
State* state;
std::stack<State> stck;
};
#endif
turtle.c
#include <iostream>
#include <cstring>
#include <cmath>
#include "angle.h"
#include "turtle.h"
#include "write.h"
Turtle::Turtle(double t) : reference(t)
{
state=new State(pos,90.0);
}
Turtle::~Turtle(void)
{
delete state;
}
void Turtle::process(const char* s)
{
for (unsigned int i=0;i<strlen(s);i++) {
process(s[i]);
}
}
void Turtle::process(char c)
{
switch (c) {
case 'F':
draw();
break;
case 'f':
move();
break;
case '+':
state->setAngle(changeAngle(state->getAngle(),reference));
break;
case '-':
state->setAngle(changeAngle(state->getAngle(),-reference));
break;
case '[':
stck.push(*state);
break;
case ']':
*state=stck.top();
stck.pop();
break;
}
}
void Turtle::draw(void)
{
Point save=state->getPoint();
state->setPoint(getNewPosition(state->getPoint(),state->getAngle()));
Write::line(save,state->getPoint());
}
void Turtle::move(void)
{
state->setPoint(getNewPosition(state->getPoint(),state->getAngle()));
}
double Turtle::changeAngle(double a,double delta)
{
double n=a+delta;
if (n<0.0) {
return 360.0+n;
}
if (n>=360.0) {
return n-360.0;
}
return n;
}
const Point Turtle::getNewPosition(const Point& p,double angle)
{
double x=p.x()+std::cos(degrees2radians(angle));
double y=p.y()+std::sin(degrees2radians(angle));
Point r(x,y);
return r;
}
Natural Looking Trees
In order to create a forest of trees they need to look different from each other. To solve this the
expansion is done randomly. To implement this only the expand program needs to be changed.
page-28
Initiator -> F
Iterations -> 5
Angle -> 35
%%
F (0.33) -> F[+F]F[-F]F
F (0.33) -> F[+F]F
F (0.34) -> F[-F]F
page-28.svg
lex.l
%{
#include <cstring>
#include <cstdlib>
#include "parse.h"
unsigned int line=1;
%}
%option noyywrap
%option prefix="lx"
%option outfile="lex.c"
%x gen
%x rule
lhs [-+A-Za-z\[\]]+
int 0|[1-9]|[1-9][0-9]+
float [0-9]+"."[0-9]*
%%
"Initiator" {
return INITIATOR;
}
"Angle" {
return ANGLE;
}
"Iterations" {
return ITERATIONS;
}
"->" {
return ARROW;
}
{int} {
lxlval.n=atoi(yytext);
return INT;
}
{float} {
lxlval.d=atof(yytext);
return FLOAT;
}
[ ]+ {}
{lhs} {
lxlval.s=new char[strlen(yytext)+1];
strcpy(lxlval.s,yytext);
return STR;
}
^"%%"[ ]*\n {
line++;
BEGIN(gen);
}
\n {
line++;
return EOL;
}
<gen>[A-Za-z] {
lxlval.c=yytext[0];
return CHR;
}
<gen>[ ]+ {}
<gen>"->" {
BEGIN(rule);
return ARROW;
}
<gen>\( {
return '(';
}
<gen>\) {
return ')';
}
<gen>{float} {
lxlval.d=atof(yytext);
return FLOAT;
}
<gen>\n {
line++;
return EOL;
}
<rule>[ ]+ {}
<rule>{lhs} {
lxlval.s=new char[strlen(yytext)+1];
strcpy(lxlval.s,yytext);
return STR;
}
<rule>\n {
line++;
BEGIN(gen);
return EOL;
}
<rule>. {
return UNK;
}
<gen>. {
return UNK;
}
. {
return UNK;
}
parse.y
%{
#include <iostream>
#include "system.h"
int lxlex(void);
void lxerror(char*);
extern unsigned int line;
extern System* sys;
%}
%union {
char* s;
char c;
double d;
unsigned int n;
}
%token EOL
%token ARROW
%token INITIATOR
%token UNK
%token ANGLE
%token ITERATIONS
%token <s> STR
%token <c> CHR
%token <d> FLOAT
%token <n> INT
%%
file: header generators
| header
header: initiator angle iterations
| initiator iterations angle
| iterations angle initiator
| iterations initiator angle
| angle iterations initiator
| angle initiator iterations
initiator: INITIATOR ARROW STR eols
{
sys->setInitiator($3);
}
iterations: ITERATIONS ARROW INT eols
{
sys->setIterations($3);
}
angle: ANGLE ARROW INT eols
{
sys->setAngle($3);
}
| ANGLE ARROW FLOAT eols
{
sys->setAngle($3);
}
generators: generator
| generators generator
generator: CHR '(' FLOAT ')' ARROW STR eols
{
sys->setGenerator($1,$3,$6);
}
eols: EOL
| eols EOL
%%
void lxerror(char* msg)
{
std::cerr << "ERROR: " << line << " " << msg << std::endl;
}
choice.h
#ifndef __CHOICE_H__
#define __CHOICE_H__
#include <vector>
#include <string>
class Choice
{
public:
Choice(char);
void add(double,const char*);
const char* get(void);
private:
double getRunningTotal(void);
char c;
std::vector<std::pair<double,const char*> > p;
};
#endif
choice.c
#include <iostream>
#include <cstdlib>
#include <time.h>
#include "choice.h"
Choice::Choice(char)
{
srand(time(NULL));
}
void Choice::add(double d,const char* s)
{
p.push_back(std::make_pair(getRunningTotal()+d,s));
}
const char* Choice::get(void)
{
double random=rand();
double max=RAND_MAX;
double r=random/max;
unsigned int i=0;
for (i=0;i<p.size();i++) {
if (r<p[i].first)
return p[i].second;
}
return p[i].second;
}
double Choice::getRunningTotal(void)
{
return (p.size()==0) ? 0.0:p[p.size()-1].first;
}
system.h
#ifndef __SYSTEM_H__
#define __SYSTEM_H__
#include <map>
#include "choice.h"
class System
{
public:
System(void);
void setInitiator(const char*);
void setAngle(double);
void setIterations(unsigned int);
void setGenerator(char,double,const char*);
unsigned int getIterations(void);
const char* getInitiator(void);
const char* getGenerator(char);
double getAngle(void);
void write(const char*);
private:
bool in(char);
const char* initiator;
unsigned int n;
double angle;
std::map<char,Choice*> m;
};
#endif
system.c
#include <iostream>
#include "system.h"
System::System(void)
{
}
void System::setInitiator(const char* i)
{
initiator=i;
}
const char* System::getInitiator(void)
{
return initiator;
}
void System::setGenerator(char c,double p,const char* s)
{
if (in(c)) {
m[c]->add(p,s);
} else {
m[c]=new Choice(c);
m[c]->add(p,s);
}
}
const char* System::getGenerator(char c)
{
if (!in(c)) {
return 0;
}
return m[c]
->get();
}
void System::setAngle(double a)
{
angle=a;
}
double System::getAngle(void)
{
return angle;
}
void System::setIterations(unsigned int n)
{
this->n=n;
}
unsigned int System::getIterations(void)
{
return n;
}
bool System::in(char c)
{
std::map<char,
Choice*>::iterator pos=m.find(c);
return (pos!=m.end());
}
void System::write(const char* s)
{
std::cout << "Angle " << angle << std::endl;
std::cout << s << std::endl;
}
Makefile
OBJS = lex.o\
parse.o\
system.o\
grow.o\
choice.o
all: expand
expand: expand.o $(OBJS)
g++ -o $@ expand.o $(OBJS) -lfl -lm
expand.o: expand.c parse.h system.h grow.h
g++ -c expand.c
parse.o: parse.c parse.h
g++ -c parse.c
lex.o: lex.c parse.h
g++ -c lex.c
parse.c parse.h: parse.y
bison -v --debug -p lx -d -o parse.c parse.y
lex.c: lex.l
flex -d lex.l
system.o: system.c system.h
g++ -c system.c
grow.o: grow.c grow.h
g++ -c grow.c
choice.o: choice.c choice.h
g++ -c choice.c
clean:
rm -f $(OBJS) parse.c parse.h lex.c
rm -f parse.output
rm -f expand expand.o