{ |one, step, back| }

The Shape Example in sed

Contributed by Scott Anderson

I had to implement my own control language and build a stack-based processor to get this one.

This is my first sed program (!) so go easy on the criticism... ;-)

Put the files in a directory and execute the following command:

  sed -n -f shapes.sed shapes.soo

Code for sed

File: shapes.sed

# November 12, 1999 - Scott Anderson
# OO sed engine with code for a polymorphic shape example.
# I'm going to Hell, I just know it.

# Features:
# Single inheritance, dynamic dispatch, multiple return values on
# functions, possible use of method signatures, superclass constructors
# are automatically called.

# Where to start... the program stores the class and instance
# information in a delimited form. See the shapes.soo file for details.
# Function calls and arguments are stacked on the front of the stream,
# while return values are stacked on the end. The datastream is held in
# the hold area while a new command is read, then carried around the call 
# stack with each function call.



: start

# Ignore comments
/[^#]*#.*/{
	s/\([^#]*\)#.*/\1/
}

# Skip blank lines
/^$/{n;b start}

# prepend the command with a delimiter
s/^/%/

: main

# Read the class definitions and put them in the hold area
/^%def ~.\+~/{
	s/%def //
	H
	x
	s/^\n*//g
	s/~\n~/~~/g
	x
	b skipend
}

# Exit command, eh?
/^%exit.*/{
	q
}

# Append the hold area data to the current command if it isn't already
there
/[\n~]/!{G;h;}

# instantiate a class
/^%obj/{
	tobj1
	:obj1
	# Class with no super class to inherit
	s/%obj \([^ ]\+\) \([^ ]\+\).*\n.*~\2~__\([^~]\+\)~.*/%\2&!\1!\3/
	t endCreate

	# inherit a superclass by copying the superclass data
	s/%obj \([^ ]\+\) \([^ ]\+\).*\n~.*~\2~_\([^_]\+\)_.*/%\3&!\1!/
	s/\(.*\)%obj [^ ]\+ \([^
]\+\).*\n.*~\1~_[^_]*_\([^~]\+\)~.*~\2~_\1_\([^~]\+\)~.*/\2%&\3\4/

	: endCreate

	# have everything in the system inherit a base object if you wish
	s/%obj\( [^ ]\+ \)[^ ]\+ /%baseobj\1/
}

# Set a property on an object
# format: setprop objReference properyName newValue
# Simply performs a substitution on the proper field.
/^%setprop /{
	s/\([^ ]\+\) \([^ ]\+\) \([^ ]\+\) \([^
%\n]\+\)\(.*!\)\2\(![^!]*-\)\3\(,\)[^-]\+\(-.*\)/\1\5\2\6\3\7\4\8/
	b popfunc
}

# Get a property value
# format: getprop objReference properyName
# return: @value on the end of the datastream
/^%getprop /{
	s/\([^ ]\+ \)\([^ ]\+\) \([^
%\n]\+\)\(.*!\)\2\(![^!]*-\)\3\(,\)\([^-]\+\)\(-.*\)/\1\2
\3\4\2\5\3\6\7\8@\7/
	b popfunc
}


# Dynamic dispatch. Look up a method call in the datastream and
substitute the actual method call, then place the
# result back on the call stack.
/^%call /{
	s/%call \([^ ]\+\) \([^
%\n]\+\)\(.*\n\)\(.\+!\)\1\(![^!]\+=\)\2\(,\)\([^=]\+\)\(.\+\)/%\7
\1\3\4\1\5\2\6\7\8/
	b main
}


# constructors

# format: shape x y
/^%shape/{
	tshape1
	:shape1

	# set the origin from the arguments by placing two setprop calls on the
stack
	s/\(^%shape[^ ]* \)\([^ ]\+\) \([0-9]\+\) \([0-9]\+\)\(\n.*\)/%setprop
\2 x \3%setprop \2 y \4\1\5/
	t main

	b popfunc
}

# format: rectangle x y w h
/^%rectangle/{
	trectangle1
	:rectangle1
	
	# set the width and height from the last two args. since the object
construction routine places the superclass
	# constructor on the call stack, the remaining arguments will be passed
on to the shape constructor.
	s/\(^%rectangle[^ ]\+ \)\([^ ]\+\)\( [0-9]\+ [0-9]\+\) \([0-9]\+\)
\([0-9]\+\)\(\n.*\)/%setprop \2 w \4%setprop \2 h \5\1\2\3\6/
	t main

	b popfunc
}

# format: circle x y r
/^%circle/{

	tcircle1
	:circle1

	# set the radius from the last arg. since the object construction
routine places the superclass
	# constructor on the call stack, the remaining arguments will be passed
on to the shape constructor.
	s/\([^ ]\+ \)\([^ ]\+\)\( [0-9]\+ [0-9]\+\)
\([0-9]\+\)\(\n.*\)/%setprop \2 r \4\1\2\3\5/
	t main

	b popfunc
}

# null function for the base object. 
/^%baseobj/{
	b popfunc
}


# class methods

# move a shape relative to its origin
# the first part adds the numbers to the properties
/^%srmove [^%\n ]\+ [^%\n ]\+ [^%\n]\+[%\n]/{
	trmove1
	:rmove1

	# get the origin by pushing two getprop calls on the stack
	# note that this is a variant method of getting two properties and not
as clean
	/@/!s/^\(%srmove \)\([^ ]\+\)\([^\n]*\n.*\)/%getprop \2 x\1\2\3/
	/^[^@]\+@[0-9]\+$/s/^\(%srmove \)\([^ ]\+\)\([^\n]*\n.*\)/%getprop \2
y\1\2\3/
	t main

	# push the second part of the function call on the stack, followed by
two add calls.
	s/%srmove \([^ ]\+\) \([0-9]\+\)
\([0-9]\+\)\([^@]\+\)@\([0-9]\+\)@\([0-9]\+\)$/%add \2 \5%add \3
\6%srmove \1\4/
	t main

	b popfunc
}

# finish function to actually modify the data
/^%srmove [^%\n ]\+[%\n]/{
	trmove2
	:rmove2

	# store the results of the add calls to the origin
	s/[^ ]\+ \([^ ]\+\)\([%\n].*\)@\([0-9]\+\)@\([0-9]\+\)/%setprop \1 x
\3%setprop \1 y \4\2/
	t main

	b popfunc
}

# set the width of a rectangle
/^%rsetw/{
	trsetw1
	:rsetw1

	# push a setprop call on the call stack
	s/\(^%rsetw\) \([^ ]\+\)\ \([0-9]\+\)\(\n.*\)/%setprop \2 w \3\1\4/
	t main

	b popfunc
}

# draw a rectangle
/^%rdraw/{
	trdraw1
	:rdraw1

	# push a call to getprop for x, y, w, and h on the callstack
	# I'll clean this up some time to make it one operation.
	/@/!s/^\(%rdraw \)\([^ ]\+\)\([^\n]*\n.*\)/%getprop \2 x\1\2\3/
	/^[^@]\+@[0-9]\+$/s/^\(%rdraw \)\([^ ]\+\)\([^\n]*\n.*\)/%getprop \2
y\1\2\3/
	/^[^@]\+@[0-9]\+@[0-9]\+$/s/^\(%rdraw \)\([^
]\+\)\([^\n]*\n.*\)/%getprop \2 w\1\2\3/
	/^[^@]\+@[0-9]\+@[0-9]\+@[0-9]\+$/s/^\(%rdraw \)\([^
]\+\)\([^\n]*\n.*\)/%getprop \2 h\1\2\3/
	t main

	# pull the results off and print it
	h
	s/.\+@\(.\+\)@\(.\+\)@\(.\+\)@\(.\+\)$/Drawing rectangle at: (\1,\2),
width: \3, height: \4/p
	x
	s/\([^@]\+\)@.\+$/\1/
	h

	b popfunc
}

#draw a circle
/^%cdraw/{
	tcdraw1
	:cdraw1

	# push a call to getprop for x, y, and r on the callstack
	# I'll clean this up some time to make it one operation.
	/@/!s/^\(%cdraw \)\([^ ]\+\)\([^\n]*\n.*\)/%getprop \2 x\1\2\3/
	/^[^@]\+@[0-9]\+$/s/^\(%cdraw \)\([^ ]\+\)\([^\n]*\n.*\)/%getprop \2
y\1\2\3/
	/^[^@]\+@[0-9]\+@[0-9]\+$/s/^\(%cdraw \)\([^
]\+\)\([^\n]*\n.*\)/%getprop \2 r\1\2\3/
	t main

	# pull the results off and print it
	h
	s/.\+@\(.\+\)@\(.\+\)@\(.\+\)$/Drawing circle at: (\1,\2), radius: \3/p
	x
	s/\([^@]\+\)@.\+$/\1/
	h

	b popfunc
}

# Add two numbers.
# Works by decrementing the right number while incrementing the left
number.
# Does not handle negative numbers.
/^%add /{
	h
	s/%add \([0-9]\+\) \([0-9]\+\).\+/\1 \2/

	: repeat

	# prepare 9s on the number to be incremented
	: eat9
	s/9\(_* \)/_\1/
	t eat9
	
	# prepare 0s on the number to be decremented
	: eat0
	s/\(.\+ .*\)0\(=*$\)/\1=\2/
	t eat0
	
	# switch tailing _s to 0s on the increment number 
	s/^\(_* \)/0\1/
	
	# decrement the left number
	s/8\(_* \)/9\1/
	s/7\(_* \)/8\1/
	s/6\(_* \)/7\1/
	s/5\(_* \)/6\1/
	s/4\(_* \)/5\1/
	s/3\(_* \)/4\1/
	s/2\(_* \)/3\1/
	s/1\(_* \)/2\1/
	s/0\(_* \)/1\1/
	
	# replace remaining _s with 0s
	s/_/0/g
	
	# increment the right number
	s/1\(=*\)$/0\1/
	s/2\(=*\)$/1\1/
	s/3\(=*\)$/2\1/
	s/4\(=*\)$/3\1/
	s/5\(=*\)$/4\1/
	s/6\(=*\)$/5\1/
	s/7\(=*\)$/6\1/
	s/8\(=*\)$/7\1/
	s/9\(=*\)$/8\1/
	
	# replace remaining =s with 9s
	s/=/9/g
	
	# if the right number isn't all 0s, repeat
	/ 0*$/!b repeat

	# store the results on the return value stack and pop the call
	s/ .*$//
	s/^/@/
	G
	s/^\(@[0-9]\+\)\n\(.\+\)/\2\1/


	b popfunc
}


# catchall to manage the loop
{
	b skipend
}

# pop a function off the function call stack
: popfunc
{
	s/%[^%\n]*\n*//
	h
	b main
}

: skipend

File: shapes.soo

# This is the control file for the shapes program.

# The dynamic dispatch and specific methods are implemented
# in shapes.sed.

# class definitions:
# Uses the form
~classname~_superclassIfAny_-field,defaultvalue-*=method,vtableName=~

def ~shape~__-x,0--y,0-=move,smove==rmove,srmove=~
def ~rectangle~_shape_-w,0--h,0-=setw,rsetw==seth,rseth==draw,rdraw=~
def ~circle~_shape_-r,0-=setr,csetr==draw,cdraw=~



# Program calls:

# Create a rectangle and a circle
obj r1 rectangle 10 20 5 6
obj c1 circle 15 25 8

# Draw and move each of the polymorphically
call r1 draw
call r1 rmove 100 100
call r1 draw

call c1 draw
call c1 rmove 100 100
call c1 draw

# create another rectangle
obj r2 rectangle 0 0 15 15

# call a rectangle specific function
call r2 setw 30
# draw it
call r2 draw

# redundant as the program will exit anyways.
exit

Output

Drawing rectangle at: (10,20), width: 5, height: 6
Drawing rectangle at: (110,120), width: 5, height: 6
Drawing circle at: (15,25), radius: 8
Drawing circle at: (115,125), radius: 8
Drawing rectangle at: (0,0), width: 30, height: 15