_ROLL YOUR OWN OBJECT-ORIENTED LANGUAGE_ by Michael Floyd [LISTING ONE] /* File : OOP.PRO -- Include file that adds inheritance mechanism and message passing facility. This file also declares the object-oriented predicates msg(), method(), has(), and is_a(). Objects are implemented using a technique known as frames; the inheritance mechanism is based on the article "Suitable for Framing" by Michael Floyd (Turbo Technix, April/May '87). Michael Floyd -- DDJ -- 8/28/90 -- CIS: [76703,4057] or MCI Mail: MFLOYD */ DOMAINS object = object(string,slots) % not actually used in examples objects = object* slot = slot(string,value) slots = slot* value = int(integer) ; ints(integers) ; real_(real) ; reals(reals) ; str(string) ; strs(strings) ; object(string,slots,parents) ; objects(objects) parents = string* integers = integer* reals = real* strings = string* % --- OOP preds to be used by the programmer --- DATABASE has(string,slots) % storage for instance vars is_a(string,string) % hierarchy relationships PREDICATES msg(string,string) % send a message to an object method(string, string) % define a method % --- Internal Predicates not called ditrectly by the programmer --- inherit(string,slot) % inheritance mechanism description(string,slot) % search for matching clauses member(slot,slots) % look for object in a list CLAUSES /* Inheritance Mechanism */ inherit(Object,Value):- description(Object,Value),!. inherit(Object,Value):- is_a(Object,Object1), inherit(Object1,Value), description(Object1,_). description(Object,Value):- has(Object,Description), member(Value,Description). description(Object,slot(method,str(Value))):- method(Object,Value). /* Simple message processor */ msg(Object,Message):- inherit(Object,slot(method,str(Message))). /* Support Clauses */ member(X,[X|_]):-!. % Find specified member in a list member(X,[_|L]):-member(X,L). [LISTING TWO] /* File: FIGURES.PRO -- Object Prolog example that models FIGURES example in Turbo C++ and Turbo Pascal documentation Michael Floyd -- DDJ -- 8/28/90 */ include "bgi.pro" include "OOP.PRO" domains key = escape; up_arrow; down_arrow; left_arrow; right_arrow; other database - SHAPES anyShape(string) PREDICATES % Support predicates horiz(integer, integer, string) vert(integer, integer, integer) readkey(integer, integer, integer) key_code(key, integer, integer, integer) key_code2(key, integer, integer, integer) repeat main CLAUSES /* Methods */ /* point is an example of an Abstract object. Note that variables passed through the database must be explicitly called by the child method (i.e. variables are not inherited). */ method(point, init):- assert(has(point,[slot(x_coord,int(150)), slot(y_coord,int(150))])). method(point, done):- retractall(has(point,_)). method(point,show):-!, has(point,[slot(x_coord,int(X)), slot(y_coord,int(Y))]), putpixel(X,Y,blue). method(point,hide):-!, has(point,[slot(x_coord,int(X)), slot(y_coord,int(Y))]), putpixel(X,Y,black). /* Example of a virtual method */ method(point,moveTo):-!, anyShape(Object), msg(Object,hide), retract(has(point,[slot(x_coord,int(DeltaX)), slot(y_coord,int(DeltaY))])), msg(Object,show). method(point,drag):- has(point,[slot(x_coord,int(X)), slot(y_coord,int(Y))]), anyShape(Shape),!, msg(Shape,show), repeat, readkey(Key, DeltaX, DeltaY), assertz(has(point,[slot(x_coord,int(DeltaX)), slot(y_coord,int(DeltaY))])), msg(point,moveTo), Key = 27. /* Circle Methods */ method(circle, init):-!, method(point, init), assert(anyShape(circle)). method(circle, done):-!, retract(anyShape(circle)), method(point, done). method(circle, show):-!, has(point,[slot(x_coord,int(X)), slot(y_coord,int(Y))]), setcolor(white), circle(X,Y,50). method(circle, hide):-!, has(point,[slot(x_coord,int(X)), slot(y_coord,int(Y))]), setcolor(black), circle(X,Y,50). method(circle, drag):- msg(circle, hide), is_a(circle, Ancestor), msg(Ancestor, drag), msg(circle, show). /* arc Methods */ method(arc, init):-!, assert(anyShape(arc)), assert(has(point,[slot(x_coord,int(150)), slot(y_coord,int(150))])), assert(has(arc,[slot(radius,int(50)), slot(startAngle,int(25)), slot(endAngle,int(90))])). method(arc, done):- retract(anyShape(arc)),!, retractall(has(arc,_)), method(point, init). method(arc, show):- has(point,[slot(x_coord,int(X)), slot(y_coord,int(Y))]), has(arc,[slot(radius,int(Radius)), slot(startAngle,int(Start)), slot(endAngle,int(End))]),!, setcolor(white), arc(X, Y, Start, End, Radius). method(arc, hide):- has(point,[slot(x_coord,int(X)), slot(y_coord,int(Y))]), has(arc,[slot(radius,int(Radius)), slot(startAngle,int(Start)), slot(endAngle,int(End))]),!, setcolor(black), arc(X, Y, Start, End, Radius). method(arc,drag):- msg(arc, hide), is_a(arc,Ancestor),!, msg(Ancestor,drag), msg(arc, show). /* rectangle Methods */ method(rectangle,init):- has(rectangle,[slot(length,int(L)), slot(width,int(W))]),!. method(rectangle,init):-!, write("Enter Length of rectangle: "), readint(L),nl, write("Enter Width of rectangle: "), readint(W),nl, assert(has(rectangle,[slot(length,int(L)), slot(width,int(W))])). method(rectangle,done):- retract(has(rectangle,[slot(length,int(L)), slot(width,int(W))])),!. method(rectangle,draw):-!, has(rectangle,[slot(length,int(L)), slot(width,int(W))]), write("Z"), horiz(1,L,"D"), write("?"),nl, vert(1,W,L), write("@"), horiz(1,L,"D"), write("Y"). method(rectangle,draw):- write("Cannot draw rectangle"),nl. /* Support Methods */ horiz(I,L,Chr):- I <= L,!, TempI = I + 1, write(Chr), horiz(TempI,L,Chr). horiz(I,L,Chr):-!. vert(I,W,L):- I <= W,!, TempI = I + 1, write("3"), horiz(1,L," "), write("3"),nl, vert(TempI,W,L). vert(I,W,L):-!. /* Ancestor/Child relationships - should be stored in consult() file */ is_a(circle,point). is_a(arc,point). is_a(triangle,shape). is_a(rectangle,shape). is_a(solid_rectangle,rectangle). /* Generic clause to read cursor keys - used by the Drag method */ readkey(Val, NewX, NewY) :- readchar(T), char_int(T, Val), key_code(Key, Val, NewX, NewY). key_code(escape, 27, 0, 0) :- !. key_code(Key, 0, NewX, NewY) :- !, readchar(T), char_int(T, Val), key_code2(Key, Val, NewX, NewY). key_code2(up_arrow, 72, NewX, NewY) :- !, has(point,[slot(x_coord,int(X)), slot(y_coord,int(Y))]), NewX = X, NewY = Y - 5. key_code2(left_arrow, 75, NewX, NewY):- !, has(point,[slot(x_coord,int(X)), slot(y_coord,int(Y))]), NewX = X - 5, NewY = Y. key_code2(right_arrow, 77, NewX, NewY) :- !, has(point,[slot(x_coord,int(X)), slot(y_coord,int(Y))]), NewX = X + 5, NewY = Y. key_code2(down_arrow, 80, NewX, NewY) :- !, has(point,[slot(x_coord,int(X)), slot(y_coord,int(Y))]), NewX = X, NewY = Y + 5. key_code2(other, _,0,0). /* Supports the repeat/fail loop */ repeat. repeat:- repeat. main:- nl, initialize, % init BGI graphics makewindow(1,7,0,"",0,0,25,80), msg(circle, init), % create and manipulate a circle msg(circle, show), msg(circle, drag), msg(circle, done), clearwindow, msg(arc, init), % create and manipulate an arc msg(arc, show), msg(arc, drag), msg(arc, done), closegraph, % return to text mode makewindow(2,2,3,"",0,0,25,80), msg(rectangle, init), % create a rectangle in text mode msg(rectangle, draw), msg(rectangle, done). goal main. [LISTING THREE] /* File: BGI.PRO -- Minimum required to detect graphics hardware an initialize system in graphics mode using BGI. BGI.PRE is included with PDC Prolog. Michael Floyd -- DDJ -- 8/28/90 */ include "D:\\prolog\\include\\BGI.PRE" CONSTANTS bgi_Path = "D:\\prolog\\bgi" PREDICATES Initialize CLAUSES Initialize:- DetectGraph(G_Driver, G_Mode), InitGraph(G_Driver,G_Mode, _, _, bgi_Path),!. [LISTING FOUR] include "bgi.pro" include "support.pro" database - figures anyShape(string) point = object int XCoord YCoord method(init) if XCoord = 150, YCoord = 150. method(done) if retract(has(point,_)). method(show) if putpixel(XCoord,YCoord,blue). method(hide) if putpixel(XCoord,YCoord,black). method(moveTo) if anyShape(Object), msg(Object, hide), retract(has(point,_)), msg(Object, show). method(drag) if anyShape(Shape), msg(Shape,show), repeat, readkey(Key, DeltaX, DeltaY), XCoord = DeltaX, YCoord = DeltaY, msg(point,moveTo), Key = 27. end. circle = object(point) int XCoord YCoord method(init) if XCoord = 200, YCoord = 200, assert(anyShape(circle)). method(done) if retract(anyShape(circle)), msg(point,done). method(show) if setcolor(white), circle(XCoord, YCoord, 50). method(hide) if setcolor(black), circle(XCoord, YCoord, 50). method(drag) if msg(circle,hide), is_a(circle, Ancestor), msg(Ancestor,drag), msg(circle,show). end. arc = object(point) int XCoord YCoord Radius StartAngle EndAngle method(init) if Radius = 50, StartAngle = 25, EndAngle = 90, msg(point, init). method(done) if retract(anyShape(arc)), retractall(has(arc,_)), msg(point, done). method(show) if setcolor(white), arc(XCoord,YCoord,StartAngle,EndAngle,Radius). method(hide) if setcolor(black), arc(XCoord,YCoord,StartAngle,EndAngle,Radius). method(drag) if msg(arc,hide), is_a(arc,Ancestor), msg(Ancestor,drag), msg(arc,show). end. [LISTING FIVE] /* File: PARSER.PRO -- Implements parser to translate ODL to Object Prolog code. Top-down parser; simulates parse tree through predicate calls. Michael Floyd -- DDJ -- 8/28/90 */ include "lex.pro" DOMAINS file = infile; outfile; tmpfile PREDICATES main repeat gen(tokl) scan scan_object(tokl,string) findAncestor(tokl) init_vars scan_methods(string) getMethEnd(string,string) write_includes generate_code generate_methods generate_ancestor(string) generate_vars fixVar(tokl) insert_isa(tokl) bindvars(tokl,tokl) addVarRef(tokl) assert_temp(strings,tokl) construct_has(tokl) empty(tokl) isvar(tokl,tokl) is_op(tok) value(tok, integer) search_ch(CHAR,STRING,INTEGER,INTEGER) process(string) read(string) datatype(string) headbody(tokl,tokl,tokl) search_msg(tokl,tokl,tokl) constVar(string,tok) writeSlotVars write_seperator(tokl) write_comma append(tokl,tokl,tokl) CLAUSES repeat. repeat:- repeat. /**** Parser ****/ scan:- readln(ObjectStr), ObjectStr <> "", tokl(ObjectStr,ObjList), scan_object(ObjList,Object), init_vars, scan_methods(Object),!, generate_code. scan:- scan. scan_object([H|List],S):- member(object,List), str_tok(S,H), assert(objectname(S)), findAncestor(List). scan_object(List,_):- openappend(tmpfile,"headers.$$$"), writedevice(tmpfile), gen(List),nl, writedevice(screen), closefile(tmpfile),trace(off), fail. findAncestor(List):- member(lpar,List), insert_isa(List). findancestor(_). insert_isa([H|List]):- str_tok(S,H), S <> "(", insert_isa(List). insert_isa([H1,H2|List]):- str_tok(Ancestor,H2), assert(ancestor(Ancestor)). init_vars:- readln(VarStr), process(VarStr). process(VarStr):- fronttoken(VarStr,Token, RestStr), datatype(Token), tokl(VarStr,VarList),!. % tokenize/init variables process(VarStr):- assert(unread(VarStr)). datatype(int). % datatypes supported datatype(real). constVar(int,int(_)). % convert tok to string constVar(real,real_(_)). read(Str):- retract(unread(Str)),!. read(Str):- readln(Str). scan_methods(MethodId):- readln(FirstLn), getMethEnd(FirstLn,Method), search_ch('(',Method,0,N), % Find lpar and insert MethodId N1 = N+1, % and add comma fronttoken(MComma,MethodId,","), frontstr(N1,Method,Str,OUT1),!, fronttoken(Method1,MComma,Out1), fronttoken(Method2,Str,Method1), tokl(Method2,MList), % Now Tokenize the method not(member(end,MList)), % Check for End statement assert(methods(MList)), % Store method list scan_methods(MethodId). % Look for more methods scan_methods(_):- !. getMethEnd(Line1,ReturnLn):- search_ch('.',Line1,0,N), N <> 0,!, ReturnLn = Line1. getMethEnd(Line1,ReturnLn):- readln(Line2), fronttoken(AppendLn,Line1,Line2), getMethEnd(AppendLn,ReturnLn). /**** Entry point into the code generator ****/ generate_code:- objectname(Object), generate_ancestor(Object), openappend(tmpfile,"methods.$$$"), writedevice(tmpfile), generate_methods, generate_vars, vars(VarList),!, openappend(tmpfile,"has.$$$"), writedevice(tmpfile), write("has(",Object,",",VarList), write(")."),nl, writedevice(screen), closefile(tmpfile), retract(objectname(Object)). generate_vars:- findall(Var,var(Var),VarList), % retrieve vars retractall(var(_)), % cleanup database fixVar(VarList), findall(X,var(X),Slots), % retrieve new vars retractall(var(_)), % cleanup database assert(vars(Slots)). % store vars as list of slots generate_vars:- !. fixVar([]):- !. fixVar([slot(UpToken,Const)|Rest]):- upper_lower(UpToken,Token), assert(var(slot(Token,Const))), fixVar(Rest). generate_ancestor(Object):- openappend(tmpfile,"isa.$$$"), % open temp file for is_a writedevice(tmpfile), % stdout to tmpfile objectname(Obj), % get current object id retract(ancestor(Parent)), % get parent in hierarchy write("is_a(",Obj,",",Parent,")."), % write is_a clause nl, writedevice(screen), % stdout to screen closefile(tmpfile). % close temp file generate_ancestor(_):- % always succeed writedevice(screen), % stdout to screen closefile(tmpfile). % close temp file generate_methods:- retract(methods(Method)),!, headBody(Method,Head,Body), bindvars(Body,NewBody), gen(Head), write(":-"), nl, addVarRef(NewBody), gen(NewBody),nl, generate_methods. generate_methods:- writedevice(screen), closefile(tmpfile). /* Binding of variable names in for has() lookups */ addVarRef(Body):- findall(Variable, var(slot(Variable,_)), VList), findall(X, var(slot(_,X)), XList), assert_temp(VList, XList), construct_has(Body). addVarRef(Body). assert_temp([],[]):- !. assert_temp([V|VList],[X|XList]):- constVar(Type,X), assert(tempvar(V)), assert(temptype(Type)), assert_temp(VList,XList). construct_has(Body):- objectname(Object), write(" has(",Object,",","["), writeSlotVars, write(")"), write_seperator(Body),nl. write_seperator([]):- write("."). write_seperator(_):- write(","). writeSlotVars:- retract(tempVar(Var)), retract(temptype(Type)), upper_lower(Var,VarId), write("slot(",VarId,", ",Type,"(",Var,"))"), write_comma, writeSlotVars. writeSlotVars:- !, write("]"). write_comma:- tempvar(_), write(","). write_comma:- !. /* Append two lists */ append([], List, List). append([H|List1], List2, [H|List3]):- append(List1, List2, List3). search_msg([H,H2|Body],[],Body):- H = msg, H2 = lpar. search_msg([H|Method], [H|Head], Body):- search_msg(Method,Head,Body). search_ch(CH,STR,N,N):- % Search for char in string frontchar(STR,CH,_),!. % and return its position search_ch(CH,STR,N,N1):- frontchar(STR,_,S1), N2 = N + 1, search_ch(CH,S1,N2,N1). headbody([H|Body],[],Body):- str_tok("if",H). headBody([H|Method], [H|Head], Body):- headBody(Method,Head,Body). bindvars(Method,NewMethod):- is_op(Op), % supports any operator member(Op,Method), % defined by is_op() isvar(Method,[H|RestMethod]), % locate variable in method bindvars(RestMethod,NewMethod). % look for more vars bindvars(NewMethod,NewMethod):- !. % return Method w/out vars empty([]). % simple test for empty list isvar([],[]):-!. isvar([id(X),H2,H3|RestMethod],RestMethod):- is_op(H2), value(H3,Value), objectname(ObjId), retract(var(slot(X,_))),!, % add "var not decl." error here assert(var(slot(X,H3))). isvar([H|Method],NewMethod):- isvar(Method,NewMethod). is_op(equals). is_op(plus). value(int(X),X). gen([]):- !. gen([H|List]):- str_tok(S,H), write(S), gen(List). write_includes:- write("include \"oop.pro\""),nl. main:- /**** Reads file (e.g, FIGURES.ODL) specified on command line. First order of business is to add error handling for command line processsor. ****/ comline(Filename), openread(infile, Filename), readdevice(infile), repeat, scan, eof(infile), readdevice(keyboard), openwrite(outfile,"newfig.pro"), writedevice(outfile), write_includes,nl, file_str("headers.$$$",Headers), write(Headers),nl, write("clauses\n"), file_str("methods.$$$",Methods), write(Methods),nl,nl, file_str("isa.$$$",Isa), write(Isa),nl,nl, file_str("has.$$$",Has), write(Has), writedevice(screen), closefile(outfile), closefile(infile), deletefile("headers.$$$"), deletefile("methods.$$$"), deletefile("isa.$$$"), deletefile("has.$$$"), write("done"). goal main. [LISTING SIX] /* File: LEX.PRO -- Implements scanner which tokenizes ODL. To modify, add appropriate DOMAIN declarations and str_tok definitions. Michael Floyd -- DDJ -- 8/28/90 */ DOMAINS tok = id(string); int(integer); real_(real) ; plus; minus; mult; div; lpar; rpar; comma; colon; semicolon; period; object; method; msg; end; ancestor; var(string); equals; if_; slash; bslash; slot(string,tok); dummy tokl = tok* strings = string* DATABASE nextTok(string) % Token lookahead objectname(string) % current Object ID vars(tokl) % variables list methods(tokl) % methods list var(tok) % individual var ancestor(string) % tracks Object's ancestor unread(string) tempVar(string) temptype(string) PREDICATES tokl(string, tokl) % entry point into the scanner tokenize(string,tokl) % tokenize a string str_tok(string, tok) % return individual token member(tok, tokl) % verify member is in list scan_next(string) % setup lookahead stack clauses str_tok("int",slot(Token,int(0))):- retract(nextTok(Token)),!, assert(var(slot(Token,int(0)))), str_tok("int",_). str_tok("int",slot(dummy,int(0))):- !, assert(nextTok(dummy)). str_tok("real",slot(Token,real_(0))):- retract(nextTok(Token)),!, assert(var(slot(Token,real_(0)))), str_tok("real",_). str_tok("real",slot(dummy,real_(0))):- !. str_tok("(", lpar):- !. str_tok(")", rpar):- !. str_tok("=", equals):- !. str_tok("+", plus):- !. str_tok("-", minus):- !. str_tok("*", mult):- !. str_tok("/", div):- !. str_tok("\"",bslash):- !. str_tok(",", comma):- !. str_tok(":", colon):- !. str_tok(";", semicolon):- !. str_tok(".", period):- !. str_tok("if", if_):- !. str_tok("object", object):- !. str_tok("method", method):- !. str_tok("msg", msg):- !. str_tok("end", end):-!. /* str_tok(Var, var(Var)):- frontchar(Var,X,_), X >= 'A', X <= 'Z'.*/ str_tok(ID, id(ID)):- isname(ID),!. str_tok(IntStr,int(Int)):- str_int(Intstr,Int). /* Entry point into the scanner */ tokl(Str, Tokl):- fronttoken(Str, Token, RestStr), scan_next(RestStr), tokenize(Str,Tokl). tokenize("",[]):- !, retractall(nexttok(_)). tokenize(Str, [Tok|Tokl]):- fronttoken(Str, Token, RestStr), str_tok(Token, Tok), tokenize(RestStr, Tokl). scan_next(""). scan_next(RestStr):- fronttoken(RestStr, NextToken, MoreStr), assert(nexttok(NextToken)), scan_next(MoreStr). member(X,[X|_]):-!. member(X,[_|L]):-member(X,L).