1# AutoDoc: Generate documentation from GAP source code 2# 3# Copyright of AutoDoc belongs to its developers. 4# Please refer to the COPYRIGHT file for details. 5# 6# SPDX-License-Identifier: GPL-2.0-or-later 7 8## 9InstallGlobalFunction( Scan_for_AutoDoc_Part, 10 function( line, plain_text_mode ) 11 local position, whitespace_position, command, argument; 12 #! @DONT_SCAN_NEXT_LINE 13 position := PositionSublist( line, "#!" ); 14 if position = fail and plain_text_mode = false then 15 return [ false, line ]; 16 fi; 17 if plain_text_mode <> true then 18 line := StripBeginEnd( line{[ position + 2 .. Length( line ) ]}, " " ); 19 fi; 20 ## Scan for a command 21 position := PositionSublist( line, "@" ); 22 if position = fail then 23 return [ "STRING", line ]; 24 fi; 25 whitespace_position := PositionSublist( line, " " ); 26 if whitespace_position = fail then 27 command := line{[ position .. Length( line ) ]}; 28 argument := ""; 29 else 30 command := line{[ position .. whitespace_position - 1 ]}; 31 argument := line{[ whitespace_position + 1 .. Length( line ) ]}; 32 fi; 33 return [ command, argument ]; 34end ); 35 36## Scans a string for <element> after <element_not_before_element> appeared. 37## This is necessary to scan the filter list for method declarations 38## that contain \[\]. 39BindGlobal( "AUTODOC_PositionElementIfNotAfter", 40 function( list, element, element_not_before_element ) 41 local current_pos; 42 if not IsList( list ) then 43 Error( "<list> must be a list" ); 44 fi; 45 if Length( list ) > 0 and list[ 1 ] = element then 46 return 1; 47 fi; 48 49 for current_pos in [ 2 .. Length( list ) ] do 50 if list[ current_pos ] = element and list[ current_pos - 1 ] <> element_not_before_element then 51 return current_pos; 52 fi; 53 od; 54 return fail; 55end ); 56 57 58BindGlobal( "AutoDoc_PrintWarningForConstructor", 59 AutoDoc_CreatePrintOnceFunction( "Installed GAPDoc version does not support constructors" ) ); 60 61## 62InstallGlobalFunction( AutoDoc_Type_Of_Item, 63 function( current_item, type, default_chapter_data ) 64 local item_rec, entries, has_filters, ret_val; 65 item_rec := current_item; 66 if PositionSublist( type, "DeclareCategoryCollections") <> fail then 67 entries := [ "Filt", "categories" ]; 68 ret_val := "<C>true</C> or <C>false</C>"; 69 has_filters := "No"; 70 if not IsBound( item_rec!.arguments ) then 71 item_rec!.arguments := "obj"; 72 fi; 73 item_rec!.coll_suffix := true; 74 elif PositionSublist( type, "DeclareCategory" ) <> fail then 75 entries := [ "Filt", "categories" ]; 76 ret_val := "<C>true</C> or <C>false</C>"; 77 has_filters := 1; 78 elif PositionSublist( type, "DeclareRepresentation" ) <> fail then 79 entries := [ "Filt", "categories" ]; 80 ret_val := "<C>true</C> or <C>false</C>"; 81 has_filters := 1; 82 elif PositionSublist( type, "DeclareAttribute" ) <> fail then 83 entries := [ "Attr", "attributes" ]; 84 has_filters := 1; 85 elif PositionSublist( type, "DeclareProperty" ) <> fail then 86 entries := [ "Prop", "properties" ]; 87 ret_val := "<C>true</C> or <C>false</C>"; 88 has_filters := 1; 89 elif PositionSublist( type, "DeclareOperation" ) <> fail then 90 entries := [ "Oper", "methods" ]; 91 has_filters := "List"; 92 elif PositionSublist( type, "DeclareConstructor" ) <> fail then 93 if IsPackageMarkedForLoading( "GAPDoc", ">=1.6.1" ) then 94 entries := [ "Constr", "methods" ]; 95 else 96 AutoDoc_PrintWarningForConstructor(); 97 entries := [ "Oper", "methods" ]; 98 fi; 99 has_filters := "List"; 100 elif PositionSublist( type, "DeclareGlobalFunction" ) <> fail then 101 entries := [ "Func", "global_functions" ]; 102 has_filters := "No"; 103 if not IsBound( item_rec!.arguments ) then 104 item_rec!.arguments := "arg"; 105 fi; 106 elif PositionSublist( type, "DeclareGlobalVariable" ) <> fail then 107 entries := [ "Var", "global_variables" ]; 108 has_filters := "No"; 109 item_rec!.arguments := fail; 110 item_rec!.return_value := false; 111 elif PositionSublist( type, "DeclareFilter" ) <> fail then 112 entries := [ "Filt", "properties" ]; 113 has_filters := "No"; 114 item_rec!.arguments := fail; 115 item_rec!.return_value := false; 116 elif PositionSublist( type, "DeclareInfoClass" ) <> fail then 117 entries := [ "InfoClass", "info_classes" ]; 118 has_filters := "No"; 119 item_rec!.arguments := fail; 120 item_rec!.return_value := false; 121 elif PositionSublist( type, "KeyDependentOperation" ) <> fail then 122 entries := [ "Oper", "methods" ]; 123 has_filters := 2; 124 else 125 return fail; 126 fi; 127 item_rec!.item_type := entries[ 1 ]; 128 if not IsBound( item_rec!.chapter_info ) or item_rec!.chapter_info = [ ] then 129 item_rec!.chapter_info := default_chapter_data.( entries[ 2 ] ); 130 fi; 131 if IsBound( ret_val ) and ( item_rec!.return_value = [ ] or item_rec!.return_value = false ) then 132 item_rec!.return_value := [ ret_val ]; 133 fi; 134 return has_filters; 135end ); 136 137## 138InstallGlobalFunction( AutoDoc_Parser_ReadFiles, 139 function( filename_list, tree, default_chapter_data ) 140 local current_item, flush_and_recover, chapter_info, current_string_list, 141 Scan_for_Declaration_part, flush_and_prepare_for_item, current_line, filestream, 142 level_scope, scope_group, read_example, command_function_record, autodoc_read_line, 143 current_command, was_declaration, filename, system_scope, groupnumber, rest_of_file_skipped, 144 context_stack, new_man_item, add_man_item, Reset, read_code, title_item, title_item_list, plain_text_mode, 145 current_line_unedited, deprecated, 146 ReadLineWithLineCount, Normalized_ReadLine, line_number, ErrorWithPos, create_title_item_function, 147 current_line_positition_for_filter, read_session_example; 148 groupnumber := 0; 149 level_scope := 0; 150 autodoc_read_line := false; 151 context_stack := [ ]; 152 chapter_info := [ ]; 153 line_number := 0; 154 155 ReadLineWithLineCount := function( stream ) 156 line_number := line_number + 1; 157 return ReadLine( stream ); 158 end; 159 Normalized_ReadLine := function( stream ) 160 local string; 161 string := ReadLineWithLineCount( stream ); 162 if string = fail then 163 return fail; 164 fi; 165 NormalizeWhitespace( string ); 166 return string; 167 end; 168 ErrorWithPos := function(arg) 169 local list; 170 list := Concatenation(arg, [ ",\n", "at ", filename, ":", line_number]); 171 CallFuncList(Error, list); 172 end; 173 new_man_item := function( ) 174 local man_item; 175 if IsBound( current_item ) and IsTreeForDocumentationNodeForManItemRep( current_item ) then 176 return current_item; 177 fi; 178 179 # implicitly end any subsection 180 if IsBound( chapter_info[ 3 ] ) then 181 Unbind( chapter_info[ 3 ] ); 182 current_item := SectionInTree( tree, chapter_info[ 1 ], chapter_info[ 2 ] ); 183 fi; 184 185 if IsBound( current_item ) then 186 Add( context_stack, current_item ); 187 fi; 188 189 man_item := DocumentationManItem( tree ); 190 if IsBound( scope_group ) then 191 SetGroupName( man_item, scope_group ); 192 fi; 193 man_item!.chapter_info := ShallowCopy( chapter_info ); 194 man_item!.tester_names := fail; 195 return man_item; 196 end; 197 add_man_item := function( ) 198 local man_item; 199 man_item := current_item; 200 if context_stack <> [ ] then 201 current_item := Remove( context_stack ); 202 else 203 Unbind( current_item ); 204 fi; 205 if IsBound( man_item!.chapter_info ) then 206 SetChapterInfo( man_item, man_item!.chapter_info ); 207 fi; 208 if Length( ChapterInfo( man_item ) ) <> 2 then 209 ErrorWithPos( "declarations must be documented within a section" ); 210 fi; 211 Add( tree, man_item ); 212 end; 213 Reset := function( ) 214 chapter_info := [ ]; 215 context_stack := [ ]; 216 Unbind( current_item ); 217 plain_text_mode := false; 218 end; 219 Scan_for_Declaration_part := function() 220 local declare_position, current_type, filter_string, has_filters, 221 position_parenthesis, nr_of_attr_loops, i; 222 223 ## fail is bigger than every integer 224 declare_position := Minimum( [ PositionSublist( current_line, "Declare" ), PositionSublist( current_line, "KeyDependentOperation" ) ] ); 225 if declare_position <> fail then 226 current_item := new_man_item(); 227 current_line := current_line{[ declare_position .. Length( current_line ) ]}; 228 position_parenthesis := PositionSublist( current_line, "(" ); 229 if position_parenthesis = fail then 230 ErrorWithPos( "Something went wrong" ); 231 fi; 232 current_type := current_line{ [ 1 .. position_parenthesis - 1 ] }; 233 has_filters := AutoDoc_Type_Of_Item( current_item, current_type, default_chapter_data ); 234 if has_filters = fail then 235 ErrorWithPos( "Unrecognized scan type" ); 236 return false; 237 fi; 238 current_line := current_line{ [ position_parenthesis + 1 .. Length( current_line ) ] }; 239 ## Now the funny part begins: 240 ## try fetching the name: 241 ## Assuming the name is in the same line as its 242 while PositionSublist( current_line, "," ) = fail and PositionSublist( current_line, ");" ) = fail do 243 current_line := Normalized_ReadLine( filestream ); 244 od; 245 current_line := StripBeginEnd( current_line, " " ); 246 current_item!.name := current_line{ [ 1 .. Minimum( [ PositionSublist( current_line, "," ), PositionSublist( current_line, ");" ) ] ) - 1 ] }; 247 current_item!.name := StripBeginEnd( ReplacedString( current_item!.name, "\"", "" ), " " ); 248 249 # Deal with DeclareCategoryCollections: this has some special 250 # rules on how the name of a new category is derived from the 251 # string given to it. Since the code for that is not available in 252 # a separate GAP function, we have to replicate this logic here. 253 # To understand what's going on, please refer to the 254 # DeclareCategoryCollections documentation and implementation. 255 if IsBound(current_item!.coll_suffix) then 256 if EndsWith(current_item!.name, "Collection") then 257 current_item!.name := 258 current_item!.name{[1..Length(current_item!.name)-6]}; 259 fi; 260 if EndsWith(current_item!.name, "Coll") then 261 current_item!.coll_suffix := "Coll"; 262 else 263 current_item!.coll_suffix := "Collection"; 264 fi; 265 current_item!.name := Concatenation(current_item!.name, 266 current_item!.coll_suffix); 267 fi; 268 269 current_line := current_line{ [ Minimum( [ PositionSublist( current_line, "," ), PositionSublist( current_line, ");" ) ] ) + 1 .. Length( current_line ) ] }; 270 filter_string := "for "; 271 ## FIXME: The next two if's can be merged at some point 272 if IsInt( has_filters ) then 273 for i in [ 1 .. has_filters ] do 274 ## We now search for the filters. A filter is either followed by a ',', if there is more than one, 275 ## or by ');' if it is the only or last one. So we search for the next delimiter. 276 while PositionSublist( current_line, "," ) = fail and PositionSublist( current_line, ");" ) = fail do 277 Append( filter_string, StripBeginEnd( current_line, " " ) ); 278 current_line := ReadLineWithLineCount( filestream ); 279 NormalizeWhitespace( current_line ); 280 od; 281 current_line_positition_for_filter := Minimum( [ PositionSublist( current_line, "," ), PositionSublist( current_line, ");" ) ] ) - 1; 282 Append( filter_string, StripBeginEnd( current_line{ [ 1 .. current_line_positition_for_filter ] }, " " ) ); 283 current_line := current_line{[ current_line_positition_for_filter + 1 .. Length( current_line ) ]}; 284 if current_line[ 1 ] = ',' then 285 current_line := current_line{[ 2 .. Length( current_line ) ]}; 286 elif current_line[ 1 ] = ')' then 287 current_line := current_line{[ 3 .. Length( current_line ) ]}; 288 fi; 289 ## FIXME: Refactor this whole if IsInt( has_filters ) case! 290 if has_filters - i > 0 then 291 Append( filter_string, ", " ); 292 fi; 293 od; 294 elif has_filters = "List" then 295 while AUTODOC_PositionElementIfNotAfter( current_line, '[', '\\' ) = fail do 296 current_line := ReadLineWithLineCount( filestream ); 297 NormalizeWhitespace( current_line ); 298 od; 299 current_line := current_line{ [ AUTODOC_PositionElementIfNotAfter( current_line, '[', '\\' ) + 1 .. Length( current_line ) ] }; 300 while AUTODOC_PositionElementIfNotAfter( current_line, ']', '\\' ) = fail do 301 Append( filter_string, StripBeginEnd( current_line, " " ) ); 302 current_line := ReadLineWithLineCount( filestream ); 303 NormalizeWhitespace( current_line ); 304 od; 305 Append( filter_string, StripBeginEnd( current_line{[ 1 .. AUTODOC_PositionElementIfNotAfter( current_line, ']', '\\' ) - 1 ]}, " " ) ); 306 else 307 filter_string := false; 308 fi; 309 if IsString( filter_string ) then 310 filter_string := ReplacedString( filter_string, "\"", "" ); 311 fi; 312 if filter_string <> false then 313 if current_item!.tester_names = fail and StripBeginEnd( filter_string, " " ) <> "for" then 314 current_item!.tester_names := filter_string; 315 fi; 316 if StripBeginEnd( filter_string, " " ) = "for" then 317 has_filters := "empty_argument_list"; 318 fi; 319 ##Adjust arguments 320 if not IsBound( current_item!.arguments ) then 321 if IsInt( has_filters ) then 322 if has_filters = 1 then 323 current_item!.arguments := "arg"; 324 else 325 current_item!.arguments := JoinStringsWithSeparator( List( [ 1 .. has_filters ], i -> Concatenation( "arg", String( i ) ) ), "," ); 326 fi; 327 elif has_filters = "List" then 328 current_item!.arguments := List( [ 1 .. Length( SplitString( filter_string, "," ) ) ], i -> Concatenation( "arg", String( i ) ) ); 329 if Length( current_item!.arguments ) = 1 then 330 current_item!.arguments := "arg"; 331 else 332 current_item!.arguments := JoinStringsWithSeparator( current_item!.arguments, "," ); 333 fi; 334 elif has_filters = "empty_argument_list" then 335 current_item!.arguments := ""; 336 fi; 337 fi; 338 fi; 339 add_man_item(); 340 return true; 341 fi; 342 declare_position := Minimum( [ PositionSublist( current_line, "InstallMethod" ), PositionSublist( current_line, "InstallOtherMethod" ) ] ); 343 ## Fail is larger than every integer. 344 if declare_position <> fail then 345 current_item := new_man_item(); 346 current_item!.item_type := "Oper"; 347 ##Find name 348 position_parenthesis := PositionSublist( current_line, "(" ); 349 current_line := current_line{ [ position_parenthesis + 1 .. Length( current_line ) ] }; 350 ## find next colon 351 current_item!.name := ""; 352 while PositionSublist( current_line, "," ) = fail do 353 Append( current_item!.name, current_line ); 354 current_line := Normalized_ReadLine( filestream ); 355 od; 356 position_parenthesis := PositionSublist( current_line, "," ); 357 Append( current_item!.name, current_line{[ 1 .. position_parenthesis - 1 ]} ); 358 NormalizeWhitespace( current_item!.name ); 359 current_item!.name := StripBeginEnd( current_item!.name, " " ); 360 while AUTODOC_PositionElementIfNotAfter( current_line, '[', '\\' ) = fail do 361 current_line := Normalized_ReadLine( filestream ); 362 od; 363 position_parenthesis := AUTODOC_PositionElementIfNotAfter( current_line, '[', '\\' ); 364 current_line := current_line{[ position_parenthesis + 1 .. Length( current_line ) ]}; 365 filter_string := "for "; 366 while PositionSublist( current_line, "]" ) = fail do 367 Append( filter_string, current_line ); 368 od; 369 position_parenthesis := AUTODOC_PositionElementIfNotAfter( current_line, ']', '\\' ); 370 Append( filter_string, current_line{[ 1 .. position_parenthesis - 1 ]} ); 371 current_line := current_line{[ position_parenthesis + 1 .. Length( current_line )]}; 372 NormalizeWhitespace( filter_string ); 373 if IsString( filter_string ) then 374 filter_string := ReplacedString( filter_string, "\"", "" ); 375 fi; 376 if current_item!.tester_names = fail then 377 current_item!.tester_names := filter_string; 378 fi; 379 ##Maybe find some argument names 380 if not IsBound( current_item!.arguments ) then 381 while PositionSublist( current_line, "function(" ) = fail and PositionSublist( current_line, ");" ) = fail do 382 current_line := Normalized_ReadLine( filestream ); 383 od; 384 position_parenthesis := PositionSublist( current_line, "function(" ); 385 if position_parenthesis <> fail then 386 current_line := current_line{[ position_parenthesis + 9 .. Length( current_line ) ]}; 387 filter_string := ""; 388 while PositionSublist( current_line, ")" ) = fail do; 389 current_line := StripBeginEnd( current_line, " " ); 390 Append( filter_string, current_line ); 391 current_line := Normalized_ReadLine( current_line ); 392 od; 393 position_parenthesis := PositionSublist( current_line, ")" ); 394 Append( filter_string, current_line{[ 1 .. position_parenthesis - 1 ]} ); 395 NormalizeWhitespace( filter_string ); 396 filter_string := StripBeginEnd( filter_string, " " ); 397 current_item!.arguments := filter_string; 398 fi; 399 fi; 400 if not IsBound( current_item!.arguments ) then 401 current_item!.arguments := Length( SplitString( current_item!.tester_names, "," ) ); 402 current_item!.arguments := JoinStringsWithSeparator( List( [ 1 .. current_item!.arguments ], i -> Concatenation( "arg", String( i ) ) ), "," ); 403 fi; 404 add_man_item(); 405 return true; 406 fi; 407 return false; 408 end; 409 read_code := function( ) 410 local code, temp_curr_line, comment_pos, before_comment; 411 code := [ "<Listing Type=\"Code\"><![CDATA[\n" ]; 412 while true do 413 temp_curr_line := ReadLineWithLineCount( filestream ); 414 if temp_curr_line[ Length( temp_curr_line )] = '\n' then 415 temp_curr_line := temp_curr_line{[ 1 .. Length( temp_curr_line ) - 1 ]}; 416 fi; 417 if plain_text_mode = false then 418 comment_pos := PositionSublist( temp_curr_line, "#!" ); 419 if comment_pos <> fail then 420 before_comment := NormalizedWhitespace( temp_curr_line{ [ 1 .. comment_pos - 1 ] } ); 421 if before_comment = "" then 422 temp_curr_line := temp_curr_line{[ comment_pos + 2 .. Length( temp_curr_line ) ]}; 423 fi; 424 fi; 425 fi; 426 if filestream = fail or PositionSublist( temp_curr_line, "@EndCode" ) <> fail then 427 break; 428 fi; 429 Add( code, temp_curr_line ); 430 od; 431 Add( code, "]]></Listing>\n" ); 432 return code; 433 end; 434 read_example := function( is_tested_example ) 435 local temp_string_list, temp_curr_line, temp_pos_comment, is_following_line, item_temp, example_node; 436 example_node := DocumentationExample( tree ); 437 example_node!.is_tested_example := is_tested_example; 438 temp_string_list := example_node!.content; 439 is_following_line := false; 440 while true do 441 temp_curr_line := ReadLineWithLineCount( filestream ); 442 if temp_curr_line[ Length( temp_curr_line )] = '\n' then 443 temp_curr_line := temp_curr_line{[ 1 .. Length( temp_curr_line ) - 1 ]}; 444 fi; 445 if filestream = fail or PositionSublist( temp_curr_line, "@EndExample" ) <> fail 446 or PositionSublist( temp_curr_line, "@EndLog" ) <> fail then 447 break; 448 fi; 449 ##if is comment, simply remove comments. 450 #! @DONT_SCAN_NEXT_LINE 451 temp_pos_comment := PositionSublist( temp_curr_line, "#!" ); 452 if temp_pos_comment <> fail then 453 temp_curr_line := temp_curr_line{[ temp_pos_comment + 3 .. Length( temp_curr_line ) ]}; 454 Add( temp_string_list, temp_curr_line ); 455 is_following_line := false; 456 continue; 457 else 458 if is_following_line then 459 temp_curr_line := Concatenation( "> ", temp_curr_line ); 460 if PositionSublist( temp_curr_line, ";" ) <> fail then 461 is_following_line := false; 462 fi; 463 else 464 if temp_curr_line = "" then 465 continue; 466 fi; 467 temp_curr_line := Concatenation( "gap> ", temp_curr_line ); 468 is_following_line := PositionSublist( temp_curr_line, ";" ) = fail; 469 fi; 470 Add( temp_string_list, temp_curr_line ); 471 continue; 472 fi; 473 od; 474 return example_node; 475 end; 476 read_session_example := function( is_tested_example, plain_text_mode ) 477 local temp_string_list, temp_curr_line, temp_pos_comment, 478 is_following_line, item_temp, example_node, 479 incorporate_this_line; 480 example_node := DocumentationExample( tree ); 481 example_node!.is_tested_example := is_tested_example; 482 temp_string_list := example_node!.content; 483 while true do 484 temp_curr_line := ReadLineWithLineCount( filestream ); 485 if temp_curr_line[ Length( temp_curr_line )] = '\n' then 486 temp_curr_line := temp_curr_line{[ 1 .. Length( temp_curr_line ) - 1 ]}; 487 fi; 488 if filestream = fail or PositionSublist( temp_curr_line, "@EndExampleSession" ) <> fail 489 or PositionSublist( temp_curr_line, "@EndLogSession" ) <> fail then 490 break; 491 fi; 492 incorporate_this_line := plain_text_mode; 493 if not plain_text_mode then 494 #! @DONT_SCAN_NEXT_LINE 495 temp_pos_comment := PositionSublist( temp_curr_line, "#!" ); 496 if temp_pos_comment <> fail then 497 incorporate_this_line := true; 498 temp_curr_line := temp_curr_line{[ temp_pos_comment + 2 .. Length( temp_curr_line ) ]}; 499 if Length( temp_curr_line ) >= 1 and temp_curr_line[ 1 ] = ' ' then 500 Remove( temp_curr_line, 1 ); 501 fi; 502 fi; 503 fi; 504 if incorporate_this_line then 505 Add( temp_string_list, temp_curr_line ); 506 fi; 507 od; 508 return example_node; 509 end; 510 deprecated := function(name, f) 511 return function(args...) 512 Info(InfoWarning, 1, TextAttr.1, "WARNING: ----------------------------------------------------------------------------", TextAttr.reset); 513 Info(InfoWarning, 1, TextAttr.1, "WARNING: ", name, " is deprecated; please refer to the AutoDoc manual for details", TextAttr.reset); 514 Info(InfoWarning, 1, TextAttr.1, "WARNING: ----------------------------------------------------------------------------", TextAttr.reset); 515 f(); 516 end; 517 end; 518 command_function_record := rec( 519 ## HACK: Needed for AutoDoc parser to be scanned savely. 520 ## The lines where the AutoDoc comments are 521 ## searched cause problems otherwise. 522 @DONT_SCAN_NEXT_LINE := function() 523 ReadLineWithLineCount( filestream ); 524 end, 525 @DoNotReadRestOfFile := function() 526 Reset(); 527 rest_of_file_skipped := true; 528 end, 529 @BeginAutoDoc := deprecated("@BeginAutoDoc", function() 530 autodoc_read_line := fail; 531 end), 532 @AutoDoc := ~.@BeginAutoDoc, 533 @EndAutoDoc := deprecated("@EndAutoDoc", function() 534 autodoc_read_line := false; 535 end), 536 537 @Chapter := function() 538 local scope_chapter; 539 scope_chapter := ReplacedString( current_command[ 2 ], " ", "_" ); 540 current_item := ChapterInTree( tree, scope_chapter ); 541 chapter_info[ 1 ] := scope_chapter; 542 end, 543 @ChapterLabel := function() 544 local scope_chapter, label_name; 545 if not IsBound( chapter_info[ 1 ] ) then 546 ErrorWithPos( "found @ChapterLabel with no active chapter" ); 547 fi; 548 label_name := ReplacedString( current_command[ 2 ], " ", "_" ); 549 scope_chapter := ChapterInTree( tree, chapter_info[ 1 ] ); 550 scope_chapter!.additional_label := Concatenation( "Chapter_", label_name ); 551 end, 552 @ChapterTitle := function() 553 local scope_chapter; 554 if not IsBound( chapter_info[ 1 ] ) then 555 ErrorWithPos( "found @ChapterTitle with no active chapter" ); 556 fi; 557 scope_chapter := ChapterInTree( tree, chapter_info[ 1 ] ); 558 scope_chapter!.title_string := current_command[ 2 ]; 559 end, 560 561 @Section := function() 562 local scope_section; 563 if not IsBound( chapter_info[ 1 ] ) then 564 ErrorWithPos( "found @Section with no active chapter" ); 565 fi; 566 scope_section := ReplacedString( current_command[ 2 ], " ", "_" ); 567 current_item := SectionInTree( tree, chapter_info[ 1 ], scope_section ); 568 Unbind( chapter_info[ 3 ] ); 569 chapter_info[ 2 ] := scope_section; 570 end, 571 @SectionLabel := function() 572 local scope_section, label_name; 573 if not IsBound( chapter_info[ 2 ] ) then 574 ErrorWithPos( "found @SectionLabel with no active section" ); 575 fi; 576 label_name := ReplacedString( current_command[ 2 ], " ", "_" ); 577 scope_section := SectionInTree( tree, chapter_info[ 1 ], chapter_info[ 2 ] ); 578 scope_section!.additional_label := Concatenation( "Section_", label_name ); 579 end, 580 @SectionTitle := function() 581 local scope_section; 582 if not IsBound( chapter_info[ 2 ] ) then 583 ErrorWithPos( "found @SectionTitle with no active section" ); 584 fi; 585 scope_section := SectionInTree( tree, chapter_info[ 1 ], chapter_info[ 2 ] ); 586 scope_section!.title_string := current_command[ 2 ]; 587 end, 588 @EndSection := deprecated("@EndSection", function() 589 if not IsBound( chapter_info[ 2 ] ) then 590 ErrorWithPos( "found @EndSection with no active section" ); 591 fi; 592 Unbind( chapter_info[ 2 ] ); 593 Unbind( chapter_info[ 3 ] ); 594 current_item := ChapterInTree( tree, chapter_info[ 1 ] ); 595 end), 596 597 @Subsection := function() 598 local scope_subsection; 599 if not IsBound( chapter_info[ 1 ] ) or not IsBound( chapter_info[ 2 ] ) then 600 ErrorWithPos( "found @Subsection with no active section" ); 601 fi; 602 scope_subsection := ReplacedString( current_command[ 2 ], " ", "_" ); 603 current_item := SubsectionInTree( tree, chapter_info[ 1 ], chapter_info[ 2 ], scope_subsection ); 604 chapter_info[ 3 ] := scope_subsection; 605 end, 606 @SubsectionLabel := function() 607 local scope_subsection, label_name; 608 if not IsBound( chapter_info[ 3 ] ) then 609 ErrorWithPos( "found @SubsectionLabel with no active subsection" ); 610 fi; 611 label_name := ReplacedString( current_command[ 2 ], " ", "_" ); 612 scope_subsection := SubsectionInTree( tree, chapter_info[ 1 ], chapter_info[ 2 ], chapter_info[ 3 ] ); 613 scope_subsection!.additional_label := Concatenation( "Subsection_", label_name ); 614 end, 615 @SubsectionTitle := function() 616 local scope_subsection; 617 if not IsBound( chapter_info[ 3 ] ) then 618 ErrorWithPos( "found @SubsectionTitle with no active subsection" ); 619 fi; 620 scope_subsection := SubsectionInTree( tree, chapter_info[ 1 ], chapter_info[ 2 ], chapter_info[ 3 ] ); 621 scope_subsection!.title_string := current_command[ 2 ]; 622 end, 623 @EndSubsection := deprecated("@EndSubsection", function() 624 if not IsBound( chapter_info[ 3 ] ) then 625 ErrorWithPos( "found @EndSubsection with no active subsection" ); 626 fi; 627 Unbind( chapter_info[ 3 ] ); 628 current_item := SectionInTree( tree, chapter_info[ 1 ], chapter_info[ 2 ] ); 629 end), 630 631 @BeginGroup := function() 632 local grp; 633 if current_command[ 2 ] = "" then 634 groupnumber := groupnumber + 1; 635 current_command[ 2 ] := Concatenation( "AutoDoc_generated_group", String( groupnumber ) ); 636 fi; 637 scope_group := ReplacedString( current_command[ 2 ], " ", "_" ); 638 end, 639 @EndGroup := function() 640 Unbind( scope_group ); 641 end, 642 @Description := function() 643 current_item := new_man_item(); 644 SetManItemToDescription( current_item ); 645 NormalizeWhitespace( current_command[ 2 ] ); 646 if current_command[ 2 ] <> "" then 647 Add( current_item, current_command[ 2 ] ); 648 fi; 649 end, 650 @Returns := function() 651 current_item := new_man_item(); 652 SetManItemToReturnValue( current_item ); 653 if current_command[ 2 ] <> "" then 654 Add( current_item, current_command[ 2 ] ); 655 fi; 656 end, 657 @Arguments := function() 658 current_item := new_man_item(); 659 current_item!.arguments := current_command[ 2 ]; 660 end, 661 @Label := function() 662 current_item := new_man_item(); 663 current_item!.tester_names := current_command[ 2 ]; 664 end, 665 @Group := function() 666 local group_name; 667 current_item := new_man_item(); 668 group_name := ReplacedString( current_command[ 2 ], " ", "_" ); 669 SetGroupName( current_item, group_name ); 670 end, 671 @GroupTitle := function() 672 local group_name, chap_info, group_obj; 673 current_item := new_man_item(); 674 if not HasGroupName( current_item ) then 675 ErrorWithPos( "found @GroupTitle with no Group set" ); 676 fi; 677 group_name := GroupName( current_item ); 678 chap_info := fail; 679 if HasChapterInfo( current_item ) then 680 chap_info := ChapterInfo( current_item ); 681 elif IsBound( current_item!.chapter_info ) then 682 chap_info := current_item!.chapter_info; 683 fi; 684 if chap_info = fail or Length( chap_info ) = 0 then 685 chap_info := chapter_info; 686 fi; 687 if Length( chap_info ) <> 2 then 688 ErrorWithPos( "can only set @GroupTitle within a Chapter and Section."); 689 fi; 690 group_obj := DocumentationGroup( tree, group_name, chap_info ); 691 group_obj!.title_string := current_command[ 2 ]; 692 end, 693 @ChapterInfo := function() 694 local current_chapter_info; 695 current_item := new_man_item(); 696 current_chapter_info := SplitString( current_command[ 2 ], "," ); 697 current_chapter_info := List( current_chapter_info, i -> ReplacedString( StripBeginEnd( i, " " ), " ", "_" ) ); 698 SetChapterInfo( current_item, current_chapter_info ); 699 end, 700 @BREAK := function() 701 ErrorWithPos( current_command[ 2 ] ); 702 end, 703 @SetLevel := function() 704 level_scope := Int( current_command[ 2 ] ); 705 end, 706 @ResetLevel := function() 707 level_scope := 0; 708 end, 709 @Level := function() 710 current_item!.level := Int( current_command[ 2 ] ); 711 end, 712 713 @InsertChunk := function() 714 local label_name; 715 label_name := ReplacedString( current_command[ 2 ], " ", "_" ); 716 Add( current_item, DocumentationChunk( tree, label_name ) ); 717 end, 718 @BeginChunk := function() 719 local label_name; 720 if IsBound( current_item ) then 721 Add( context_stack, current_item ); 722 fi; 723 label_name := ReplacedString( current_command[ 2 ], " ", "_" ); 724 current_item := DocumentationChunk( tree, label_name ); 725 end, 726 @Chunk := ~.@BeginChunk, 727 @EndChunk := function() 728 if autodoc_read_line = true then 729 autodoc_read_line := false; 730 fi; 731 if context_stack <> [ ] then 732 current_item := Remove( context_stack ); 733 else 734 Unbind( current_item ); 735 fi; 736 end, 737 738 @InsertSystem := deprecated("@InsertSystem", ~.@InsertChunk), 739 @System := deprecated("@System", ~.@BeginChunk), 740 @BeginSystem := ~.@System, 741 @EndSystem := deprecated("@EndSystem", ~.@EndChunk), 742 743 @BeginCode := function() 744 local label_name, tmp_system; 745 label_name := ReplacedString( current_command[ 2 ], " ", "_" ); 746 tmp_system := DocumentationChunk( tree, label_name ); 747 Append( tmp_system!.content, read_code() ); 748 end, 749 @Code := ~.@BeginCode, 750 @InsertCode := ~.@InsertChunk, 751 752 @BeginExample := function() 753 local example_node; 754 example_node := read_example( true ); 755 Add( current_item, example_node ); 756 end, 757 758 @Example := ~.@BeginExample, 759 @BeginLog := function() 760 local example_node; 761 example_node := read_example( false ); 762 Add( current_item, example_node ); 763 end, 764 @Log := ~.@BeginLog, 765 766 STRING := function() 767 local comment_pos; 768 if not IsBound( current_item ) then 769 return; 770 fi; 771 comment_pos := PositionSublist( current_line_unedited, "#!" ); 772 if comment_pos <> fail then 773 current_line_unedited := current_line_unedited{[ comment_pos + 2 .. Length( current_line_unedited ) ]}; 774 fi; 775 Add( current_item, current_line_unedited ); 776 end, 777 @BeginLatexOnly := function() 778 Add( current_item, "<Alt Only=\"LaTeX\"><![CDATA[" ); 779 if current_command[ 2 ] <> "" then 780 Add( current_item, current_command[ 2 ] ); 781 fi; 782 end, 783 @EndLatexOnly := function() 784 if autodoc_read_line = true then 785 autodoc_read_line := false; 786 fi; 787 Add( current_item, "]]></Alt>" ); 788 end, 789 @LatexOnly := function() 790 Add( current_item, "<Alt Only=\"LaTeX\"><![CDATA[" ); 791 Add( current_item, current_command[ 2 ] ); 792 Add( current_item, "]]></Alt>" ); 793 end, 794 @BeginNotLatex := function() 795 Add( current_item, "<Alt Not=\"LaTeX\"><![CDATA[" ); 796 if current_command[ 2 ] <> "" then 797 Add( current_item, current_command[ 2 ] ); 798 fi; 799 end, 800 @EndNotLatex := function() 801 if autodoc_read_line = true then 802 autodoc_read_line := false; 803 fi; 804 Add( current_item, "]]></Alt>" ); 805 end, 806 @NotLatex := function() 807 Add( current_item, "<Alt Not=\"LaTeX\"><![CDATA[" ); 808 Add( current_item, current_command[ 2 ] ); 809 Add( current_item, "]]></Alt>" ); 810 end, 811 @Dependency := function() 812 if not IsBound( tree!.worksheet_dependencies ) then 813 tree!.worksheet_dependencies := [ ]; 814 fi; 815 NormalizeWhitespace( current_command[ 2 ] ); 816 Add( tree!.worksheet_dependencies, SplitString( current_command[ 2 ], " " ) ); 817 end, 818 @BeginAutoDocPlainText := deprecated("@BeginAutoDocPlainText", function() 819 plain_text_mode := true; 820 end), 821 @AutoDocPlainText := ~.@BeginAutoDocPlainText, 822 @EndAutoDocPlainText := deprecated("@EndAutoDocPlainText", function() 823 plain_text_mode := false; 824 end), 825 @ExampleSession := function() 826 local example_node; 827 example_node := read_session_example( true, plain_text_mode ); 828 Add( current_item, example_node ); 829 end, 830 @BeginExampleSession := ~.@ExampleSession, 831 @LogSession := function() 832 local example_node; 833 example_node := read_session_example( false, plain_text_mode ); 834 Add( current_item, example_node ); 835 end, 836 @BeginLogSession := ~.@LogSession 837 ); 838 839 ## The following commands are specific for worksheets. They do not have a packageinfo, 840 ## and no place to extract those infos. So these commands are needed to make insert the 841 ## information directly into the document. 842 title_item_list := [ "Title", "Subtitle", "Version", "TitleComment", "Author", 843 "Date", "Address", "Abstract", "Copyright", "Acknowledgements", "Colophon" ]; 844 845 create_title_item_function := function( name ) 846 return function() 847 if not IsBound( tree!.TitlePage.( name ) ) then 848 tree!.TitlePage.( name ) := [ ]; 849 fi; 850 current_item := tree!.TitlePage.( name ); 851 Add( current_item, current_command[ 2 ] ); 852 end; 853 end; 854 855 ## Note that we need to create these functions in the helper function 856 ## create_title_item_function to ensure that the <name> variable is bound properly. 857 ## Without this intermediate helper, the wrong closure is taken, 858 ## and later, when the function is executed, the value for <name> will be the last 859 ## value <title_item> had, i.e., the last entry of <title_item_list>. 860 for title_item in title_item_list do 861 command_function_record.( Concatenation( "@", title_item ) ) := create_title_item_function( title_item ); 862 od; 863 864 rest_of_file_skipped := false; 865 ##Now read the files. 866 for filename in filename_list do 867 Reset(); 868 ## FIXME: Is this dangerous? 869 if PositionSublist( filename, ".autodoc" ) <> fail then 870 plain_text_mode := true; 871 fi; 872 filestream := InputTextFile( filename ); 873 if filestream = fail then 874 Error( "could not open ", filename ); 875 fi; 876 line_number := 0; 877 while true do 878 if rest_of_file_skipped = true then 879 rest_of_file_skipped := false; 880 break; 881 fi; 882 current_line := ReadLineWithLineCount( filestream ); 883 if current_line = fail then 884 break; 885 fi; 886 current_line_unedited := ShallowCopy( current_line ); 887 NormalizeWhitespace( current_line ); 888 current_command := Scan_for_AutoDoc_Part( current_line, plain_text_mode ); 889 if current_command[ 1 ] <> false then 890 if autodoc_read_line <> fail then 891 autodoc_read_line := true; 892 fi; 893 if not IsBound( command_function_record.(current_command[ 1 ]) ) then 894 ErrorWithPos("unknown AutoDoc command ", current_command[ 1 ]); 895 fi; 896 command_function_record.(current_command[ 1 ])(); 897 continue; 898 fi; 899 current_line := current_command[ 2 ]; 900 if autodoc_read_line = true or autodoc_read_line = fail then 901 was_declaration := Scan_for_Declaration_part( ); 902 if not was_declaration and autodoc_read_line <> fail then 903 autodoc_read_line := false; 904 fi; 905 fi; 906 od; 907 od; 908end ); 909