Current issue

Vol.26 No.4

Vol.26 No.4

Volumes

© 1984-2024
British APL Association
All rights reserved.

Archive articles posted online on request: ask the archivist.

archive/25/3

Volume 25, No.3

Generating documentation with ADOC

by Kai Jaeger (kai@aplteam.com)

One of the important advantages of the object-oriented (OO) paradigm is that implementation details are hidden from the user of a class. All one can see is the public interface. There is a problem as well: without having created an instance you cannot see anything of the public interface that is not shared. For creating the instance you might have to look at the documentation. With ADOC this can be achieved effortlessly.

Dyalog’s APL Tools Group recommends the use of ADOC for documenting classes and intends to do so itself as appropriate. Therefore you are likely to find ADOCable information in classes delivered by Dyalog in the future.

Introduction to ADOC

ADOC offers two services:

  • Gather all the built-in information available in any class by definition: fields, properties and methods of both types, shared and instance.
  • Collect information added by the programmer.

It then compiles an HTML file which not only displays all these pieces of information, it also allows you to print it.

Let’s take a look at an example:

:Class Sample_01
:Field Public Instance CRLF←⎕UCS 13 10
∇ r←Version
  :Access Public Shared
  r←'1.0.0' '2011-09-25'
∇
∇ make2(arg1 arg2)
  :Implements Constructor
  :Access Public Instance
∇
∇ r←Hello
  :Access Public Instance
  r←'World'
∇
Prim←{{⍵/⍨2=+⌿0=⍵∘.|⍵}⍳⍵
:EndClass

The script Sample_01 has one shared and two instance methods. It also has a field of type Instance and one private method. If ADOC is installed as a User Command (discussed in a second) then this statement:

]ADOC.Browse Sample_01

shows this in your default browser:

Sample_01

As you can see ADOC has gathered all the information provided by the public interface of the class Sample_01. That is certainly useful but ADOC can do much more than that. Let us introduce a namespace script NS_01:

:Namespace NS_01
∇ r←Hi
  :Access Public Instance
  r←'There'
∇
∇ r←Sum vector
  :Access Public Shared
  r←+/vector
∇
∇ r←a Times b
  r←a×b
∇
:EndNamespace

Note that the namespace script comes with one private method, one shared method and one instance method. Now let’s introduce a class Sample_02 which is a copy of Sample_01 plus one more statement right after the :Class line: it includes the namespace script NS_01:

:Class Sample_02
:Include NS_01
:Field Public Instance CRLF←⎕UCS 13 10
…

Now let’s execute:

      ]ADOC.Browse Sample_02

Note that ADOC has included the two public methods that come from the included namespace:

Sample_02

ADOC has also added a table-of-contents (toc) with links to fields, instance methods and shared methods. ADOC not only deals with included namespaces, it can also handle inheritance. In order to prove that let’s introduce a class Sample_03 which inherits from Sample_02:

:Class Sample_03 : Sample_02
:Include NS_01
:Field Public Instance CRLF←⎕UCS 13 10
∇ r←TheAnswerIs
  :Access Public Instance
  r←42
∇
∇ TidyUp
:Implements Destructor
∇
:EndClass

After executing ]ADOC.Browse Sample_03, this is what the browser shows:

Sample_03

ADOC has added a kind of sub-toc underneath the Instance methods heading, providing links to the three methods. Now this may look a little bit over the top right now, but with more methods these links will prove to be useful.

Note that the header declares that Sample_03 is inheriting from Sample_02. Note also that the method Hello is listed as an instance method, together with the information that it was inherited from Sample_02.

This is all well and good, but to become really useful it needs more information than just the method signatures.

Adding content (documentation)

Information regarding the type and structure of the arguments a method is expected, and what is returned as a result is naturally something that needs to be added by a human being. By following a set of simple rules you can make ADOC insert such pieces of information into the HTML page generated by ADOC.

Adding public comments

Let’s introduce a class Sample_04 which has just one shared method but a couple of paragraphs:

:Class Sample_04
⍝ This is a single-line paragraph.
⍝ This is a paragraphs that spans _
⍝ over two lines in the script.

⍝ This is a third paragraph.
  ∇ r←Hello
  :Access Public Shared
  r←'World'
∇
:EndClass

This is what ADOC makes of this:

Sample_04

As you can see, a blank followed by an underscore at the end of a line is treated by ADOC as glue this line together with the next one. But how does ADOC determine what it should take into the HTML page and what it shouldn’t? To find out we process Sample_05:

:Class Sample_05
∇ r←TheUltimateAnswer
⍝ This method is a homage to Douglas Adams.
  :Access Public Shared
⍝ We don't need millions of years in order to calculate _
⍝ the - somewhat surprising - result.

⍝ As you can see, empty lines don't change a thing.
  r←42
⍝ We simply assign it.
∇
:EndClass

And this is what ADOC is making of that:

Sample_05

Note that the :Access Public line is ignored, and so are any blank lines. All comment lines until the very first APL statement are processed by ADOC. In ADOC terms they are public comments.

Adding Lists

# can be used for marking up numbered lists, and * for marking up bulleted lists:

:Class Sample_06
⍝ We have numbered lists:
⍝ # This is the first topic
⍝ # The second one which in the code spans _
⍝    over two lines
⍝ # And number three.

⍝ And we also have bulleted lists:
⍝ * One topic
⍝ * Just another topic

∇ r←TheUltimateAnswer
⍝ This method is a homage to Douglas Adams.
  :Access Public Shared
  r←42
∇
:EndClass

And this is the result:

Sample_06

Note that the underscore at the end of a list element is treated the same way as it is within paragraphs.

Adding headers

There are headers available as well:

:Class Sample_07
⍝ === Header of level 3
⍝ ==== Header of level 4
⍝ ===== Header of level 5: {{⍵/⍨2=+⌿0=⍵∘.|⍵}⍳⍵}
⍝ ====== Header of level 6 ======
:EndClass

Note that the headers only need to be marked up to their left – see level. Level 1 header shouldn’t be used: they are reserved for the main header of the document as such. All the entries make it into the table of contents:

Sample_07

Last but not least, investigate the level-5 header. Although it is not set in a monospaced font, it is actually a special version of APL385 Unicode that allows us to display APL characters in a header.

Showing APL characters

There are two different ways to show APL characters: you either embed APL code in an ordinary paragraph or you create stand-alone APL code, also called a code block.

Embedded APL characters

In order to embed APL characters within an ordinary paragraph, the APL code needs to be enclosed by two double-quotes:

:Class Sample_08
⍝ == Embedding APL chars
⍝ This is an ordinary paragraph with ""{{⍵/⍨2=+⌿0=⍵∘.|⍵}⍳⍵}"" _
⍝ some APL code embedded in between.
:EndClass

And this is how the result looks like:

Sample_08

A block of APL code

Not surprisingly, the HTML pre tag is used in order to insert a block of APL code:

:Class Sample_09
⍝ The following is a kind of "APL paragraph":
⍝ <pre>
⍝ ⍝ This is an example how not to calculate prime numbers in APL:
⍝ {{⍵/⍨2=+⌿0=⍵∘.|⍵}⍳⍵}
⍝ ⍝ There are better (faster) ways of doing this in APL.
⍝ </pre>
⍝ Note that code marked as <pre> is not wrapped: it is up to _
⍝ you to provide code in reasonably sized chunks.
:EndClass

The result:

Sample_09

Note that code blocks are not wrapped: it is up to the author to take care of reasonably long lines.

Embedding HTML

Within paragraphs you can embed HTML tags into your documentation:

:Class Sample_10
⍝ Note that you can include _
⍝ <i><b>ordinary</b> HTML code</i> _
⍝ into your documentation easily.
:Property Hello
:Access Public Shared
	∇ r←get
	  r←'World'
	∇
:EndProperty
∇ r←Version
  :Access Public Shared
  r←'1.0.0' '2011-09-25'
∇
:EndClass

This has actually the desired effect:

Sample_10

However, this has a drawback if you actually want any of the special HTML chars: <,> or &. In other words if you would like them to appear in your documentation you must include them as HTML entities[1] rather than as the characters themselves. Note that this is not true within a block of APL code.

The number of tags you can make use of is naturally limited because most HTML tags are created by ADOC automatically anyway. That leaves <b> and <i> and <em>.

One speciality needs to be mentioned in this context. Look at this class:

:Class Sample_11
⍝ More information regarding ADOC is available at _
⍝ http://aplwiki.com/ADOC
:Property Hello
:Access Public Shared
	∇ r←get
	  r←'World'
	∇
:EndProperty
:EndClass

It contains an external link. You don’t need to worry about this because this is handled for you:

Sample_11

Note that this is not done by ADOC – it’s actually the browser which is adding the link. Sometimes however you don’t want a particular string to appear as a hyperlink at all. Look at the following example: the text tries to explain what the file:// entry means. In this case you don’t want this string to be a hyperlink because the link would get you nowhere anyway. The only reasonable way to get around this is to specify the two slashes trailing the word file: as &amp;#47;.

:Class Sample_12
⍝ The format can be either file:///localhost/foo.html or, as a _
⍝ shortcut, file:&amp#47;&amp#47;/foo.html
:Property Hello
:Access Public Shared
	∇ r←get
	  r←'World'
	∇
:EndProperty
:EndClass:EndClass

This is the result:

Sample_12

A bunch of classes

If it happens that the complexity of a given project forces you into writing a bunch of classes rather than a single one then most likely we want to generate a document that contains all these classes. You can achieve this by specifying more than one class to the Browse method:

ref←Sample_03 Sample_02 Sample_01 Sample_11
cs←#.ADOC.CreateBrowseDefaults
cs.Caption←'Complex example'
cs #.ADOC.Browse ref

This is the result:

A bunch of classes

As you can see ADOC has compiled a kind of main table-of-contents listing all the classes involved. There is still a problem: when dealing with a bunch of classes having a reference of some sort for every class involved is not enough: in order to get something done you need to know the workflow. For example, quite often you start with an instance of a certain class, and then you add instances of other classes to properties of this main instance.

To create all-singing, all-dancing documentation you need to add information explaining the workflow, which I like to call ‘the big picture’. This can be achieved by adding ordinary functions to the list provided to ADOC via the right argument which contain nothing but comments. If it is just one function at the start of the list that’s fine, but you can add such functions pretty much everywhere.

ADOC considers all of them but the first one as containers: all references after such a function name pointing to classes are going to become children of the function in the hierarchy built up by ADOC. That is reflected in the table-of-contents inserted at the top.

See this example:

l←''
l,←⊂'#.BigPicture'
l,←⊂'#.Workflow'
l,←⊂Sample_09
l,←⊂Sample_04
l,←⊂Sample_02
l,←⊂'#.Container'
l,←⊂Sample_07
l,←⊂Sample_08
l,←⊂Sample_11
cs←#.ADOC.CreateBrowseDefaults
cs.Caption←'The big picture'
cs.withColor←0
cs #.ADOC.Browse l

These are the three functions BigPicture, Workflow and Container:

∇ BigPicture
⍝ Get the idea
⍝ This bunch of classes allow you to ...
∇
∇ Workflow
⍝ The work flow - how to start
⍝ To start create an instance of the class "Presentation".
⍝ You can than add instances of the class "Slide" and _
⍝ add such an instance to the "Slides" property of your _
⍝ instance of the "Presentation" class by calling the _
⍝ "AddSlide" method and passing a ref to an instance of _
⍝ the "Slide" class.
∇
∇ Container
⍝ In-between container
⍝ This is a kind of 'container'; Sample_07, Sample_08 & _
⍝ Sample_11 are all ...
∇

This is the result:

The big picture

Note that the first line of the three functions is converted into a header. Note also that ADOC restricts you to just one level of nesting: a container ‘contains’ all the refs until the next container arrives.

The first function (BigPicture) stands on its own: by definition it never has children. The second one (Workflow) has three children. The remaining scripts then by definition all become children of Container.

ADOC as a User Command

ADOC can be made available as a User Command. There are two options available when calling ADOC.Browse as a User Command:

]ADOC.Browse {refToScript} –caption='My caption'
]ADOC.Browse {refToScript} –browser='C:\Programs\Opera\opera.exe'

Of course they also can be specified together.

In order to define a list of scripts note that they must be comma-separated with no blanks in between:

]ADOC.Browse ADOC,WinFile

This is because a blank is treated as an argument separator.

Conclusion

ADOC is a powerful tool that allows you to create proper documentation on scripts. By following a set of simple rules one can add comments to a script which are extracted and prepared by ADOC appropriately.

The further away documentation is from the actual code the less likely it is to be up-to-date when you look at it, so I strongly recommend keeping it as close to the code as possible. ADOC allows you to do just that.

ADOC is available on the APL Wiki [2].

References

  1. www.w3schools.com/html/html_entities.asp
  2. aplwiki.com/ADOC

 

script began 13:24:09
caching off
debug mode off
cache time 3600 sec
indmtime not found in cache
cached index is fresh
recompiling index.xml
index compiled in 0.1847 secs
read index
read issues/index.xml
identified 26 volumes, 101 issues
array (
  'id' => '10500770',
)
regenerated static HTML
article source is 'XHTML'
completed in 0.2056 secs