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
# 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
# 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
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