aboutsummaryrefslogtreecommitdiff
path: root/doc/buildfile
blob: 633505bb48b0d35c7c572de1e0eee022da838ba6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
# file      : doc/buildfile
# license   : MIT; see accompanying LICENSE file

define css: file
css{*}: extension = css

define xhtml: doc
xhtml{*}: extension = xhtml

./: {man1 xhtml}{b}         \
    css{common pre-box man} \
    file{man-*}

# @@ TODO: why no testscript manual?

./: doc{build2-build-system-manual*}     \
    css{code-box common doc pre-box toc} \
    file{manual.cli doc-* *.html2ps}

./: file{cli.sh}

# The build2 function documentation format for auto-extraction.
#
# Each listed .cxx file is expected to provide functions for one function
# family. In order to plug a new family/file, perform the following steps:
#
# 1. List the corresponding functions-<family>.cxx file stem below.
# 2. Add a section and source the generated .cli file in manual.cli.
#
# The functions-<family>.cxx file is expected to contain one or more comments
# in the following form:
#
# // <synopsis-line>+
# // <blank-line>
# // (<paragraph-line>+|<code-block-line>+
# // <blank-line>)+
#
# That is, the comment starts with one or more synopsis lines followed by a
# blank line followed by a mixture of text paragraphs and/or preformatted code
# blocks separated by blank lines. The comment must be terminated with a blank
# line. See functions-regex.cxx for comprehensive examples.
#
# The synopsis line should be in the form:
#
# // $[<family>.]<name>(...)
#
# Each synopsis line may or may not be be qualified with <family>. The rule is
# as follows: If the function can only be called qualified, then the synopsis
# should contains a single qualified line. If the function can be called
# unqualified, then the synopsis should contains a single unqualified line.
# If some signatures can be called unqualifed while some -- only qualified,
# then there should be both qualified and unqualified lines. Note that there
# can also be functions with different <name>s in a single synopsis block.
#
# The text paragraphs may contain `...` and <...> fragments which are
# translated to \c{} and \ci{}, respectively. Note that these fragments cannot
# span multiple lines.
#
# The preformatted code blocks must be indented four spaces (not counting
# the space after //).
#
# There is problem with distinguishing blanks within a code block and a blank
# that separates the code block from the subsequent paragraph (or another code
# block). Strictly speaking, such a blank should be indented with four spaces
# but trailing spaces at the end of the line are generally frowned upon and in
# our code should be automatically zapped on save.
#
# So what we are going to do is treat a single blank line between two code
# lines as belonging to the code block rather than separating two code
# blocks. The latter can be achieved with a double blank line. Note that this
# means we cannot have double blank lines within a code block.

# @@ TODO: using file{.cli}, change to cli{} once switch to ad hoc recipes.
# @@ TODO: get rid of backlink below once switch to ad hoc recipes.

for ff: functions-builtin        \
        functions-string         \
        functions-integer        \
        functions-bool           \
        functions-path           \
        functions-name           \
        functions-target         \
        functions-regex          \
        functions-process        \
        functions-filesystem     \
        functions-project-name   \
        functions-process-path   \
        functions-target-triplet
{
  alias{functions}: file{$(ff).cli}: $src_root/libbuild2/cxx{$ff}
  file{$(ff).cli}: backlink = true # @@ TMP until migrate to recipe (see cli.sh)
}

file{~'/(functions-.+)\.cli/'}: cxx{~'/\1/'}
{{
  diag doc $< -> $>

  i = $path($<) # Input.
  o = $path($>) # Output.

  # Extract the family name.
  #
  family = $regex.replace($name($<), 'functions-(.+)', '\1')
  family = $regex.replace($family, '-', '_')

  echo "// Auto-extracted from $leaf($i) for \$$(family).*\(\)" >$o

  # The overall plan is as follows: read the file line by line recognizing the
  # function documentation comments and maintaining the parsing state.
  #
  # Parsing state, one of:
  #
  # none -- outside of a documentation comment
  # syno -- inside synopsis
  # para -- inside text
  # code -- inside preformatted code block
  # blnk -- blank line separating synopsis/para/code
  #
  s = none # Current state.
  p = none # Previous state.

  ln = [uint64] 0 # Line number.
  for -n l <=$i
    ln += 1

    # Look for a C++ comments and extract its text.
    #
    t = $regex.match($l, '\s*// ?(.*)', return_subs)

    # Note that when writing the output we use the "leading blank line" rather
    # than trailing approach. That is, we write the blank before starting the
    # next block rather than after.

    if ($t == [null])
      if ($s != 'none')
        if ($s != 'blnk')
          exit "$i:$ln: blank line expected after description"
        end

        # Close delayed code block (see below for details).
        #
        if ($p == 'code')
          echo "\\" >>$o # end code
        end

        echo "\"" >>$o # end cli doc string
      end

      p = $s
      s = 'none'
    else
      # This is a comment. What we do next depends on which state we are in.
      #
      if ($s == 'none' || $s == 'syno')
        p = $s

        # See if this is a synopsys line.
        #
        if $regex.match($t, '\$.+\(.+\)')
          if ($s == 'none')
            synopsis = [strings] # Accumulate synopsis lines.
            s = 'syno'
          end

          synopsis += $t
        elif ($s == 'syno')
          if ($t != '')
            exit "$i:$ln: blank line expected after synopsis"
          end

          echo "$\n\"" >>$o # start cli doc string

          # Write the heading. Use the first function name as id.
          #
          # Note that while the functions in the synopsis could be
          # unqualified, in the heading we always write them qualified. We
          # also have to suppress duplicates since the same function can be
          # mentioned in the synopsis both qualified and unqualified.
          #
          id = [null]
          hs = [strings]
          for t: $synopsis
            t = $regex.replace($t, '\$(.+)\(.+\)', '\1')  # Extract func name.
            f = $regex.match($t, '(.+)\..+', return_subs) # Extract family.

            if ($f == [null])
              t = "$(family).$t" # Qualify.
            elif ($f != $family)
              exit "$i:$ln: function family in $t does not match $family"
            end

            if ($id == [null]) # First.
              id = $regex.replace($t, '\.', '-')
            end

            # Suppress duplicates.
            #
            if! $find($hs, $t)
              hs += $t
            end
          end

          h = $regex.merge($hs, '(.+)', '\\c{$\1()}', ', ')

          echo "\\h2#functions-$id|$h|$\n" >>$o # heading

          echo "\\" >>$o # start synopsis
          for t: $synopsis
            echo $t >>$o # synopsis line
          end
          echo "\\" >>$o # end synopsis

          s = 'blnk'
        end
      else # para|code|blnk
        # See if this is a code line.
        #
	c = $regex.match($t, '    (.+)', return_subs)

        if ($c != [null])
          # Code line.
          #
          if ($s == 'para')
            exit "$i:$ln: blank line expected before code block"
          end

          # Treat a single blank line between two code lines as belonging to
          # the code block rather than separating two code blocks (see above
          # for details).
          #
          if ($s == 'blnk')
            if ($p == 'code')
              echo '' >>$o # continue code, write preceding blank
              s = 'code'
            else
              echo "$\n\\" >>$o # start code
            end
          end

          echo $regex.replace($c, '"', '\\"') >>$o # write code line

          p = $s
          s = 'code'
        elif ($t != '')
          # Paragraph line.
          #
          if ($s == 'code')
            exit "$i:$ln: blank line expected after code block"
          end

	  # Close delayed code block (see above for details).
          #
          if ($p == 'code')
            echo "\\" >>$o # end code
          end

          if ($s == 'blnk')
            echo '' >>$o # start para
          end

          t = $regex.replace($t, '\\', '\\\\') # Escape backslashed
          t = $regex.replace($t, '"', '\\"')   # Escape double quotes.

          # Convert `` to \c{} and <> to \ci{}.
          #
          t = $regex.replace($t, '`([^`]+)`', '\\c{\1}')
          t = $regex.replace($t, '<([^\s<>]+)>', '\\ci{\1}')

          echo $t >>$o # write para line

          p = $s
          s = 'para'
        else
          # Blank line.
          #

          # Note that we delay closing the code block in case this blank line
          # is followed by another code line (and is therefore treated as
          # belonging to the code block; see above for details).
          #
          if ($s != 'code' && $p == 'code')
            echo "\\" >>$o # end code
          end

          #if ($s == 'para')
            # end para
          #end

          p = $s
          s = 'blnk'
        end
      end
    end
  end
}}