;;; -*- logo -*- ;;; ;;; LOOPS -- ucbLogo Object Oriented Programming System -- ;;; ;;; ... a pretentious name for a short Berkeley Logo initializing file ;;; that makes it feasible to apply principles described in Scheme and ;;; the Art of Programming. ;;;; LOOPS ;;; object compiler to object.maker [:&args&] local [&proc& proc.&text &static& var.&list var.&tmp val.&list &lambda& &optional& formal.&names &locals& init.&static &formals& &method& &binding& &class&] make "&class& first :&args& make "&args& bf :&args& make "proc.&text text :&class& ;; parsing class optional formal parameters & binding arguments ;; most of this is done by Logo parser -- the only intervention is: ;; extracting formal names and stripping default input numbers, ;; which make no sense in this context make "formal.&names filter [[&x] [op not.tf numberp :&x]] map [ [&x] [(if.tf listp :&x [op first :&x] [op :&x])]] first :proc.&text local :formal.&names define "&optional& fput first :proc.&text [[op map "thing :formal.&names]] (foreach apply "&optional& :&args& :formal.&names [[&a &f] [make :&f :&a]]) er (list "&optional&) ;; inheritance if.tf equalp first first bf :proc.&text "inherit.from [ run lput quoted :&class& first bf :proc.&text make "proc.&text bf :proc.&text] ;; temporary -- compile time bindings make "proc.&text bf :proc.&text make "&binding& first :proc.&text (while [not (or [equalp first :&binding& "static] [equalp first :&binding& "lambda] [emptyp bf :proc.&text])] [run :&binding& make "proc.&text bf :proc.&text make "&binding& first :proc.&text]) ;; setting up static storage make "var.&list [] make "&static& first :proc.&text ;; checking for top-level object if.tf not namep "unq.&object.name [ local [unq.&object.name method.&stack export.&stack] make "unq.&object.name gensym make "method.&stack [] make "export.&stack []] make "&proc& (word :unq.&object.name ". gensym ". :&class&) er lput (list :&proc&) [[] []] bury lput (list :&proc&) [[] []] (while [equalp first :&static& "static] [;; get names make "var.&tmp firsts last :&static& ;; get values & push method name if keyword "method.export found make "val.&list (map [ [&x &y] [if.tf equalp first first :&x "method.export [push "method.&stack :&y]] [op begin :&x]] bfs last :&static& :var.&tmp) ;; bind name-value pairs (foreach :var.&tmp :val.&list [[&r &l] [pprop :&proc& :&r :&l]]) make "var.&list se :var.&list :var.&tmp ;; making bindings visible in next STATIC line during compile ;; time -- that's why all these &&&... local :var.&tmp (foreach :var.&tmp :val.&list [[&r &l] [make :&r :&l]]) ;; try next static line make "proc.&text bf :proc.&text make "&static& first :proc.&text]) ;; mandatory LAMBDA line make "&lambda& first :proc.&text if not (and [equalp first :&lambda& "lambda] [equalp first last :&lambda& ":]) [ (pr [ERROR: Missing or incorrect LAMBDA line in:] :&class&) pr :&lambda& pr [it should look like this:] pr [lambda :messagename] throw "toplevel] ;; building object make "&locals& list "local :var.&list make "init.&static map.se [ [&var] [op (list "make word "" :&var "gprop word "" :&proc& word "" :&var)] ] :var.&list ;; stripping column from message name make "&formals& (list bf last :&lambda&) ;; compiling methods make "&method& (reduce [[x y] [op (word :x ". :y)]] lput "&method bl split.word :&class& ".) if.tf or memberp :&class& :.accept.method [not definedp :&method&] [ define :&method& compile.object fput [] insert.method bf :proc.&text bury :&method& make ".accept.method remove :&class& :.accept.method] ;; compiling object op compile.object (fput :&formals& fput :&locals& fput :init.&static (list (list "op char 40 ".and (list ".maybeoutput :&method&) char 41 ))) end ;;; auxiliary procedures ;; exporting methods from subclass .macro method.export :class.list :method.text foreach :class.list [[&x] [push "export.&stack :&x]] op (list insert.invoke :method.text) end ;; checking method before compilation to insert.method :text if.tf emptyp :method.&stack [op :text] if.tf not.tf memberp :&class& :export.&stack [op :text] if.tf not.tf listp :text [op :text] op insert.invoke :text end to delegate :object :msg if.tf definedp :object [op invoke :object :msg] op invoke thing :object :msg end ;; unsafe send -- for uniform syntax 'send "objectname ...' to send [:msg.list] 2 op send.helper first :msg.list end to send.helper :send.object if.tf listp :send.object ~ [op apply :send.object (list (fput :send.object bf :msg.list))] if.tf definedp :send.object ~ [op apply :send.object (list (fput :send.object bf :msg.list))] op send.helper thing :send.object end ;; indirect addressing -- all storage classes should understand ;; message indirect to indirect [:message] 2 op apply "send (fput send first :message "indirect bf :message) end ;; used in DO messages -- gets the value of the object to #self op send "self "self end ;; pacifying Logo 5beta to static :ignore end ;; pacifying Logo 5beta to lambda pr [ERROR: You probably tried to execute 'somename.class' procedure] pr [USAGE: define "objectname (object.maker "somename.class [initexp ...])] pr [| |: then send messages ... (send "objectname "somemessage ...)] throw "toplevel end ;; defining top-level object -- need the object name to new :object.name :definition define :object.name :definition push "loops.object.stack :object.name end make "loops.object.stack [] ;; Emacs IDE procedures to inspect.methods.objects :file_elisp_code local [o.prop] localmake "wr_elisp_code writer localmake "printwidthlimit 2048 localmake "printdepthlimit 16 openwrite :file_elisp_code setwrite :file_elisp_code pr "COMPILED-METHODS: pr [] pr filter [equalp "&method last split.word ? ".] first buried pr [] pr "TOP-LEVEL-OBJECTS: pr [] make "loops.object.stack filter [not memberp ? ?rest] :loops.object.stack pr (se "base.object "loops :loops.object.stack) pr [] pr "VIRTUAL-OBJECTS: pr [] make "o.prop filter [make "o.prop split.word ? ". (and equalp first first :o.prop "g [numberp last first :o.prop] [equalp last :o.prop "class])] last buried if not emptyp :o.prop [foreach objects.group :o.prop [pr ? pr []]] close :file_elisp_code setwrite :wr_elisp_code end to objects.group :lst if.tf emptyp bf :lst [op (list :lst)] local [first result] make "first first :lst make "result objects.group bf :lst if.tf (equalp (first split.word :first ".) (first split.word first bf :lst ".)) ~ [op fput fput :first first :result bf :result] op fput (list :first) :result end to show.method.object :method :file_elisp_code [:pretty "false] 3 localmake "wr_elisp_code writer localmake "printwidthlimit 2048 localmake "printdepthlimit 64 openwrite :file_elisp_code setwrite :file_elisp_code ifelse.tf definedp :method [ foreach fulltext :method [pr ?]] [ ;localmake "virtual.o plist :method local "virtual.o make "virtual.o plist :method ifelse.tf :pretty [ localmake "template [[x y] [show (list "localmake word "\" :x quoted :y)]]] [ localmake "template [[x y] [show list :x :y]]] (foreach filter [oddp count ?rest] :virtual.o filter [evenp count ?rest] :virtual.o :template)] close :file_elisp_code setwrite :wr_elisp_code end ; to show.method.object :method :file_elisp_code [:pretty "false] 3 ; localmake "wr_elisp_code writer ; localmake "printwidthlimit 2048 ; localmake "printdepthlimit 64 ; openwrite :file_elisp_code ; setwrite :file_elisp_code ; ifelse.tf definedp :method [ ; foreach fulltext :method [pr ?]] [ ; ;localmake "virtual.o plist :method ; local "virtual.o ; make "virtual.o plist :method ; ifelse.tf :pretty [ ; localmake "template ; [[x y] [show (list "localmake word "\" :x quoted :y)]]] [ ; localmake "template [[x y] [show list :x :y]]] ; (foreach filter [oddp count ?rest] :virtual.o ; filter [evenp count ?rest] :virtual.o ; :template)] ; close :file_elisp_code ; setwrite :wr_elisp_code ; end to browse.classes :file_elisp_code localmake "wr_elisp_code writer localmake "printwidthlimit 2048 localmake "printdepthlimit 16 openwrite :file_elisp_code setwrite :file_elisp_code (foreach filter [evenp count ?rest] plist "inherit.list filter [oddp count ?rest] plist "inherit.list [[from to] [pprop "inheritance.tree "inherit insert.tree :from :to gprop "inheritance.tree "inherit]]) inherit gprop "inheritance.tree "inherit " erase [[] [] [inherit.list]] close :file_elisp_code setwrite :wr_elisp_code end to inherit :tree :level if.tf emptyp :tree [stop] if.tf wordp :tree [(pr :level :tree) stop] inherit first :tree word :level char 9 inherit bf :tree :level end to inherit.from :from :to pprop "inherit.list :to :from end to insert.tree :from :to :tree if.tf or emptyp :tree [wordp :tree] [op :tree] if.tf and equalp :from first :tree [not.tf memberp :to apply "se bf :tree] [ op fput :from fput (list :to) bf :tree] op fput insert.tree :from :to first :tree insert.tree :from :to bf :tree end to edit.classes :class :file_elisp_code localmake "wr_elisp_code writer localmake "printwidthlimit 2048 localmake "printdepthlimit 64 openwrite :file_elisp_code setwrite :file_elisp_code if.tf definedp :class [ foreach fulltext :class [pr ?]] close :file_elisp_code setwrite :wr_elisp_code end to save.image [ :filename& .and [type "|Enter file name (without extension) to save to: |] [rw]] 1 bury (list [] (list "filename& "startup "printdepthlimit "printwidthlimit "bury.list) []) ;; Structures will print to 2048 elements. This is a sefeguard for circular ;; list objects. localmake "printwidthlimit 2048 localmake "printdepthlimit 1024 make "bury.list buried make "startup [ bury :bury.list (type "|Image: | last split.word :filename& "/ "| loaded.| )] if equalp first :filename& "" [make "filename& bf :filename&] if equalp last split.word :filename& ". "lgimg [ make "filename& apply "word bl split.word :filename& ".] savel (map "se contents buried) word :filename& ".lgimg end ;; Redefining method procedure? make ".accept.method [] bury [[static.make object.maker delegate send send.helper base.object new static lambda insert.method.h insert.method method.export indirect #self inspect.methods.objects objects.group show.method.object edit.classes save.image browse.classes loops inherit inherit.from insert.tree] [.accept.method loops.object.stack] [inherit.list]] ;;; base classes ;; Base object -- source commented out, only compiled code loaded ; to base.object :message ; local [object msg] ; make "object first :message ; make "msg bf :message ; op letrec [[ ; [get.sym [[txt] ; [condc [ ; [[emptyp :txt] [op "#unspecified]] ; [[equalp first :txt "gprop] ; [op first split.word bf first bf :txt ".]] ; [else [op (get.sym bf :txt)]]]]]]] ; [case [ ; [first :msg] ; [[type] [op "base.object]] ; [[erase.object] ; [localmake "symbol ; (get.sym apply "se bf bf text :object)] ; [localmake "statics filter ; [[x] [op equalp :symbol first split.word :x ".]] ; last buried] ; [er lput :statics [[] []]] ; [if not equalp :object "base.object [er (list :object)]] ; [make "loops.object.stack remove :object :loops.object.stack] ; [gc op nodes]] ; [[error] ; [(apply "pr combine "ERROR:\ bf :msg)] ; [throw "toplevel]] ; [else [(pr "ERROR: :msg "not "understood "by "object: :object)] ; [throw "toplevel]]]]] ; end ; to loops :message ; case [ ; [first bf :message] ; [[new global] ; [define first bf bf :message apply "object.maker bf bf bf :message] ; [push "loops.object.stack first bf bf :message] ; [op first bf bf :message]] ; [[virtual local] [op apply "object.maker bf bf :message]] ; [[erase.methods] ; [erase filter [equalp last split.word ? ". "&method] first buried] ; [gc op nodes]] ; [else [op delegate "base.object :message]]] ; end ;; All classes requiring static storage (which is just about all), ;; should use (dotted)pair.class as the only link to static variables. to pair.class :xinit [:yinit []] local "cons.cell make "cons.cell (list :xinit) .setbf :cons.cell :yinit static [[pair.o :cons.cell]] lambda :msg op case [ [first bf :msg] [[get getx self indirect] [first :pair.o]] [[set setx make] [.setfirst :pair.o first bf bf :msg first :pair.o]] [[gety] [bf :pair.o]] [[sety] [.setbf :pair.o first bf bf :msg bf :pair.o]] [[raw] [:pair.o]] [[p2l] [list first :pair.o bf :pair.o]] [[type] ["dotted.pair.object]] [else [delegate "base.object :msg]]] end ;; swap.pair based on pair.class to swap.pair.class :xinit [:yinit []] static [[pair.o [(send "loops "virtual "pair.class :xinit :yinit)]]] lambda :msg local "tmp op case [ [first bf :msg] [[swap] [make "tmp send "pair.o "gety] [ignore (send "pair.o "sety send "pair.o "get)] [(send "pair.o "set :tmp)]] [[type] ["swap.pair.object]] [else [delegate "pair.o :msg]]] end ;; border.class is used to create terminal objects, that can stop execution, ;; and extract results by sending messages to other objects to border.class :text [:objects []] [:msgs []] static [[text.o [(send "loops "virtual "pair.class :text)]] [objmsg.pairs [(send "loops "virtual "pair.class :objects :msgs)]]] lambda :message local [self msg] make "self first :message make "msg bf :message case [ [first :msg] [[exit.outer] [op "false]] [[erase.object] [op delegate "base.object :msg]] [else [ ;; output text and all results of terminal object msg calls ;; use 'self to send msg 'exit.outer to border.class object -- as a ;; flag to break out of the outer loop (see queens8.class) op se send "text.o "get ~ (map [[x y] [op apply "send se :x :y]] send "objmsg.pairs "get send "objmsg.pairs "gety)]]] end ;; simple counter to counter.class :init if not numberp :init [(send "base.object "error [Input must be a number!])] static [[init.val :init] [pair.o [(send "loops "virtual "pair.class :init)]]] lambda :msg op case [ [first bf :msg] [[inc] [(send "pair.o "set sum (send "pair.o "get) 1)]] [[dec] [(send "pair.o "set (send "pair.o "get) - 1)]] [[reset] [(send "pair.o "set :init.val)]] [[type] ["counter.object]] [else [delegate "pair.o :msg]]] end ;; circular counter init -> limit , init -> limit ... to rewinding.counter.class :init :limit if not numberp :limit [(send "base.object "error [Input must be a number!])] static [[init.val :init] [rewind :limit] [counter.o [(send "loops "virtual "counter.class :init)]]] lambda :msg case [ [first bf :msg] [[inc] [op ifelse (send "counter.o "get) < :rewind [(send "counter.o "inc)] [(send "counter.o "reset)]]] [[type] [op "rewinding.counter.object]] [[dec] [op delegate "base.object :msg]] [else [op delegate "counter.o :msg]]] end ;; container word/list to container.class :init static [[container.o [(send "loops "virtual "pair.class :init)]]] lambda :message local [msg self] make "msg bf :message make "self first :message op case [ [first :msg] [[bf butfirst] [ignore (send "container.o "sety first send "container.o "get)] [(send "container.o "set bf send "container.o "get)]] [[bl butlast] [ignore (send "container.o "sety last send "container.o "get)] [(send "container.o "set bl send "container.o "get)]] [[first last count pick empty? emptyp] [invoke :#target send "container.o "get]] [[word se sentence] [(send "self "do! fput word "\( :#target lput "\) first bf :msg)]] [[do] [run first bf :msg]] [[do!] [(send "self "set (send "self "do first bf :msg))]] [[item member? memberp member] [(invoke :#target first bf :msg send "container.o "get)]] [[listp list? wordp word?] [(invoke :#target send "container.o "self)]] [[flush] [(send "container.o "set ifelse listp send "container.o "get [[]] ["])]] [[type] ["container.object]] [else [delegate "container.o :message]]] end ;; word class to word.class [:init "] if not wordp :init [ (send "base.object "error [Input must be a word!])] static [[word.o [(send "loops "virtual "container.class :init)]]] lambda :msg op case [ [first bf :msg] [[type] ["word.object]] [else [delegate "word.o :msg]]] end ;; list class to list.class [:init []] if not listp :init [ (send "base.object "error [Input must be a list!])] static [[list.o [(send "loops "virtual "container.class :init)]]] lambda :msg op case [ [first bf :msg] [[fput lput] [(send "list.o "set (invoke :#target first bf bf :msg send "list.o "self))]] [[type] ["list.object]] [else [delegate "list.o :msg]]] end ;; FILO structure based on list class to stack.class static [[holder.o [(send "loops "virtual "list.class)]]] lambda :msg op case [ [first bf :msg] [[push] [(send "holder.o "fput first bf bf :msg)]] [[pop] [ifelse.tf send "holder.o "empty? [(send "holder.o "error [Stack is empty])] [ignore send "holder.o "bf send "holder.o "gety]]] [[top] [ifelse.tf send "holder.o "empty? [(send "holder.o "error [Stack is empty])] [send "holder.o "first]]] [[size] [send "holder.o "count]] [[type] ["stack.object]] [else [delegate "holder.o :msg]]] end ;; double ended list class to double.end.class [:init []] if not listp :init [(send "base.object "error [Input must be a list!])] static [[cell.o [ (send "loops "virtual "pair.class :init ifelse emptyp :init [:init] [last.pair :init])]]] lambda :message local [msg self tmp] make "msg bf :message make "self first :message case [ [first :msg] [[lput] [make "tmp (list first bf :msg)] [ifelse.tf emptyp send "cell.o "get [ignore (send "cell.o "set :tmp)] [.setbf (send "cell.o "gety) :tmp]] [op (send "cell.o "sety :tmp)]] [[first indirect] [op first send "cell.o "get]] [[bf butfirst] [op (send "cell.o "set bf send "cell.o "get)]] [[last] [op first send "cell.o "gety]] [[empty? emptyp] [op emptyp send "cell.o "get]] [[fput] [if.tf emptyp send "cell.o "get [op (send "self "lput first bf :msg)]] [make "tmp (list first bf :msg)] [.setbf :tmp send "cell.o "get] [op (send "cell.o "set :tmp)]] [[import] [ignore (send "cell.o "sety last.pair fput "x first bf :msg)] [op (send "cell.o "set first bf :msg)]] [[bl butlast] [op (send "self "import bl send "cell.o "get)]] [[count] [op count send "cell.o "get]] [[flush] [op (send "self "import [])]] [[member? memberp] [op memberp first bf :msg send "cell.o "get]] [[member] [op member first bf :msg send "cell.o "get]] [[shift.left] [make "tmp first send "cell.o "get] [ignore send "self "bf] [ignore (send "self "lput :tmp)] [op send "cell.o "get]] [[shift.right] [make "tmp first send "cell.o "gety] [ignore send "self "bl] [op (send "self "fput :tmp)]] [[export self] [op map "identity send "cell.o "get]] [[type] [op "double.ended.list.object]] [else [op delegate "cell.o :message]]] end ;; queue based on double ended class to queue.class static [[del.o [(send "loops "virtual "double.end.class)]]] lambda :message local [tmp msg self] make "msg bf :message make "self first :message case [ [first :msg] [[enqueue] [op first (send "del.o "lput first bf :msg)]] [[dequeue] [make "tmp send "self "front] [ignore send "del.o "bf] [op :tmp]] [[front] [if.tf send "del.o "empty? [(send "del.o "error [queue is empty])]] [op send "del.o "first]] [[type] [op "queue.object]] [[lput fput bf butfirst bl butlast first last shift.left shift.right] [op delegate "base.object :message]] [else [op delegate "del.o :message]]] end ;; circular list storage to circular.class static [[pointer [[mark]]]] static [[start.o [localmake "cl (list :pointer) .setbf :cl :cl] [(send "loops "virtual "pair.class :cl)]]] static [[end.o [(send "loops "virtual "pair.class (send "start.o "get))]]] lambda :message local [tmp msg self] make "msg bf :message make "self first :message op letrec [[ [open [[] [.setbf (send "end.o "get) []] [op (send "start.o "get)]]] [close [[] [.setbf (send "end.o "get) (send "start.o "get)] [op (send "start.o "get)]]] [bl.pair [[clst] [if.tf .eq first bf bf :clst :pointer [op :clst]] [op (bl.pair bf :clst)]]]] [case [ [first :msg] [[lput] [make "tmp (send "end.o "get)] [.setbf :tmp (list first bf :msg)] [.setbf bf :tmp (send "start.o "get)] [op bf (send "end.o "set bf :tmp)]] [[fput] [make "tmp bf (send "start.o "get)] [.setbf (send "start.o "get) (list first bf :msg)] [.setbf bf (send "start.o "get) :tmp] [if.tf .eq first (send "end.o "get) :pointer [ ignore (send "end.o "set bf (send "start.o "get))]] [op (send "start.o "get)]] [[bf butfirst] [.setfirst (send "start.o "set bf (send "start.o "get)) :pointer] [make "tmp (send "start.o "get)] [.setbf (send "end.o "get) :tmp] [op :tmp]] [[bl butlast] [make "tmp (send "start.o "get)] [.setbf (send "end.o "set (bl.pair bf :tmp)) :tmp] [op :tmp]] [[first indirect] [op first bf (send "start.o "get)]] [[last] [op first (send "end.o "get)]] [[empty? emptyp] [op .eq first bf (send "start.o "get) :pointer]] [[count] [make "tmp count bf (open)] [ignore (close)] [op :tmp]] [[export] [make "tmp map "identity bf (open)] [ignore (close)] [op :tmp]] [[import] [make "tmp (send "start.o "set fput :pointer first bf :msg)] [.setbf (send "end.o "set last.pair :tmp) :tmp] [op (send "self "export)]] [[flush] [op (send "self "import [])]] [[memberp member?] [make "tmp memberp first bf :msg bf (open)] [ignore (close)] [op :tmp]] [[member] [op member first bf :msg (send "self "export)]] [[rotate] [make "tmp first bf (send "start.o "get)] [.setfirst bf (send "start.o "get) :pointer] [.setfirst (send "start.o "get) :tmp] [ignore (send "end.o "set (send "start.o "get))] [op (send "start.o "set bf (send "start.o "get))]] [[type] [op "circular.list.object]] [[set] [op delegate "base.object :message]] [else [op delegate "start.o :message]]]]] end ;; Inheritance tree pprop "inheritance.tree "inherit [pair.class [swap.pair.class] [counter.class [rewinding.counter.class]] [container.class [word.class] [list.class [stack.class]]] [double.end.class [queue.class]] [border.class] [circular.class]] bury [[counter.class rewinding.counter.class queue.class container.class list.class word.class stack.class circular.class pair.class swap.pair.class double.end.class border.class] [] [inheritance.tree]] ;;; compiled code to base.object :message local [object msg] make "object first :message make "msg bf :message op run [local "get.sym make "get.sym [[txt] [run [if.tf emptyp :txt [op "#unspecified] if.tf equalp first :txt "gprop [op first split.word bf first bf :txt ".] op ( invoke :get.sym bf :txt )]]] local "dummy.letrec make "dummy.letrec [[] [run [local "#target make "#target run [first :msg] ifelse.tf memberp :#target [type] [op "base.object] [ifelse.tf memberp :#target [erase.object] [localmake "symbol ( invoke :get.sym apply "se bf bf text :object ) localmake "statics filter [[x] [op equalp :symbol first split.word :x ".]] last buried er lput :statics [[] []] if not equalp :object "base.object [er ( list :object )] make "loops.object.stack remove :object :loops.object.stack gc op nodes] [ifelse.tf memberp :#target [error] [( apply "pr combine "ERROR\:\ bf :msg ) throw "toplevel] [( pr "ERROR: :msg "not "understood "by "object: :object ) throw "toplevel]]]]]] run [( invoke :dummy.letrec )]] end to loops :message run [local "#target make "#target run [first bf :message] ifelse.tf memberp :#target [new global] [define first bf bf :message apply "object.maker bf bf bf :message push "loops.object.stack first bf bf :message op first bf bf :message] [ifelse.tf memberp :#target [virtual local] [op apply "object.maker bf bf :message] [ifelse.tf memberp :#target [erase.methods] [erase filter [equalp last split.word ? ". "&method] first buried gc op nodes] [op delegate "base.object :message]]]] end bury [[base.object loops loops.compiler] [] []] pr "| OOP layer OK| ;;; End of .LOOPS