diff options
Diffstat (limited to 'graphics/pgf/base/tex/generic/modules/pgfmoduleoo.code.tex')
-rw-r--r-- | graphics/pgf/base/tex/generic/modules/pgfmoduleoo.code.tex | 844 |
1 files changed, 844 insertions, 0 deletions
diff --git a/graphics/pgf/base/tex/generic/modules/pgfmoduleoo.code.tex b/graphics/pgf/base/tex/generic/modules/pgfmoduleoo.code.tex new file mode 100644 index 0000000000..86c1e8e996 --- /dev/null +++ b/graphics/pgf/base/tex/generic/modules/pgfmoduleoo.code.tex @@ -0,0 +1,844 @@ +% Copyright 2019 by Till Tantau +% Copyright 2019 by Saso Zivanovic +% +% This file may be distributed and/or modified +% +% 1. under the LaTeX Project Public License and/or +% 2. under the GNU Public License. +% +% See the file doc/generic/pgf/licenses/LICENSE for more details. +% +% Based on pgfmoduleoo.code.tex v1.8 by Till Tantau. This version adds inheritance. + + + +% Support of object-oriented programming in TeX, with inheritance. + + +% The oo support works as follows: +% +% The main supported concepts are classes, objects, methods, +% attributes and signal/slots. A class defines a set of methods, which are, +% in the end, just normal TeX macros. Once a class has been created, +% it can be instantiated by calling the \pgfoonew command, resulting +% in a new object. Objects are local to their group. Given an object, +% you can send it a message, resulting in the method code of the +% object's method to be executed. While an object exists, it has a set +% of attributes whose values can change over time. Attributes values +% are not local to TeX groups, rather their life-cycle is the same as +% the object's (which, however, is local to the group in which the +% object was declared). +% +% The implementation is as follows: There is an ID counter that is +% increased each time an object is created. This counter is local to +% the group, which means that when a group ends the counter will +% revert to the previous value, allowing objects to the reused in +% subsequent groups. +% +% A method is just a normal \TeX macro, but to call it you write +% \objecthandle.methodname(parameters). The \objecthandle is a macro +% that is created when you say \pgfoonew. The special object +% \pgfoothis is the current object. +% +% Attributes are stored globally in internal TeX macros whose name is +% composed of the object number and the attribute name. + + + + +% Declares a class +% +% #1 = class name +% #2 = methods +% +% Description: +% +% This command declares a class for future use. Inside #2, the macro +% \method can be used to declare a method. The \method macro takes a +% method name, parameters (methods are normal TeX macros, after all) +% and body. + +% base classes are put in parenthesis before the new class name, but +% are optional. +\def\pgfooclass{% + \pgfutil@ifnextchar ({% + \pgfooclass@ + }{% + \pgfooclass@() + }% +}% +\long\def\pgfooclass@(#1)#2#3{% + \def\pgfoo@classname{#2}% + \expandafter\ifx\csname pgfooY\pgfoo@classname.@pgfooinit\endcsname\relax\else + \pgferror{class #2 is already defined}% + \fi + \pgfoo@ciii{#2}{#1}% + \expandafter\let\csname pgfooY#2@pgfoo@mro\endcsname\pgfoo@mro + \let\pgfoo@origmethod=\method% + \let\pgfoo@origattribute=\attribute% + \let\method=\pgfoo@declaremethod% + \let\attribute=\pgfoo@declareattribute% + \let\pgfoo@attributes=\pgfutil@empty% + \let\pgfoo@methods=\pgfutil@empty% + #3% + % inherit + \def\pgfoo@temp@baseclasses{#1}% + \pgfoo@inherit@methods + \pgfoo@inherit@attributes + % Always present (and never inherited) methods: + \expandafter\let\csname pgfooY\pgfoo@classname.get handle\endcsname\pgfoo@obj% + \expandafter\let\csname pgfooY\pgfoo@classname.get id\endcsname\pgfoo@id% + % for compatibility with previous versions of pgfoo, define method + % with the same name as the class as a synonym for init + \expandafter\let\expandafter\pgfoo@init\csname pgfooY\pgfoo@classname.init\endcsname + \expandafter\let\csname pgfooY\pgfoo@classname.\pgfoo@classname\endcsname\pgfoo@init + % Cleanup + \let\method=\pgfoo@origmethod% + \let\attribute=\pgfoo@origattribute% +}% + +\def\pgfoo@emptyinit(){}% +\def\pgfoo@escapeif#1#2\fi{\fi#1}% a little helper + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Compute MRO using C3 algorithm +% The sequences are enclosed in brackets; elements of sequences and +% the big list are terminated by a comma and a dot, respectively. (Why +% comma *and* a dot: TeX's parset---brackets don't form groups! Soooo, +% why not braces? Because I don't know how do delete excess "{},"s...) + +% #1 = current class, #2 = list of base classes +\def\pgfoo@ciii#1#2{% + % \pgfoo@seq will hold a list of sequences. + % (i) the first sequence has just one element: the current class + \def\pgfoo@seq{[#1,].}% + % (ii) every subclass has a sequence (in the order as they were + % given): the class' sequence is its mro. + \pgfutil@for\pgfoo@baseclass:={#2}\do{% + \edef\pgfoo@baseclass@mro{\csname pgfooY\pgfoo@baseclass @pgfoo@mro\endcsname}% + \expandafter\ifx\expandafter\relax\pgfoo@baseclass@mro\relax\else + \edef\pgfoo@seq{\pgfoo@seq[\pgfoo@baseclass@mro,].}% + \fi + }% + % The final sequence is a sequence of base classes, in the order as + % they were given. + \ifx\relax#2\relax\else + \edef\pgfoo@seq{\pgfoo@seq[#2,].}% + \fi + \edef\pgfoo@temp{()(\pgfoo@seq)}% + \expandafter\pgfoo@ciii@merge\pgfoo@temp +}% + +% #1 = MRO so far +% #2 = remaining sequences +% no empty sequences [] should arrive here: they should be removed +% before calling this macro +\def\pgfoo@ciii@merge(#1)(#2){% + \ifx\relax#2\relax + \pgfoo@ciii@done(#1)% + \else\pgfoo@escapeif{% split #2 + \pgfoo@ciii@merge@A(#1)(#2)% + }\fi% +}% +% a splitter: to be only called from \pgfoo@ciii@merge +\def\pgfoo@ciii@merge@A(#1)(#2.#3){% + \ifx\relax#3\relax % if #3 is empty, we can take the shortcut + \pgfoo@ciii@mergeA@done(#1)(#2)% + \else\pgfoo@escapeif{% + \pgfoo@ciii@merge@checkHead(#1)()(#2)()(#3)% + }\fi +}% + +\def\pgfoo@ciii@mergeA@done(#1)([#2]){% + \pgfoo@ciii@done(#1#2)% +}% + +% #1 = MRO so far +% #2 = sequences with bad heads (dot after each sequence) +% [#3,#4] = the sequence whose head we're testing right +% now---obviously, it should not be empty! (#3 has no comma, +% #4 has a comma after each class name) +% #5 = #3 does not occur in the tail of these sequences (dot after +% each sequence) +% #6 = the remaining sequences (dot after each sequence) +\def\pgfoo@ciii@merge@checkHead(#1)(#2)([#3,#4])(#5)(#6){% + \pgfutil@in@{[#3,}{#2}% + \ifpgfutil@in@\pgfoo@escapeif{% #3 is already blacklisted + \pgfoo@ciii@clean\pgfoo@ciii@merge@checkHead@P{(#1)(#2[#3,#4].)(#5#6)}% + }\else\pgfoo@escapeif{% + \ifx\relax#6\relax\pgfoo@escapeif{% no more sequences to test the head against! + % so, #3 is a good head, and we can recurse + \pgfoo@ciii@goodHeadFound(#1)(#3)(#2[#4].#5)% + }\else\pgfoo@escapeif{% split #6 and get to work + \pgfoo@ciii@merge@checkHead@A(#1)(#2)([#3,#4])(#5)(#6)% + }\fi + }\fi +}% +% splitter: to be only called from \pgfoo@ciii@merge@checkHead +% we will check if #3 occurs in the tail of #6 +\def\pgfoo@ciii@merge@checkHead@A(#1)(#2)([#3,#4])(#5)(#6.#7){% + \pgfoo@ciii@merge@checkHead@B(#1)(#2)([#3,#4])(#5)(#6)(#7)% +}% +% splitter: to be only called from \pgfoo@ciii@merge@checkHead@A +% &worker: we will check if #3 occurs in #7. If it does, #3 is a bad head. +\def\pgfoo@ciii@merge@checkHead@B(#1)(#2)([#3,#4])(#5)([#6,#7])(#8){% +% \def\pgfoo@ciii@tempA{#3}\def\pgfoo@ciii@tempB{#5}% +% \ifx\pgfoo@ciii@tempA\pgfoo@ciii@tempB +% \pgferror{An attempt to derive from the same class twice}% +% \else\pgfoo@escapeif{% + \pgfutil@in@{,#3,}{,#7}% + \ifpgfutil@in@\pgfoo@escapeif{% bad head! get next head and restart + % but: need to clean up first! + \pgfoo@ciii@clean\pgfoo@ciii@merge@checkHead@P{(#1)(#2[#3,#4].)(#5[#6,#7].#8)}% + }\else\pgfoo@escapeif{% so far, so good + \ifx\relax#8\relax\pgfoo@escapeif{% if #8 is empty, we won: + % #3 is a good head! we will move it to MRO + % but clean-up first! + \pgfoo@ciii@goodHeadFound(#1)(#3)(#2[#4].#5[#6,#7].)% + }\else\pgfoo@escapeif{% proceed to the first seq in #8 + \pgfoo@ciii@clean\pgfoo@ciii@merge@checkHead{(#1)(#2)([#3,#4])(#5[#6,#7].)(#8)}% + }\fi + }\fi +% }\fi +}% + +% #1 = MRO +% #2 = good head +% #3 = the remaining sequences (in need of removing the good head from +% them, and then also some general cleanup) +\def\pgfoo@ciii@goodHeadFound(#1)(#2)(#3){% + \pgfutil@in@{,#2,}{#3}% + \ifpgfutil@in@\pgfoo@escapeif{% + \def\pgfoo@ciii@removeGoodHead(##1)(##2)(##3,#2,##4){% + \pgfoo@ciii@goodHeadFound(##1)(##2)(##3,##4)}% + \pgfoo@ciii@removeGoodHead(#1)(#2)(#3)% + }\else\pgfoo@escapeif{% + \pgfutil@in@{[#2,}{#3}% + \ifpgfutil@in@\pgfoo@escapeif{% + \def\pgfoo@ciii@removeGoodHead(##1)(##2)(##3[#2,##4){% + \pgfoo@ciii@goodHeadFound(##1)(##2)(##3[##4)}% + \pgfoo@ciii@removeGoodHead(#1)(#2)(#3)% + }\else\pgfoo@escapeif{% + \pgfoo@ciii@clean\pgfoo@ciii@merge{(#1#2,)(#3)}% + }\fi + }\fi +}% + +% mediates between the bad head exit of \pgfoo@ciii@merge@checkHead@B +% and \pgfoo@ciii@merge@checkHead; we need the mediator because #3 +% below might have been affected by cleaning. +\def\pgfoo@ciii@merge@checkHead@P(#1)(#2)(#3){% + \if\relax#3\relax % no more good head candidates! + \pgferror{Bad MRO: There is no good (monotonic etc.) + Method Resolution Order for your class hierarchy}% + \else\pgfoo@escapeif{% + \pgfoo@ciii@merge@checkHead@Q(#1)(#2)(#3)% + }\fi +}% +% splitter for \pgfoo@ciii@merge@checkHead@P +% #3 = the sequence containing the newest good head candidate +\def\pgfoo@ciii@merge@checkHead@Q(#1)(#2)(#3.#4){% + \pgfoo@ciii@merge@checkHead(#1)(#2)(#3)()(#4)% +}% + +% cyclically removes all occurrences of "[]," and "[]." (i.e., empty +% sequences) in #2. When done, calls #1 with whatever remains. +\def\pgfoo@ciii@clean#1#2{% clean commas + \pgfutil@in@{[].}{#2}% clean dots + \ifpgfutil@in@\pgfoo@escapeif{% + \pgfoo@ciii@clean@dot#1#2\pgfoo@ciii@clean@END + }\else\pgfoo@escapeif{% + #1#2% + }\fi +}% +\def\pgfoo@ciii@clean@dot#1#2[].#3\pgfoo@ciii@clean@END{% + \pgfoo@ciii@clean#1{#2#3}% +}% + +\def\pgfoo@ciii@done(#1,){% + \def\pgfoo@mro{#1}% +}% + +% end of C3 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +% Declare a method +% +% Description: +% +% Defines a method. To use/invoke this method for an object \object, +% you write \object.methodname(parameters). This will cause the method +% body to be invoked with the argument "(parameters)". +% +% To define the method \method should be directly followed by the +% method name and, then, by (, followed by a parameter pattern, +% followed by ), followed by the message body. Spaces are allowed only +% after "\method" and after the closing ). +% +% Example: +% +% \pgfooclass{MyPlot}{ +% +% \attribute x=0; +% \attribute y=0; +% +% \method MyPlot() { +% } +% +% \method getX(#1) { +% \pgfooget{x}{#1} +% } +% +% \method setPoint(#1,#2) { +% \pgfooset{x}{#1} +% \pgfooset{y}{#2} +% } +% } + +% define the method macro and append the method to the method collection +\long\def\pgfoo@declaremethod#1(#2)#3{% + \def\pgfoo@method{#1}% + \ifx\pgfoo@classname\pgfoo@method + \def\pgfoo@method{init}% + \fi + \expandafter\long\expandafter\def\csname pgfooY\pgfoo@classname.\pgfoo@method\endcsname(#2){#3}% + \edef\pgfoo@methods{\pgfoo@methods,\pgfoo@method}% +}% + +\def\pgfoo@inherit@methods{% + % for non-derived classes: + \expandafter\ifx\expandafter\relax\pgfoo@temp@baseclasses\relax + % if init is not defined, define an empty one + \expandafter\ifx\csname pgfooY\pgfoo@classname.init\endcsname\relax + \expandafter\let\csname pgfooY\pgfoo@classname.init\endcsname\pgfoo@emptyinit% + \edef\pgfoo@methods{,init\pgfoo@methods}% + \fi + \fi + \if\relax\pgfoo@methods\relax\else + % gobble the initial comma + \edef\pgfoo@methods{\expandafter\pgfutil@gobble\pgfoo@methods}% + \fi + % remember the list of collected methods + \expandafter\let\csname pgfooY\pgfoo@classname @pgfoo@methods\endcsname\pgfoo@methods + % get MRO + \expandafter\let\expandafter\pgfoo@mro\csname pgfooY\pgfoo@classname @pgfoo@mro\endcsname + % loop throough base classes in MRO + \pgfutil@for\pgfoo@baseclass:=\pgfoo@mro\do{% + \ifx\pgfoo@baseclass\pgfoo@classname % skip self + \else\pgfoo@escapeif{% + % get methods defined in this base class + \expandafter\let\expandafter\pgfoo@methods\csname pgfooY\pgfoo@baseclass @pgfoo@methods\endcsname + \pgfutil@for\pgfoo@method:=\pgfoo@methods\do{% for each method + % check if it is already defined in our class + \expandafter\ifx\csname pgfooY\pgfoo@classname.\pgfoo@method\endcsname\relax + % if not, link the method name from our class to base class + \edef\pgfoo@temp{\noexpand\let + \expandafter\noexpand\csname pgfooY\pgfoo@classname.\pgfoo@method\endcsname + \expandafter\noexpand\csname pgfooY\pgfoo@baseclass.\pgfoo@method\endcsname}% + \pgfoo@temp + \fi + }% + }\fi + }% +}% + +\def\pgfoosuper(#1,#2).#3({% + #2.get id(\pgfoo@temp@id)% + \edef\pgfoo@classname{\csname pgfooX\pgfoo@temp@id @class\endcsname}% + \def\pgfoo@super@A##1,#1,##2.##3({% + \pgfoo@super@B##2.##3(% + }% + \expandafter\let\expandafter\pgfoo@mro\csname pgfooY\pgfoo@classname @pgfoo@mro\endcsname + \expandafter\pgfoo@super@A\expandafter,\pgfoo@mro,.#3(% +}% +\def\pgfoo@super@B#1,#2.#3({% + \expandafter\ifx\csname pgfooY#1.#3\endcsname\relax + \pgfoo@escapeif{\pgfoo@super@B#2.#3(}% + \else + \pgfoo@escapeif{\expandafter\pgfoo@caller\pgfoo@temp@id.#1.#3(}% + \fi +}% + + + +% Declare an attribute +% +% #1 = attribute name +% #2 = optional default value +% +% Description: +% +% Declares the attribute #1 for the current class. If the attribute +% name is followed by =, the text following the equal sign is the +% default value. + +\def\pgfoo@declareattribute#1;{% + \pgfutil@in@{ =}{#1}% + \ifpgfutil@in@% + \pgfoo@declareunpackspace#1;% + \else% + \pgfutil@in@={#1}% + \ifpgfutil@in@% + \pgfoo@declareunpack#1;% + \else% + \pgfoo@declareattribute@\let{#1}\pgfutil@empty + \fi% + \fi% +}% +\def\pgfoo@declareunpack#1=#2;{\pgfoo@declareattribute@\def{#1}{{#2}}}% +\def\pgfoo@declareunpackspace#1 =#2;{\pgfoo@declareattribute@\def{#1}{{#2}}}% + +% put the initial value in the class's namespace +% put the attr name in a list +\def\pgfoo@declareattribute@#1#2#3{% + \expandafter#1\csname pgfooY\pgfoo@classname @#2\endcsname#3% + \expandafter\def\expandafter\pgfoo@attributes\expandafter{\pgfoo@attributes,#2}% +}% + +% for each base class: +% for each attribute declared in that class: +% add the attr on a list if it's not there yet +% convert the list of (attr,class which it comes from) into an +% efficient sequence of triples \op{attrname}\pointer-to-initial-value +% (\op is then set differently in init and garbage collection) +\def\pgfoo@inherit@attributes{% + % store the list of attributes declared in this class for use in + % derived classes + \if\relax\pgfoo@attributes\relax\else + \edef\pgfoo@attributes{\expandafter\pgfutil@gobble\pgfoo@attributes}% gobble comma + \fi + \expandafter\let\csname pgfooY\pgfoo@classname.@pgfoo@attributes\endcsname\pgfoo@attributes% + % get MRO + \expandafter\let\expandafter\pgfoo@mro\csname pgfooY\pgfoo@classname @pgfoo@mro\endcsname + \def\pgfoo@allattributes{}% here we will store the attrs declared in base classes + \pgfutil@for\pgfoo@baseclass:=\pgfoo@mro\do{% for each base class in MRO order + % the attributes declared in this base class + \expandafter\let\expandafter\pgfoo@attributes\csname pgfooY\pgfoo@baseclass.@pgfoo@attributes\endcsname + \pgfutil@for\pgfoo@attribute:=\pgfoo@attributes\do{% for each attribute + % check if we have already found it in some previous base class + \edef\pgfoo@temp{{,\pgfoo@attribute=}{\pgfoo@allattributes,}}% + \expandafter\pgfutil@in@\pgfoo@temp + \ifpgfutil@in@\else % don't overwrite + % append to the list + \edef\pgfoo@allattributes{\pgfoo@allattributes,\pgfoo@attribute=\pgfoo@baseclass}% + \fi + }% + }% + \if\relax\pgfoo@allattributes\relax\else + \edef\pgfoo@allattributes{\expandafter\pgfutil@gobble\pgfoo@allattributes}% gobble comma + \fi + % this macro will hold a list of attributes from all base classes + % we run this macro at init and gc + \def\pgfoo@process@attributes{}% + \pgfutil@for\pgfoo@attributeclass:=\pgfoo@allattributes\do{% for each (attribute, class) pair + % append to the attribute processor + \expandafter\pgfoo@inherit@attributes@appendattribute\pgfoo@attributeclass.% + }% + \expandafter\let\csname pgfooY\pgfoo@classname .@pgfoo@process@attributes\endcsname\pgfoo@process@attributes +}% + + +% #1=attr, #2=class which the attr comes from +\def\pgfoo@inherit@attributes@appendattribute#1=#2.{% + \def\pgfoo@tempA{\pgfoo@attribute@op{#1}}% + \expandafter\expandafter\expandafter\def\expandafter\expandafter\expandafter\pgfoo@tempB\expandafter\expandafter\expandafter{\expandafter\pgfoo@tempA\csname pgfooY#2@#1\endcsname}% + \expandafter\expandafter\expandafter\def\expandafter\expandafter\expandafter\pgfoo@process@attributes\expandafter\expandafter\expandafter{\expandafter\pgfoo@process@attributes\pgfoo@tempB}% + % above is just this: + % \def\pgfoo@process@attributes{\pgfoo@process@attributes\pgfoo@attribute@op{#1}% + % \csname pgfooY#2@#1\endcsname} + % with \pgfoo@process@attributes and \csname expanded exactly once: +}% + +% \pgfoo@attribute@op for garbage collection +\def\pgfoo@gc@attribute#1#2{\pgfoolet{#1}\relax}% + + +\newcount\pgfoo@objectcount +\newcount\pgfoothis@count + +% The attribute value method +% +% #1 = attr +% +% This method inserts the current value of the given attribute for the +% current object. + +\def\pgfoovalueof#1{% + \csname pgfooX\the\pgfoothis@count @#1\endcsname% +}% + + +% The attribute get method +% +% #1 = attr +% #2 = macro +% +% This method makes the macro equal to the current value of the +% attribute for the current object. + +\def\pgfooget#1#2{% + \expandafter\let\expandafter#2\csname pgfooX\the\pgfoothis@count @#1\endcsname% +}% + + +% The attribute set method +% +% #1 = attr +% #2 = value +% +% This method sets the given attribute for the current object to the +% given value. + +\long\def\pgfooset#1#2{% + \expandafter\gdef\csname pgfooX\the\pgfoothis@count @#1\endcsname{#2}% +}% + + +% The attribute set method (expanded version) +% +% #1 = attr +% #2 = value +% +% This method sets the given attribute for the current object to the +% expanded version of the given value. + +\long\def\pgfooeset#1#2{% + \expandafter\xdef\csname pgfooX\the\pgfoothis@count @#1\endcsname{#2}% +}% + + +% The attribute let method +% +% #1 = attr +% #2 = value +% +% This method sets the given attribute for the current object to the +% given value using \let. + +\def\pgfoolet#1#2{% + \expandafter\global\expandafter\let\csname pgfooX\the\pgfoothis@count @#1\endcsname#2% +}% + + +% Add something to an attribute at the end +% +% #1 = attr +% #2 = code +% +% This method adds the give code to the attr at the end. + +\def\pgfooappend#1#2{% + \expandafter\expandafter\expandafter\def% + \expandafter\expandafter\expandafter\pgf@oo@temp\expandafter\expandafter\expandafter{\csname pgfooX\the\pgfoothis@count @#1\endcsname#2}% + \expandafter\global\expandafter\let\csname pgfooX\the\pgfoothis@count @#1\endcsname\pgf@oo@temp% +}% + + +% Add something to an attribute at the beginning +% +% #1 = attr +% #2 = code +% +% This method adds the give code to the attr at the beginning. + +\def\pgfooprefix#1#2{% + \pgfooget{#1}\pgf@oo@temp% + \def\pgf@oo@@temp{#2}% + \expandafter\expandafter\expandafter\def% + \expandafter\expandafter\expandafter\pgf@oo@temp% + \expandafter\expandafter\expandafter{\expandafter\pgf@oo@@temp\pgf@oo@temp}% + \pgfoolet{#1}\pgf@oo@temp% +}% + + + +% Help macro for scanning arguments +\newtoks\pgfoo@toks + +\def\pgfoo@collect@args{% + \pgfutil@ifnextchar\bgroup\pgfoo@collect@args@group\pgfoo@collect@args@nogroup% +}% +\def\pgfoo@collect@args@nogroup#1){% + \pgfoo@toks{#1}% + \pgfoo@continue% +}% +\def\pgfoo@collect@args@group#1{% + \pgfutil@ifnextchar){\pgfoo@toks{{#1}}\expandafter\pgfoo@continue\pgfutil@gobble}{\pgfoo@collect@args@nogroup{#1}}% +}% + + +% Instantiate an object +% +% Possible syntax: +% +% 1) \pgfoonew new <class name>(<constructor parameters) +% 2) \pgfoonew \<objectname>=new <class name>(<constructor parameters) +% 3) \pgfoonew{attribute}=new <class name>(<constructor parameters) +% +% Description: +% +% Creates an object. After the object has been created, the method +% called <class name> (the constructor) is invoked. If the +% \<objectname>= part is present, the macro is assigned to the newly +% created object. + +\def\pgfoonew{% + \pgfutil@ifnextchar n{% + \pgfoo@new\pgfoo@dummy=% + }{% + \pgfutil@ifnextchar\bgroup{% + \pgfoo@new@attribute% + }{% + \pgfoo@new% + }% + }% +}% +\def\pgfoo@new#1={% + \def\pgfutil@reserved@a{#1}% + \let\pgfutil@next\pgfoo@@new + \futurelet\pgfutil@let@token\pgfutil@ignorespaces}% +\def\pgfoo@@new new #1({% + \expandafter\ifx\csname pgfooY#1.get id\endcsname\relax% + \pgferror{Unknown class '#1'}% + \else% + \expandafter\pgfoo@new@create\pgfutil@reserved@a{#1}% + {% + \pgfoothis@count\pgfoo@objectcount% + \let\pgfoo@attribute@op\pgfoolet + \csname pgfooY#1.@pgfoo@process@attributes\endcsname% + }% + \let\pgfoo@continue=\pgf@oo@new@cont% + \expandafter\pgfoo@collect@args% + \fi% +}% + +\def\pgf@oo@new@cont{% + \expandafter\pgfoolastobj\expandafter.\expandafter i\expandafter n\expandafter i\expandafter t\expandafter(\the\pgfoo@toks)% + \aftergroup\pgfoogc% cleanup after group +}% + +\def\pgfoo@new@attribute#1={% + \def\pgfutil@reserved@a{#1}% + \let\pgfutil@next\pgfoo@@new@attribute + \futurelet\pgfutil@let@token\pgfutil@ignorespaces}% +\def\pgfoo@@new@attribute new #1({% + \expandafter\ifx\csname pgfooY#1.get id\endcsname\relax% + \pgferror{Unknown class '#1'}% + \else% + \pgfoo@new@create\pgfoo@temp{#1}% + {% + \pgfoothis@count\pgfoo@objectcount% + \let\pgfoo@attribute@op\pgfoolet% + \csname pgfooY#1.@pgfoo@process@attributes\endcsname% + }% + \expandafter\pgfoolet\expandafter{\pgfutil@reserved@a}\pgfoo@temp% + \let\pgfoo@continue=\pgf@oo@new@cont% + \expandafter\pgfoo@collect@args% + \fi% +}% + +\def\pgfoo@new@create#1#2{% + \advance\pgfoo@objectcount by 1\relax% + \edef\pgfoolastobj{\noexpand\pgfoo@caller{\the\pgfoo@objectcount}}% + \expandafter\gdef\csname pgfooX\the\pgfoo@objectcount @class\endcsname{#2}% + \let#1\pgfoolastobj% +}% + +\def\pgfoo@caller#1.#2({% + \expandafter\let\expandafter\pgfoo@caller@temp\csname pgfooY\csname pgfooX#1@class\endcsname.#2\endcsname% + \ifx\pgfoo@caller@temp\relax% + % assume that #2 is of form "superclass.method" + \expandafter\let\expandafter\pgfoo@caller@temp\csname pgfooY#2\endcsname% + \ifx\pgfoo@caller@temp\relax% + \pgferror{Object #1 has no method '#2'}% + \fi% + \fi% + \def\pgf@marshal{% + \pgfoothis@count=#1\relax% + }% + \let\pgfoo@continue\pgfoo@caller@cont% + \pgfoo@collect@args% +}% +\def\pgfoo@caller@cont{% + \edef\pgf@marshal{% + \pgf@marshal% + \noexpand\pgfoo@caller@temp(\the\pgfoo@toks)% + }% + \expandafter\pgf@marshal\expandafter\pgfoothis@count\the\pgfoothis@count\relax% +}% +\let\pgfoo@orig@caller=\pgfoo@caller + +% The special "this" object + +\def\pgfoothis.#1({% + \expandafter\let\expandafter\pgfoo@caller@temp\csname pgfooY\csname pgfooX\the\pgfoothis@count @class\endcsname.#1\endcsname% + \ifx\pgfoo@caller@temp\relax% + % assume that #1 is of form "superclass.method" + \expandafter\let\expandafter\pgfoo@caller@temp\csname pgfooY#1\endcsname% + \fi% + \let\pgfoo@continue\pgfoothis@cont% + \pgfoo@collect@args% +}% +\def\pgfoothis@cont{% + \expandafter\pgfoo@caller@temp\expandafter(\the\pgfoo@toks)% +}% + + + +% Get the object id +% +% #1 = macro to store the id +% +% Description: +% +% This special method allows you to access the object id. You can then +% use \pgfoocall to call a method using this id. This is mainly useful +% when you wish to store the id for a longer time. + +\def\pgfoo@id(#1){% + \edef#1{\the\pgfoothis@count}% +}% + +% Yields the object with the given id +% +% #1 = id +% +% Description: +% +% Given an object id, \pgfooobj{<id>} will yield the object having +% this id. + +\def\pgfooobj#1{% + \pgfoo@caller{#1}% +}% + + +% Get the object +% +% #1 = macro to store the object +% +% Description: +% +% This special method allows you to get a new handle to a given +% object. If \obj is an object, you could normally just say +% \let#1=\obj. However, if \obj happens to be \pgfoothis, then you may +% wish to get a handle to the object itself, not to the special macro +% \pgfoothis. In this case you can say \obj.get handle(#1). + +\def\pgfoo@obj(#1){% + \edef#1{\noexpand\pgfoo@caller{\the\pgfoothis@count}}% +}% + + + +% The garbage collector +% +% Description: +% +% This method frees space occupied by unused objects. Garbage are +% objects that have been destroyed because of the end of the scope in +% which they were created. In this case, however, the memory used by +% this object is not immediately reused because the attributes of the +% object are actually stored in global variables. When the garbage +% collector is called, it will set all these global variables to +% \relax, thereby ensuring that no memory is needed for them. + +\def\pgfoogc{% + {% + % We do this in a group... + \pgfoothis@count\pgfoo@objectcount% this is temporary... + \let\pgfoo@next=\pgfoo@dogc% + \pgfoo@next% + }% +}% +\def\pgfoo@dogc{% + \advance\pgfoothis@count by 1\relax% + \expandafter\ifx\csname pgfooX\the\pgfoothis@count @class\endcsname\relax% + \let\pgfoo@next=\relax% + \else% + % Cleanup this object: + % The following is the fast version of \pgfooobj{\the\pgfoo@objectcount}.@pgfoogc: + \let\pgfoo@attribute@op\pgfoo@gc@attribute + \csname pgfooY\csname pgfooX\the\pgfoothis@count @class\endcsname.@pgfoo@process@attributes\endcsname% + \expandafter\global\expandafter\let\csname pgfooX\the\pgfoothis@count @class\endcsname\relax% + \fi% + \pgfoo@next% +}% + + + +% +% +% Object class +% defines method "copy" +% +% + +\pgfooclass{object}{% + \method copy(#1) {% + % create a new object + \edef\pgfoo@temp@classname{\csname pgfooX\the\pgfoothis@count @class\endcsname}% + \expandafter\pgfoo@new@create\expandafter#1\expandafter{\pgfoo@temp@classname}% + \def\pgfoo@copy@attributes##1##2{% + \pgfooget{##1}\pgfoo@attrvalue + \expandafter\let\csname pgfooX\the\pgfoo@objectcount @##1\endcsname\pgfoo@attrvalue + }% + \let\pgfoo@attribute@op\pgfoo@copy@attributes + \csname pgfooY\pgfoo@temp@classname .@pgfoo@process@attributes\endcsname + \aftergroup\pgfoogc% cleanup after group + }% +}% + + +% +% +% Signal class +% +% + +\pgfooclass{signal} +{% + % + % This class implements signals. + % + % After you have created a signal object, you can call + % connect to connect a slot. Then, whenever the emit method is + % called, all connected methods get called. + + % Attribute + \attribute emitter;% + % Collects the objects that should be called. + + % Constructor + \method signal() {}% + + % Connect a slot (method) #2 of object #1 + \method connect(#1,#2) {% + {% + #1.get id(\pgf@tempid)% + % Save in emitter + \pgfooget{emitter}\pgf@temp% + \let\pgfoo@signal@call=\relax% avoid expansion + \edef\pgf@temp{\pgf@temp\pgfoo@signal@call{\pgf@tempid}{#2}}% + \pgfoolet{emitter}\pgf@temp% + }% + }% + + \def\pgfoo@signal@call#1#2{% + \def\pgf@temp{\pgfooobj{#1}.#2}% + \expandafter\pgf@temp\expandafter(\pgfoo@signal@args)% + }% + + % Emit a signal + \method emit(#1) {% + \def\pgfoo@signal@args{#1}% + \pgfoovalueof{emitter} + }% +}% + + + + +\endinput |