4 % Licensed by GNU GPL v3 or later version.
6 \def\title
{\TeX nicard
}
7 \def\covernote
{{\fiverm Batteries not included. Do not use this book as a
8 flotation device. This is free software
; see source file for details.
}}
10 % Prevent \outer from getting in the way
, stupid
!
14 ``u
{YJ
"@<Predeclaration of procedures@>=
21 \long\def\IndexCharacter#1':{`\.{\char`#1}'}
24 qAqA
/@
!@^\IndexCharacter\
34 \newcount\bibliocount \bibliocount
=0
36 \advance\bibliocount by
1 %
37 $^
{[\the\bibliocount
]}$
%
38 \expandafter\def\csname biblio \the\bibliocount\endcsname
{#
1}%
41 \emergencystretch
=\hsize
45 \rlap
{\vrule height
3.2pt depth
-2.5pt width \wd0
}{\box0
}%
48 @
*Introduction. This is \TeX nicard
, a program designed for similar
49 purposes of Magic Set Editor
, but in a different
(and better
) way. It
50 should be able to produce higher quality cards than Wizards of the Coast
,
51 and then they ought to use this program
, too
!
54 @^Wizards of the Coast@
>
55 @^commercial viability@
>
58 @
<Memory usage logging@
>@
;
59 @
<Interpreted C codes@
>@
;
64 @
<The include file for memory managed types@
>@
;
65 @
<Global variables@
>@
;
66 @
<Predeclaration of procedures@
>@
;
69 @ This line below should be changed with the current version number
,
70 whenever a new version is released.
(If you fork this program
, you should
71 also include some indication of forking in the \\
{version\_string
}.
)
72 % (it doesn't work if I use vertical bars here
)
76 @d version_string
"0.1"
77 @d version_number
1 // one major is worth ten minors
80 typedef unsigned char boolean
;
82 @ You might be wondering what this section is for
(especially since it
83 appears to be unused
). The reason is that some metamacros use it in order
84 to force the compiler to know the correct line numbers
(in case some lines
85 have been added by metamacros
).
90 @
<Nothing~@
>= /* ...
*/
92 @ There is also memory usage logging. If it is not being compiled for
93 memory usage logging
, it should just ignore these kind of commands.
95 @
<Memory usage logging@
>=
96 #ifndef @
!memusage_log
97 #define memusage_log
(_text
,_arg1
)
100 @
*Memory Management. This program uses a lot of similar memory management
,
101 so they will be defined in this chapter.
103 @^memory management@
>
105 @d none
-1 // indication that a |data_index| means nothing
109 char
*data
; // pointer to array of blocks
(|char
*| for use with |sizeof|
)
110 int used
; // number of blocks used
111 int allocated
; // number of blocks allocated
113 @#typedef int data_index
;
115 @ We will use an interpreted C code here
, which will send output to a
116 header file |
"memory_management.h"|.
118 @
<The include file for memory managed types@
>=
119 #include
"memory_management.h"
121 @ We will need some variables now just to keep track of which kinds of
122 memory managed areas are needed.
124 @
<Interpreted C codes@
>= @
{
125 char
**memory_managed_types
;
126 int num_memory_managed_types
;
127 memory_managed_types
=malloc
(128*sizeof
(char
*));
128 num_memory_managed_types
=0;
131 @ From this code
, the structure will be created in the header file for
132 each type that we need a |memory_of|. This section
, however
, is just a
133 ``wrapper'' code for the template.
135 @f @
!memory_of _decl_head_
// category
9
137 @
<Interpreted C codes@
>= @
{
141 sendc
(0200|'
{'
); // begin interpret mode
142 send
("send_memory_of(\"");
145 sendc
(0200|'
}'
); // end interpret mode
152 @ Here is what it does in order to keep a list of the memory managed
153 types. Note the type name was enclosed in quotation marks
, so now it will
154 be received as a string.
156 @
<Interpreted C codes@
>= @
{
157 void send_memory_of
(char
*s
) {
160 @
<Send the proper name of the memory managed type@
>;
161 for
(i
=0;i
<num_memory_managed_types
;i
++) {
162 if
(!strcmp
(s
,memory_managed_types
[i
])) return
;
164 memory_managed_types
[num_memory_managed_types
++]=s
;
168 @ @
<Send the proper name of the memory managed type@
>= {
174 @ Now the code you get to in order to define the structures in the header
175 file. We are mostly just copying the form of our |managed_memory|
176 structure
, but it will be customized to work with the specific type of the
179 @
<Interpreted C codes@
>= @
{
180 void send_memory_managed_types
() {
182 for
(i
=0;i
<num_memory_managed_types
;i
++) {
183 send
("typedef struct {");
184 send
(memory_managed_types
[i
]);
185 send
("*data; int used; int allocated; } x__");
186 send
(memory_managed_types
[i
]);
192 @ @
(memory_management.h@
>= @
{
193 send_memory_managed_types
();
196 @ These next two subroutines are used to allocate additional memory.
198 @d init_memory
(_a
,_size
) init_memory_
(&(_a),sizeof(*((_a).data)),(_size))
199 @d new_record
(_area
) new_record_
(&(_area),sizeof(*((_area).data)))
201 @
-p void
*init_memory_
(void
*mem
,int record_size
,int num_records
) {
202 managed_memory
*m
=mem
;
203 m-
>data
=malloc
(record_size
*num_records
);
205 m-
>allocated
=num_records
;
206 if
(!m-
>data
) @
<Fatal error due to lack of memory@
>;
210 @ @
-p data_index new_record_
(void
*mem
,int record_size
) {
211 managed_memory
*m
=mem
;
213 if
(m-
>used
>m-
>allocated
) {
215 m-
>data
=realloc
(m-
>data
,m-
>allocated
*record_size
);
217 if
(!m-
>data
) @
<Fatal error due to lack of memory@
>;
218 @
<Zero the new record@
>;
222 @ @
<Fatal error due to lack of memory@
>= {
223 fprintf
(stderr
,"Out of memory\n");
227 @ @
<Zero the new record@
>= {
228 memset
(m-
>data
+(record_size
*(m-
>used-1
)),0,record_size
);
231 @ Now just one more thing. It is useful to have a |foreach| macro to
234 @d foreach
(_var
,_area
) for
(_var
=0;_var
<_area.used
;_var
++)@
;
237 @
*Symbolic Names. There will be some names defined for the use of naming
238 subroutines
, symbolic constants
, patterns
, card areas
, etc. These names
239 are stored in a |managed_memory| called |names|.
241 It also stores references to other things
(defined in later chapters
). The
242 numeric value of a name in |names.data
[x
]| is |x
+256|.
247 @
<More elements of |name_data|@
>@
;
250 @ @
<Global variables@
>=
251 memory_of
(name_data
) names
;
253 @ @
<Initialize memory@
>= init_memory
(names
,16);
255 @ This subroutine finds a name
, adding it if necessary. The number
256 corresponding to it
(as described above
) will be the return value.
258 @
-p int find_name
(char
*name
) {
259 @
<Search for the |name| in |names|@
>;
260 @
<Add the new name
(it was not found
)@
>;
263 @ @
<Search for the |name| in |names|@
>= {
266 if
(!strcmp
(names.data
[i
].name
,name
)) return i
+256;
270 @ @
<Add the new name
(it was not found
)@
>= {
271 int n
=new_record
(names
);
272 names.data
[n
].name
=strdup
(name
);
276 @ A macro will be useful to access the data from a number.
278 @d name_info
(_num
) names.data
[(_num
)-0x0100]
280 @ This code lists the names. It is used for a diagnostic purpose.
282 @
<Display the list of names@
>= {
285 printf
("%d \"%s\
" ",n
+256,names.data
[n
].name
);
286 @
<Display other fields of |names.data
[n
]|@
>;
291 @
*Storage of Tokens. Tokens are stored as
16-bit numbers. Values |
0x0020|
292 to |
0x00FF| represent those ASCII characters
, and |
0x0000| to |
0x001F| are
293 ASCII control codes. Higher numbers represent an index into the |names|
294 array
(where |
0x0101| represents |names.data
[0x0001]|
).
297 @q
[data type of tokens
]@
>
298 typedef unsigned short token
;
300 @ This section lists the ASCII control codes which can be used. Some of
301 them have slightly different meaning from the ASCII standard.
303 @d null_char
0x00 // end of a |raw_data| string or similar things
304 @d pre_null_char
0x01 // becomes |null_char|
305 @d end_transmission
0x04 // marks the end of the last card in this area
306 @d tabulation
0x09 // represents a tab in a
{\TeX
} alignment
307 @d raw_data
0x10 // enter raw
{\TeX
} mode
308 @d whatsit
0x1A // a token for converting into a name token
309 @d escape_code
0x1B // represents a
{\TeX
} control sequence introducer
310 @d record_separator
0x1E // marks the end of a card
311 @d field_separator
0x1F // marks the end of a field of a card
312 @d start_name_code
0x0100
314 @ These tokens are used in card areas
, which are defined
(and described
)
317 @
*Cards. The data of the cards is stored in card areas. Each card area
318 is a list of tokens
, terminated by |record_separator|. The final card in
319 the area is terminated by |end_transmission|.
328 @ @
<More elements of |name_data|@
>=
329 boolean has_card_area
;
330 data_index card_area
;
332 @ @
<Global variables@
>=
333 memory_of
(card_area_data
) card_areas
;
335 @ @
<Initialize memory@
>= init_memory
(card_areas
,1);
337 @ A new card area is created with this.
339 @
-p data_index set_card_area
(int num
) {
340 name_data
*m
=&name_info(num);
341 @
<Use the card area which is already set
, if able@
>;
342 @
<Otherwise
, create a new card area and use the new one@
>;
345 @ @
<Use the card area which is already set
, if able@
>= {
346 if
(m-
>has_card_area
) return m-
>card_area
;
349 @ @
<Otherwise
, create a new card area and use the new one@
>= {
350 data_index n
=new_record
(card_areas
);
352 card_areas.data
[n
].allocated
=0x100;
353 card_areas.data
[n
].tokens
=malloc
(0x100*sizeof
(token
));
354 card_areas.data
[n
].used
=0;
358 @ This subroutine sends a token to a card area.
360 @
-p void send_token
(data_index a
,token x
) {
361 if
(card_areas.data
[a
].allocated
<card_areas.data
[a
].used
+4)
362 @
<Double the allocation of card area tokens@
>;
363 card_areas.data
[a
].tokens
[card_areas.data
[a
].used
++]=x
;
366 @ @
<Double the allocation of card area tokens@
>= {
367 int n
=(card_areas.data
[a
].allocated
*=2)*sizeof
(token
);
368 card_areas.data
[a
].tokens
=realloc
(card_areas.data
[a
].tokens
,n
);
371 @ @
<Display other fields of |names.data
[n
]|@
>= {
372 if
(names.data
[n
].has_card_area
)
373 printf
("C(%d) ",names.data
[n
].card_area
);
376 @ The code in this section is used to ensure that each card area is
377 properly terminated with |end_transmission| marker
, so that when it is
378 time to write the output files
, it will know when to stop.
380 @
<Send |end_transmission| to each card area@
>= {
382 foreach
(a
,card_areas
) send_token
(a
,end_transmission
);
385 @
*Patterns. For pattern matching
, we store the patterns in one memory
386 managed area. The index of the beginning of each pattern area is stored
389 These constants are special codes which can occur in the |text| string
394 @d match_keyword
3 // match a keyword followed by a character in a table
395 @d match_table
4 // match a character using a table
396 @d optional_table
5 // match a character optional using a table
398 @d jump_table
7 // use a table to jump to a marker
399 @d successful_match
8
401 @d forward_one_space
10
402 @d match_left_side
11 // match at beginning of line
403 @d match_right_side
12 // match at end of line
408 unsigned int category
; // category for keywords
409 data_index subroutine
;
413 @ @
<More elements of |name_data|@
>=
414 boolean has_pattern_area
;
415 data_index pattern_area
;
417 @ @
<Global variables@
>=
418 memory_of
(pattern_data
) pattern_areas
;
420 @ @
<Initialize memory@
>= init_memory
(pattern_areas
,4);
422 @ @
<Display other fields of |names.data
[n
]|@
>= {
423 if
(names.data
[n
].has_pattern_area
)
424 printf
("P(%d) ",names.data
[n
].pattern_area
);
427 @ A new pattern area is created with this. The patterns in an area are
428 stored like a linked list. The last one with |next| pointing to nothing
,
429 is the terminator entry.
431 @
-p data_index set_pattern_area
(int num
) {
432 name_data
*m
=&name_info(num);
433 @
<Use the pattern area which is already set
, if able@
>;
434 @
<Otherwise
, create a new pattern area and use the new one@
>;
437 @ @
<Use the pattern area which is already set
, if able@
>= {
438 if
(m-
>has_pattern_area
) return m-
>pattern_area
;
441 @ @
<Otherwise
, create a new pattern area and use the new one@
>= {
442 data_index n
=new_record
(pattern_areas
);
443 m-
>has_pattern_area
=1;
444 pattern_areas.data
[n
].subroutine
=none
;
445 pattern_areas.data
[n
].next
=none
;
449 @ @
<Display the list of patterns@
>= {
451 foreach
(i
,pattern_areas
) {
452 if
(pattern_areas.data
[i
].text
) {
453 printf
("%d:%08X:%d:%d\n",i
,pattern_areas.data
[i
].category
454 ,pattern_areas.data
[i
].subroutine
,pattern_areas.data
[i
].next
456 display_string
(pattern_areas.data
[i
].text
);
462 @
*Keywords. Keywords means words which can be placed on the card and which
463 can have special meanings
, and possibly reminder text.
465 Keywords are stored in a large list in only one keyword area. A category
466 can be given a name
, which will automatically be assigned for the next bit
467 of the keyword category when it is entered the first time.
471 char
*match
; // match text
(can contain pattern codes
)
472 unsigned int category
; // bitfield of categories
475 char
*replacement
; // replacement text or reminder text
478 @ @
<Global variables@
>=
479 unsigned int next_keyword_category
=1;
480 memory_of
(keyword_data
) keywords
;
482 @ @
<Initialize memory@
>= init_memory
(keywords
,4);
484 @ A keyword category is found
(and created
, if it is not found
) using the
487 @
-p unsigned int find_category
(char
*name
) {
488 int i
=find_name
(name
);
489 if
(name_info
(i
).value.number
) {
490 return name_info
(i
).value.number
;
491 } @
+else if
(!name_info
(i
).value.is_string
) {
492 name_info
(i
).value.number
=next_keyword_category
;
493 next_keyword_category
<<=1;
494 if
(!next_keyword_category
)
495 fprintf
(stderr
,"Too many keyword categories: %s\n",name
);
496 return name_info
(i
).value.number
;
500 @ Some stack code commands are used when dealing with reading
/writing
503 In order that you might be able to iterate them
, it will exit out of the
504 current block when trying to read nonexisting keyword info instead of
505 displaying an error message.
507 @
<Cases for system commands@
>=
510 if
(registers
['K'
].number
<0 || registers
['K'
].number
>=keywords.used
)
512 push_num
(keywords.data
[registers
['K'
].number
].extra1
);
513 push_num
(keywords.data
[registers
['K'
].number
].extra2
);
514 push_string
(keywords.data
[registers
['K'
].number
].replacement
);
518 // Write keyword info
519 if
(registers
['K'
].number
<0 || registers
['K'
].number
>=keywords.used
)
520 program_error
("Out of range");
521 free
(keywords.data
[registers
['K'
].number
].replacement
);
522 keywords.data
[registers
['K'
].number
].replacement
=pop_string
();
523 keywords.data
[registers
['K'
].number
].extra2
=pop_num
();
524 keywords.data
[registers
['K'
].number
].extra1
=pop_num
();
528 @ @
<Display the list of keywords@
>= {
530 foreach
(i
,keywords
) {
531 display_string
(keywords.data
[i
].match
);
532 printf
(" [%d:%08X:%d:%d:%d]\n",i
,keywords.data
[i
].category
533 ,keywords.data
[i
].extra1
,keywords.data
[i
].extra2
534 ,strlen
(keywords.data
[i
].replacement
)
539 @
*Card List. A sorted summary list of the cards is kept in one list
,
540 having thirty-two general-purpose numeric fields
, and a pointer to the
541 beginning of the record
(usually the name in which it will be indexed by
).
547 int amount_in_pack
; // used in pack generation
550 @ @
<Global variables@
>=
551 memory_of
(list_entry
) card_list
;
553 @ @
<Initialize memory@
>= init_memory
(card_list
,16);
555 @
*Deck Lists. Deck lists involve lists of cards or rules for cards that
556 belong to a deck or pack.
560 There is one macro |lflag| here just to convert letters to bit flags. For
561 example |lflag
('a'
)| is the least significant bit.
563 @d lflag
(_ch
) (1<<((_ch
)-'a'
))
573 @ @
<Global variables@
>=
574 memory_of
(deck_entry
) deck_lists
;
576 @ @
<More elements of |name_data|@
>=
577 boolean has_deck_list
;
578 data_index deck_list
;
580 @ @
<Initialize memory@
>= init_memory
(deck_lists
,4);
582 @ A new deck list is created with this. The deck entries are stored like a
583 linked list. The terminator has |next| pointing to |none|.
585 @
-p data_index set_deck_list
(int num
) {
586 name_data
*m
=&name_info(num);
587 @
<Use the deck list which is already set
, if able@
>;
588 @
<Otherwise
, create a new deck list and use the new one@
>;
591 @ @
<Use the deck list which is already set
, if able@
>= {
592 if
(m-
>has_deck_list
) return m-
>deck_list
;
595 @ @
<Otherwise
, create a new deck list and use the new one@
>= {
596 data_index n
=new_record
(deck_lists
);
598 deck_lists.data
[n
].next
=none
;
602 @ @
<Display the deck list@
>= {
604 foreach
(i
,deck_lists
) {
606 if
(deck_lists.data
[i
].name
) display_string
(deck_lists.data
[i
].name
);
608 printf
(" [%08X:%d:%d]\n",deck_lists.data
[i
].flags
609 ,deck_lists.data
[i
].amount
,deck_lists.data
[i
].next
);
613 @
*Word Forms. These structures are used to store word form rules
, such as
614 plurals\biblio
{Conway
, Damian. ``An Algorithmic Approach to English
615 Pluralization''. \hskip
0pt plus
1in\hbox
{}
616 \.
{http
://www.csse.monash.edu.au
/\~damian
/papers
/HTML
/Plurals.html
}}. You
617 can store up to four different kinds
, in case of languages other than
627 unsigned char orig
[32];
628 unsigned char dest
[32];
629 boolean left_boundary
;
630 boolean right_boundary
;
633 @ @
<Global variables@
>=
634 memory_of
(word_form_entry
) word_forms
;
636 @ @
<Initialize memory@
>= {
638 init_memory
(word_forms
,16);
641 word_forms.data
[i
].orig
[0]=word_forms.data
[i
].dest
[0]=0;
642 word_forms.data
[i
].next
=i
+1;
643 word_forms.data
[i
].level
=0x7FFFFFFF;
644 word_forms.data
[i
+1].orig
[0]=word_forms.data
[i
+1].dest
[0]=0;
645 word_forms.data
[i
+1].next
=none
;
646 word_forms.data
[i
+1].level
=0;
650 @ Word form rules are added and then inserted in the correct place in the
651 linked list using the |next| field. Entries with a higher numbered level
652 take higher priority
, therefore will be placed before the ones with lower
653 numbered level. Next
, longer |orig| strings come before shorter strings
,
654 since they might be more specific forms of the others and will therefore
657 @
-p data_index add_word_form
(int kind
,int level
,char
*orig
,char
*dest
) {
658 data_index n
=new_record
(word_forms
);
659 @
<Set the fields of the new word form rule@
>;
660 @
<Insert the new word form rule into the linked list@
>;
664 @ The |left_boundary| and |right_boundary| fields specify if they should
665 match only at the boundary. Characters are checked using the \.W table and
666 removed from the string to place in the list.
668 @d last_character
(_str
) ((_str
)[strlen
(_str
)-1])
670 @
<Set the fields of the new word form rule@
>= {
671 word_forms.data
[n
].level
=level
;
672 strcpy
(word_forms.data
[n
].orig
,orig
+(tables
['W'
][*orig
]==2));
673 word_forms.data
[n
].left_boundary
=(tables
['W'
][*orig
]==2);
674 if
((word_forms.data
[n
].right_boundary
=
675 (tables
['W'
][last_character
(word_forms.data
[n
].orig
)]==3)))
676 last_character
(word_forms.data
[n
].orig
)=0;
677 strcpy
(word_forms.data
[n
].dest
,dest
+(tables
['W'
][*dest
]==2));
678 if
(tables
['W'
][last_character
(word_forms.data
[n
].dest
)]==3)
679 last_character
(word_forms.data
[n
].dest
)=0;
682 @ @
<Insert the new word form rule into the linked list@
>= {
683 data_index y
=(kind
&3)<<1; // previous item to |x|
684 data_index x
=word_forms.data
[y
].next
; // current item
686 for
(;x
!=none
;y
=x
,x
=word_forms.data
[y
].next
) {
687 if
(word_forms.data
[x
].next
==none
) break
;
688 @#if
(word_forms.data
[x
].level
<level
) break
;
689 if
(word_forms.data
[x
].level
>level
) continue
;
690 @#if
(strlen
(word_forms.data
[x
].orig
)<s
) break
;
692 word_forms.data
[y
].next
=n
;
693 word_forms.data
[n
].next
=x
;
696 @ Now to do computation of changing a word by word forms. This function
697 expects only one word from input
, or multiple words where the last one
698 should be the word to be converted. Uppercase letters are converted to
699 lowercase for conversion
(but not the other way around
), but if the
700 letters are uppercase in the input
, the output will also have uppercase
701 letters on those positions. The algorithm starts from the right side of
704 The parameter |src| is the input
, and |dest| should point to a buffer
705 which is large enough to store the output string.
709 @
-p data_index reform_word
(int kind
,char
*src
,char
*dest
) {
710 char
*l
=src
+strlen
(src
);
711 data_index n
=word_forms.data
[(kind
&3)<<1].next;
712 strcpy
(dest
,src
); // this is used later
713 @
<Try each word form rule
, following the |next| pointers@
>;
714 return none
; // in case there is nothing to do
717 @ @
<Try each word form rule
, following the |next| pointers@
>= {
720 while
(n
!=none
&& word_forms.data[n].next!=none) {
721 s
=strlen
(word_forms.data
[n
].orig
); @
+ p
=l-s
;
722 @
<Check the characters matching from |p|
, going backwards@
>;
723 n
=word_forms.data
[n
].next
;
727 @ Look ahead for the definition of |wcasecmp|
(true means it matches
).
729 @
<Check the characters matching from |p|
, going backwards@
>= {
731 if
((!word_forms.data
[n
].left_boundary || p
==src
732 || tables
['W'
][p
[-1]])
733 && wcasecmp(word_forms.data[n].orig,p))
734 @
<A match to the word form rules has been found@
>;
735 @
<Go backwards
, stop if we are not allowed to continue backwards@
>;
739 @ @
<A match to the word form rules has been found@
>= {
741 sprintf
(o
,"%s%s",word_forms.data
[n
].dest
,p
+s
);
742 @
<Change the capitalization to match the original@
>;
746 @ Remember
, that for example if ``cow'' becomes ``kine''
, then ``Cow''
747 will become ``Kine''. So
, it will retain capitalization.
751 @
<Change the capitalization to match the original@
>= {
752 char
*q
=word_forms.data
[n
].orig
;
753 for
(;*p
&& *q;p++,o++,q++) if(*p==tables['U'][*q]) *o=tables['U'][*o];
756 @ @
<Go backwards
, stop if we are not allowed to continue backwards@
>= {
757 if
(word_forms.data
[n
].right_boundary
) break
; // matches only on boundary
758 if
(tables
['W'
][p
[s
]]) break
; // only the last word
(s
) can be matched
759 if
(p--
==src
) break
; // stop at beginning
762 @ This function is defined to compare strings in the way needed for
763 matching word forms
, including case conversion. The lowercase letters in
764 the |shorter| string are permitted to match lowercase and uppercase
765 letters in the |longer| string
, and the |shorter| string is permitted to
766 be shorter and still match.
768 @
-p boolean wcasecmp
(char
*shorter
,char
*longer
) {
769 for
(;;shorter
++,longer
++) {
770 if
(!*shorter
) return
1;
771 if
(!*longer
) return
0;
772 if
(*shorter
!=*longer
&& *shorter!=tables['L'][*longer]) return 0;
776 @ Of course it is now needed a command that can access these features from
777 within a \TeX nicard template. The |level| of the matched rule is also
778 returned
, in case your program might use that information for something.
780 @
<Cases for system commands@
>=
782 // Convert a word form
786 data_index n
=reform_word
(k
,o
,q
);
788 if
(n
==none
) push_num
(0);
789 else push_num
(word_forms.data
[n
].level
);
794 @ @
<Display the list of word form rules@
>= {
796 foreach
(i
,word_forms
) {
797 printf
("%d %c\"",i,word_forms.data[i].left_boundary?'[':' ');
798 display_string(word_forms.data[i].orig);
799 printf("\
"%c -> \"",word_forms.data[i].right_boundary?']':' ');
800 display_string(word_forms.data[i].dest);
801 printf("\
" %d >%d\n",word_forms.data
[i
].level
802 ,word_forms.data
[i
].next
);
806 @
*Random Number Generation. This program uses the Xorshift algorithm
,
807 invented by George Marsaglia\biblio
{Marsaglia
(July
2003). ``Xorshift
808 RNGs''. Journal of Statistical Software Vol.~
8 (Issue
14).
{\tt
809 http
://www.jstatsoft.org
/v08
/i14
/paper
}.
}.
811 @^Marsaglia
, George@
>
814 @
<Global variables@
>=
820 @ @
<Initialize the random number generator@
>= {
821 @q
[initialize the random seed
::]@
>
822 rng_seed
((unsigned int
)time
(0));
823 @q
[::initialize the random seed
]@
>
826 @ The seed parameters for the random number generator will be seeded using
827 the linear congruential generator
, which is a simpler generator which can
828 be used to seed it with.
830 The parameters |lcg_a| and |lcg_c| are parameters to the linear
831 congruential generator algorithm. The values used here are the same as
832 those used in GNU C. In this program they will be specified explicitly so
833 that you can get identical output on different computers.
838 @
-p void rng_seed
(unsigned int x
) {
839 rng_x
=x
=lcg_a
*x
+lcg_c
;
840 rng_y
=x
=lcg_a
*x
+lcg_c
;
841 rng_z
=x
=lcg_a
*x
+lcg_c
;
842 rng_w
=x
=lcg_a
*x
+lcg_c
;
845 @ There is a command to reseed it using a constant
(so that you can
846 generate the same numbers on different computers
).
848 @
<Cases for system commands@
>=
850 // Reseed the random number generator
851 if
(stack_ptr-
>is_string
) program_error
("Type mismatch");
856 @ And now follows the algorithm for generating random numbers. One change
857 has been made so that once it is modulo
, all number will still be of equal
860 Numbers are generated in the range from
0 up to but not including |limit|.
862 @d max_uint
((unsigned int
)(-1))
864 @
-p unsigned int gen_random
(unsigned int limit
) {
865 unsigned int r
=max_uint-
(max_uint
%limit
); // range check
867 @
<Make the next number |rng_w|...@
>;
868 @
<Check the range
, try again if out of range
, else |return|@
>;
872 @ @
<Make the next number |rng_w| by Xorshift algorithm@
>= {
873 unsigned int t
= rng_x ^
(rng_x
<< 11);
874 rng_x
= rng_y
; @
+ rng_y
= rng_z
; @
+ rng_z
= rng_w
;
875 rng_w ^
= (rng_w
>> 19) ^ t ^
(t
>> 8);
878 @ @
<Check the range
, try again if out of range
, else |return|@
>= {
879 if
(rng_w
<=r
) return rng_w
%limit
;
882 @ @
<Cases for system commands@
>=
884 // Generate a random number
885 if
(stack_ptr-
>is_string
) program_error
("Type mismatch");
886 stack_ptr-
>number
=gen_random
(stack_ptr-
>number
);
890 @
*Stack Programming Language. Now we get to the part where the user can
891 enter a program
, in order to control the features of this program. The
892 programming language used is like \.
{dc
}, but different.
896 Subroutines are simply stored as strings in the |names| area
, since they
897 are the same as registers.
899 @ Now we have the storage of registers. Registers
0 to
255 are stored in
900 this separate list
, while other register values are just stored in the
901 |names| list. There is also a stack
, which has storage of the same values
902 as registers can contain.
915 @ @
<More elements of |name_data|@
>=
916 register_value value
;
918 @ @
<Global variables@
>=
919 register_value registers
[256];
920 register_value stack
[max_stack
];
921 register_value
*stack_ptr
=stack-1
; // current top of stack element
923 @ Here are some codes for pushing and popping the stack.
925 @d pop_num
() ((stack_ptr--
)->number
)
927 @
-p inline void push_string
(char
*s
) {
929 stack_ptr-
>is_string
=1;
930 stack_ptr-
>text
=strdup
(s
);
933 @ @
-p inline void push_num
(int n
) {
935 stack_ptr-
>is_string
=0;
939 @ @
-p inline void stack_dup
(void
) {
940 if
((stack_ptr
[1].is_string
=stack_ptr-
>is_string
)) {
941 stack_ptr
[1].text
=strdup
(stack_ptr-
>text
);
943 stack_ptr
[1].number
=stack_ptr-
>number
;
948 @ @
-p inline void stack_drop
(void
) {
949 if
(stack_ptr-
>is_string
) free
(stack_ptr-
>text
);
953 @ @
-p inline char
*pop_string
(void
) {
954 char
*p
=stack_ptr-
>text
;
955 stack_ptr-
>is_string
=0; stack_ptr-
>text
=0;
960 @ Also
, some subroutines are needed here in order to deal with registers.
962 For |fetch_code|
, the string |
"0[]+"| is returned if it is not a string
,
963 generating a ``Type mismatch'' error when you try to run it.
965 @
-p inline char
*fetch_code
(int r
) {
967 if
(!registers
[r
].is_string
) return
"0[]+";
968 return registers
[r
].text
;
970 if
(!name_info
(r
).value.is_string
) return
"0[]+";
971 return name_info
(r
).value.text
;
975 @ @
-p inline void fetch
(int r
) {
977 if
(!(r
&~0xFF)) v=&(registers[r]);
978 else v
=&(name_info(r).value);
979 (++stack_ptr
)->is_string
=v-
>is_string
;
981 stack_ptr-
>text
=strdup
(v-
>text
);
983 stack_ptr-
>number
=v-
>number
;
987 @ @
-p inline void store
(int r
) {
989 if
(!(r
&~0xFF)) v=&(registers[r]);
990 else v
=&(name_info(r).value);
991 if
(v-
>is_string
) free
(v-
>text
);
992 v-
>is_string
=stack_ptr-
>is_string
;
994 v-
>text
=stack_ptr-
>text
;
996 v-
>number
=stack_ptr-
>number
;
1001 @ There is also a save stack. This save stack stores the saved values of
1002 the registers |'
0'| to |'
9'|
, so that you can have local variables in a
1005 @
<Global variables@
>=
1006 register_value save_stack
[520];
1007 register_value
*save_stack_ptr
=save_stack
;
1009 @ These codes deal with the save stack. Strings will be copied when
1010 saving. When loading
, strings that were previously in the registers will
1013 @
<Save local registers to the save stack@
>= {
1015 for
(i
='
0'
;i
<='
9'
;i
++) {
1016 *save_stack_ptr
=registers
[i
];
1017 if
(registers
[i
].is_string
)
1018 save_stack_ptr-
>text
=strdup
(save_stack_ptr-
>text
);
1023 @ @
<Load local registers from the save stack@
>= {
1025 for
(i
='
9'
;i
>='
0'
;i--
) {
1026 if
(registers
[i
].is_string
) free
(registers
[i
].text
);
1027 registers
[i
]=*--save_stack_ptr
;
1031 @
*Commands for Stack Programming Language. Finally
, is the code where it
1032 can be executed. The return value of this function indicates how many
1033 levels should be exit when it is called.
1035 @
-p int execute_program
(unsigned char
*prog
) {
1036 unsigned char
*ptr
=prog
;
1037 reset_execute_program
:
1040 @
<Cases for literal data commands@
>@
;
1041 @
<Cases for stack manipulation commands@
>@
;
1042 @
<Cases for arithmetic commands@
>@
;
1043 @
<Cases for flow-control commands@
>@
;
1044 @
<Cases for register
/table operation commands@
>@
;
1045 @
<Cases for string commands@
>@
;
1046 @
<Cases for condition
/compare commands@
>@
;
1047 @
<Cases for local registers commands@
>@
;
1048 @
<Cases for system commands@
>@
;
1049 @
-case '?'
: @
<Do a diagnostics command@
>@
;@
+break
;
1051 if
(*ptr
>='
0'
&& *ptr<='9') {
1052 @
<Read a literal number and push to stack@
>;
1053 } @
+else if
(0x80&*ptr) {
1054 @
<Execute a subroutine code from the current character@
>;
1058 if
(stack_ptr
<stack-1
) program_error
("Stack underflow");
1059 if
(stack_ptr
>stack
+max_stack
) program_error
("Stack overflow");
1064 @ @
<Cases for literal data commands@
>=
1066 // Literal ASCII character
1072 @
<Read a literal string and push to stack@
>;
1077 @
<Read a literal name and push its number to the stack@
>;
1081 @ @
<Read a literal number and push to stack@
>= {
1083 while
(*ptr
>='
0'
&& *ptr<='9') n=10*n+(*ptr++)-'0';
1088 @ @
<Read a literal string and push to stack@
>= {
1096 if
(!*ptr
) program_error
("Unterminated string literal");
1102 @ @
<Read a literal name and push its number to the stack@
>= {
1104 while
(*ptr
&& *ptr!=')') ptr++;
1105 if
(!*ptr
) program_error
("Unterminated string literal");
1107 push_num
(find_name
(p
));
1111 @ @
<Cases for stack manipulation commands@
>=
1113 // Drop top item of stack
1118 // Clears the stack
, rendering it empty
1119 while
(stack_ptr
>=stack
) stack_drop
();
1123 // Duplicates the value on top of the stack.
1128 // Swaps the top two values on the stack
1129 stack_ptr
[1]=stack_ptr
[0];
1130 stack_ptr
[0]=stack_ptr
[-1];
1131 stack_ptr
[-1]=stack_ptr
[1];
1135 @ @
<Cases for arithmetic commands@
>=
1137 // Add two numbers
, or concatenate two strings
1138 if
(stack_ptr-
>is_string
) {
1139 @
<Concatenate strings on the stack@
>;
1142 if
(stack_ptr-
>is_string
)
1143 program_error
("Type mismatch");
1144 stack_ptr-
>number
+=n
;
1149 // Subtract two numbers
, or compare two strings
1150 if
(stack_ptr-
>is_string
) {
1151 @
<Compare strings on the stack@
>;
1154 if
(stack_ptr-
>is_string
)
1155 program_error
("Type mismatch");
1156 stack_ptr-
>number-
=n
;
1161 // Multiply two numbers
1163 if
(stack_ptr
[0].is_string || stack_ptr
[1].is_string
)
1164 program_error
("Number expected");
1165 stack_ptr-
>number
*=n
;
1169 // Divide two numbers
1171 if
(stack_ptr
[0].is_string || stack_ptr
[1].is_string
)
1172 program_error
("Number expected");
1173 if
(n
==0) program_error
("Division by zero");
1174 stack_ptr-
>number
/=n
;
1178 // Modulo of two numbers
1180 if
(stack_ptr
[0].is_string || stack_ptr
[1].is_string
)
1181 program_error
("Number expected");
1182 if
(n
==0) program_error
("Division by zero");
1183 stack_ptr-
>number
%=n
;
1187 @ @
<Concatenate strings on the stack@
>= {
1188 char
*s
=pop_string
();
1190 if
(!stack_ptr-
>is_string
) program_error
("Type mismatch");
1191 q
=malloc
(strlen
(s
)+strlen
(stack_ptr-
>text
)+1);
1192 strcpy
(q
,stack_ptr-
>text
);
1193 strcpy
(q
+strlen
(q
),s
);
1200 @ @
<Compare strings on the stack@
>= {
1201 char
*s
=pop_string
();
1202 char
*q
=pop_string
();
1203 push_num
(strcmp
(q
,s
));
1208 @ @
<Cases for flow-control commands@
>=
1210 // Exit from multiple levels
1216 // Go back to beginning
1221 // Exit from two levels
1226 // Execute code from top of stack
1227 @
<Execute a string or subroutine code from top of stack@
>;
1231 @ Note here
, it is a recursive function call.
1234 @
<Execute a string or subroutine code from top of stack@
>= {
1235 if
(stack_ptr-
>is_string
) {
1236 char
*p
=pop_string
();
1237 int q
=execute_program
(p
);
1241 char
*p
=fetch_code
(pop_num
());
1242 int q
=execute_program
(p
);
1247 @ Since the extended characters
(|
0x80| to |
0xFF|
) do not correspond to
1248 any commands
, here we can use them to execute a subroutine code
, allowing
1249 many things related to self-modifying code
(and other stuff
) to be done
1250 that would be difficult otherwise.
1252 @
<Execute a subroutine code from the current character@
>= {
1253 char
*p
=fetch_code
(*ptr
);
1254 int q
=execute_program
(p
);
1258 @ @
<Cases for register
/table operation commands@
>=
1260 // Store value to table
1262 if
(stack_ptr-
>is_string
) program_error
("Number expected");
1264 tables
[0x7F&*++ptr][n]=pop_num();
1268 // Load value from table
1269 stack_ptr-
>number
=tables
[0x7F&*++ptr][stack_ptr->number];
1273 // Load value from register named by stack
1274 if
(stack_ptr-
>is_string
) program_error
("Number expected");
1279 // Store value in register named by stack
1280 if
(stack_ptr-
>is_string
) program_error
("Number expected");
1285 // Load value from register
1290 // Store value in register
1295 @ @
<Cases for string commands@
>=
1297 // Put brackets around a string
, or convert number to text
1298 if
(stack_ptr-
>is_string
) {
1299 @
<Put brackets around string at top of stack@
>;
1301 @
<Convert top of stack to string representation of a number@
>;
1306 // Calculate number of characters in a string
1307 char
*s
=pop_string
();
1308 push_num
(strlen
(s
));
1313 // ``ASCIIfy'' a number
1314 if
(stack_ptr-
>is_string
) {
1315 if
(stack_ptr-
>text
[0]) stack_ptr-
>text
[1]=0;
1317 int n
=stack_ptr-
>number
;
1318 stack_ptr-
>is_string
=1;
1319 stack_ptr-
>text
=malloc
(2);
1320 stack_ptr-
>text
[0]=n
;
1321 stack_ptr-
>text
[1]=0;
1326 // Take the first character from the string
1327 char
*s
=stack_ptr-
>text
;
1328 if
(!stack_ptr-
>is_string ||
!*s
) return
0;
1330 stack_ptr
[-1].text
=strdup
(s
+1);
1335 @ @
<Put brackets around string at top of stack@
>= {
1336 char
*buf
=malloc
(strlen
(stack_ptr-
>text
)+3);
1337 sprintf
(buf
,"[%s]",stack_ptr-
>text
);
1338 free
(stack_ptr-
>text
);
1339 stack_ptr-
>text
=buf
;
1342 @ @
<Convert top of stack to string representation of a number@
>= {
1344 sprintf
(buf
,"%d",stack_ptr-
>number
);
1349 @ Here is how the ``Arithmetic
IF'' command works
: On the stack you have
1350 any three values at the top
, and a number underneath it. Those are all
1351 removed
, except one of the three values which is selected based on the
1352 sign of the number
(the condition value
).
1354 @
<Cases for condition
/compare commands@
>=
1357 @
<Do the ``Arithmetic
IF''@
>;
1363 if
(stack_ptr
[0].is_string || stack_ptr
[1].is_string
)
1364 program_error
("Number expected");
1365 stack_ptr-
>number
&=n;
1369 @ Do you like this algorithm? Is this a real question?
1373 @
<Do the ``Arithmetic
IF''@
>= {
1374 register_value v
=stack_ptr
[-3];
1377 stack_ptr
[-3]=stack_ptr
[n
];
1379 stack_drop
();@
+stack_drop
();@
+stack_drop
();
1382 @ @
<Cases for local registers commands@
>=
1385 @
<Save local registers to the save stack@
>;
1390 @
<Load local registers from the save stack@
>;
1394 @ When there is a program error
(such as stack underflow
), the following
1395 subroutine is used to handle it.
1397 @d program_error
(_text
) program_error_
(prog
,ptr
,_text
)
1399 @
-p void program_error_
(char
*prog
,char
*ptr
,char
*msg
) {
1400 fprintf
(stderr
,"Error in %s on line %d",current_filename
,current_line
);
1401 fprintf
(stderr
,"\n! %s\ns%dS%dp%d near \"",msg,stack_ptr-stack,
1402 save_stack_ptr-save_stack,ptr-prog);
1403 @<Display the codes near the part that caused the error@>;
1404 fprintf(stderr,"\
"\n");
1408 @ @
<Display the codes near the part that caused the error@
>= {
1412 if
(p
<prog || p
>ptr
) p
=prog
;
1413 for
(i
=0;p
+i
<=ptr
&& p[i];i++) buf[i]=p[i];
1415 fprintf
(stderr
,"%s",buf
);
1418 @
*Tables and registers. The tables must be stored here. There are
128
1419 tables with
256 entries each
, each of which can store one byte of data.
1420 These tables are used for converting uppercase
/lowercase
, for deciding
1421 which characters need to be escaped in \TeX
, and so on.
1423 The purposes of the built-in registers are also described in this chapter.
1424 The tables and registers named by uppercase letters are for system use.
1425 The tables and registers named by lowercase can be used by the user.
1427 @
<Global variables@
>=
1428 unsigned char tables
[128][256];
1430 @ Here are the uses of the built-in tables and registers
:
1431 @^built-in registers@
>
1434 Register \.A
: The current position in the current cards area.
1436 Register \.C
: The current cards area.
1438 Register \.D
: Dots per inch
, multiplied by
100.
1440 Register \.E
: The escape character for \TeX. If this is a string
, the
1441 entire string is the prefix
; otherwise
, it is a ASCII number of the
1442 character to be used.
1444 Register \.K
: Index number for last keyword entry added. Also used when
1445 dealing with keyword operation commands
, and when a keyword is matched in
1448 Register \.P
: The current pattern area.
1450 Register \.Q
: The parameters for the ImageMagick command-line
, separated
1453 Register \.T
: Alignment tab character for \TeX. Same considerations apply
1454 as the \.E register.
1456 Register \.U
: A code to execute for a deck specification enrty with \.x
1459 Register \.V
: The version number of this program.
1461 Register \.W
: A code which pushes the whatsit replacements onto the stack.
1462 It is initialized to a blank string before each line in a card area. It
1463 should push the replacements in the reverse order of the whatsits
, so you
1464 could use a code like this
, for example
: \.
{[(Abracadabra
)]lW
+sW
}
1466 Register \.
X: Horizontal coordinate across the page
(in pixels
).
1468 Register \.
Y: Vertical coordinate across the page
(in pixels
).
1470 Register \.Z
: Should be set to a code to execute after doing everything
1471 else
(but before writing output files
).
1473 Table \.E
: Indicates which characters need escaped for \TeX.
1475 Table \.G
: Table containing information for sorting and grouping.
1477 Table \.L
: Conversion to lowercase.
1479 Table \.S
: Information for natural sorting.
1481 Table \.U
: Conversion to uppercase.
1483 Table \.W
: Table for word form rules. Zero means a letter
, one means a
1484 word separator
, two means use to mark beginning of a word
, three means use
1485 to mark the end of a word. In this program
, it is advantageous to use the
1486 fact that zero means word characters
(such as letters
), and nonzero means
1489 @d init_register
(_reg
,_val
) do@
+{
1490 registers
[_reg
].is_string
=0;
1491 registers
[_reg
].number
=(_val
);
1494 @d init_register_str
(_reg
,_val
) do@
+{
1495 registers
[_reg
].is_string
=1;
1496 registers
[_reg
].text
=strdup
(_val
);
1499 @
<Initialize the tables and registers@
>= {
1501 for
(i
=0;i
<256;i
++) init_register
(i
,0);
1502 init_register
('E'
,'\\'
);
1503 init_register
('V'
,version_number
);
1504 @
<Initialize table of alphabetical case conversion@
>;
1507 @ @
<Initialize table of alphabetical case conversion@
>= {
1508 for
(i
=0;i
<256;i
++) tables
['L'
][i
]=tables
['U'
][i
]=i
;
1509 for
(i
='A'
;i
<='Z'
;i
++) {
1510 tables
['L'
][i
]=i
+'a'
-'A'
;
1511 tables
['U'
][i
+'a'
-'A'
]=i
;
1515 @ @
<Display the contents of table |
*++ptr|@
>= {
1518 for
(i
=0;i
<256;i
++) {
1519 printf
("%c%c",tables
[t
][i
]?'
+'
:'.'
,@|
1520 (tables
[t
][i
]<0x7F && tables[t][i]>=' ')?tables[t][i]:'.'
1522 if
((i
&0x0F)==0x0F) printf("\n");
1524 for
(i
=' '
;i
<0x7F;i
++) if
(tables
[t
][i
]) printf
("%c",i
);
1527 @
*Diagnostics. Here is diagnostics commands. These are used to display the
1528 internal information on standard output
, so that you can check how these
1529 things are working.
(You can also use \.
{gdb
} for debugging purposes.
) A
1530 diagnostics command always starts with a question mark
, and is then
1531 followed by one more character indicating the type of diagnostics
1532 requestsed.
(Some are followed by an additional character after that.
)
1534 @
<Do a diagnostics command@
>= {
1536 case 'c'
: @
<Display the sorted card list@
>; @
+break
;
1537 case 'd'
: @
<Display the deck list@
>; @
+break
;
1538 case 'k'
: @
<Display the list of keywords@
>; @
+break
;
1539 case 'n'
: @
<Display the list of names@
>; @
+break
;
1540 case 'p'
: @
<Display the list of patterns@
>; @
+break
;
1541 case 's'
: @
<Display the contents of the stack@
>; @
+break
;
1542 case 't'
: @
<Display the contents of table |
*++ptr|@
>; @
+break
;
1543 case 'w'
: @
<Display the list of word form rules@
>; @
+break
;
1544 default
: program_error
("Unknown type of diagnostics");
1548 @ One subroutine is used here for displaying strings with escaped
, so that
1549 it will display on a terminal without messing it up or omitting the
1550 display of some characters.
1552 @
-p void display_string
(char
*s
) {
1554 if
(*s
<' ' ||
*s
==0x7F) {
1555 printf
("^%c",0x40^
*s
);
1562 @ @
<Display the contents of the stack@
>= {
1564 for
(p
=stack
;p
<=stack_ptr
;p
++) {
1567 display_string
(p-
>text
);
1570 printf
("%d\n",p-
>number
);
1575 @ More of the diagnostics functions are included in the chapters for the
1576 data structures which it is displaying.
1578 @
*Pattern Matching. Now
, finally
, after the chapter about patterns
, and
1579 going through many other things in between
, comes to the chapter in which
1580 patterns are actually being matched.
1582 One structure is used here for the information about how to match it
, and
1583 what has been matched from it. The parameter |num_capture| is how many
1584 captured parts there are
, and the |start| and |end| arrays store the index
1585 into the |src| string of where the matches are. The entire matched part is
1586 indicated by |start
[0]| and |end
[0]|
(note always |start
[0]==0|
).
1591 char
*truesrc
; // used for checking true beginning of the line
1593 unsigned int category
;
1599 @ This first one just matches one pattern against a string to see if it
1600 matches. It returns true if it does match.
(It is somewhat inefficient.
)
1602 @
-p boolean match_pattern
(match_info
*mat
) {
1603 char
*src
; // current start of source string
1604 char
*ptr
; // pointer into source string |src|
1605 char
*pptr
; // pointer into pattern string
1606 src
=mat-
>src
; @
+ mat-
>num_capture
=0; @
+ pptr
=mat-
>pattern
; @
+ ptr
=src
;
1607 @
<Execute the pattern on the string |src|@
>;
1611 @ This loop executes each command in the pattern in attempt to match each
1612 character. In case of mismatch
, it will break out of this loop
, and
1613 continue with the next iteration of the loop in the previous section.
1615 @d not_a_marker
!(pptr
[-1]&0x80)
1617 @
<Execute the pattern on the string |src|@
>= {
1621 mat-
>start
[++mat-
>num_capture
]=ptr-mat-
>src
; @
+break
;
1622 case end_capture
: mat-
>end
[mat-
>num_capture
]=ptr-mat-
>src
; @
+break
;
1623 case match_keyword
: @
<Do |match_keyword|@
>; @
+break
;
1625 if
(!tables
[*pptr
++][*ptr
++]) goto mismatch
; @
+break
;
1626 case optional_table
: ptr
+=!!tables
[*pptr
++][*ptr
]; @
+break
;
1627 case failed_match
: goto mismatch
;
1629 if
(!(pptr
=strchr
(mat-
>pattern
,0x80|tables
[*pptr
++][*ptr
++])))
1632 case successful_match
: @
<Do |successful_match|@
>;
1633 case back_one_space
: if
(ptr--
==mat-
>src
) goto mismatch
; @
+break
;
1634 case forward_one_space
: if
(!*ptr
++) goto mismatch
; @
+break
;
1635 case match_left_side
: if
(ptr
!=mat-
>truesrc
) goto mismatch
; @
+break
;
1636 case match_right_side
: if
(*ptr
>=' '
) goto mismatch
; @
+break
;
1637 default
: if
(not_a_marker
&& pptr[-1]!=*ptr++) goto mismatch;
1642 @ @
<Do |successful_match|@
>= {
1644 mat-
>end
[0]=ptr-mat-
>src
;
1648 @ And now
, the next part matches from an area and changes the string in
1649 place
, possibly by reallocating it. The |src| pointer passed to this
1650 function should be one that can be freed
!
1652 @
-p char
*do_patterns
(char
*src
,int area
) {
1655 int index
=0; // index into |src| string
1656 @
<Cancel if there isn't a pattern area@
>;
1658 if
(index
>=strlen
(src
)) return src
;
1659 pat
=pattern_areas.data
+name_info
(area
).pattern_area
;
1661 @
<Fill up the |mat| structure for testing the current pattern@
>;
1662 if
(mat.pattern
&& match_pattern(&mat)) {
1663 @
<Push the captured strings to the stack@
>;
1664 @
<Call the subroutine associated with this pattern@
>;
1665 if
(stack_ptr-
>is_string
) {
1666 @
<Replace the matched part from the stack and fix the |index|@
>;
1671 goto continue_matching
;
1673 @
<Select the next pattern in this area or |break|@
>;
1675 index
++; @
+ goto continue_matching
;
1678 @ @
<Cancel if there isn't a pattern area@
>= {
1679 if
(area
<256) return src
;
1680 if
(!name_info
(area
).has_pattern_area
) return src
;
1683 @ @
<Fill up the |mat| structure for testing the current pattern@
>= {
1686 mat.pattern
=pat-
>text
;
1687 mat.category
=pat-
>category
;
1690 @ @
<Push the captured strings to the stack@
>= {
1692 for
(i
=mat.num_capture
;i
;i--
) {
1693 push_string
(src
+index
+mat.start
[i
]);
1694 stack_ptr-
>text
[mat.end
[i
]-mat.start
[i
]]=0;
1698 @ @
<Call the subroutine associated with this pattern@
>= {
1699 execute_program
(names.data
[pat-
>subroutine
].value.text
);
1702 @ The memory allocated is probably more than is needed
, but this way is
1703 simpler. It is always sufficient amount
, though. Think about it.
1707 @
<Replace the matched part from the stack and fix the |index|@
>= {
1708 char
*q
=malloc
(strlen
(src
)+strlen
(stack_ptr-
>text
)+1);
1710 sprintf
(q
+index
,"%s%s",stack_ptr-
>text
,src
+index
+mat.end
[0]);
1713 index
+=strlen
(stack_ptr-
>text
);
1716 @ @
<Select the next pattern in this area or |break|@
>= {
1717 if
(pat-
>next
==none
) break
;
1718 pat
=pattern_areas.data
+pat-
>next
;
1721 @ Finally
, there is a command |'M'| to do a pattern matching and
1722 replacement with a string
, inside of a stack subroutine code.
1724 @
<Cases for system commands@
>=
1726 // do pattern matching and replacement
1728 if
(!stack_ptr-
>is_string
) program_error
("Type mismatch");
1729 stack_ptr-
>text
=do_patterns
(stack_ptr-
>text
,n
);
1733 @
*Matching Keywords. Codes for matching keywords have been placed in
1734 another chapter
, instead of making the previous chapter longer.
1736 So now we can see how it is matched keywords in a pattern code.
1738 @
<Do |match_keyword|@
>= {
1741 char t
=*pptr
++; // indicate which table to use
1742 data_index best
=none
;
1744 @
<Try matching each keyword belonging to the category@
>;
1745 if
(best
==none
) goto mismatch
;
1746 @
<Adjust the \.K register for this keyword match@
>;
1750 @ @
<Adjust the \.K register for this keyword match@
>= {
1751 if
(registers
['K'
].is_string
) free
(registers
['K'
].text
);
1752 registers
['K'
].is_string
=0;
1753 registers
['K'
].number
=best
;
1756 @ When matching keywords
, all of them will be tried
, in case there are
1757 better candidates for the search
(bigger is better
(so
, for example
,
1758 |
"Power of One"| will override |
"Power"|
); failing that
, later ones are
1759 better than earlier ones
(so that user files can override keywords in
1762 @^Courtenay
, Bryce@
>
1763 @^Houghton
, Israel@
>
1766 @
<Try matching each keyword belonging to the category@
>= {
1768 foreach
(i
,keywords
) {
1769 if
(keywords.data
[i
].category
&mat->category &&
1770 strlen
(keywords.data
[i
].match
)>=best_length
) {
1771 @
<Set up the |match_info| structure called |m|@
>;
1772 @
<Attempt applying this keyword match@
>;
1777 @ @
<Set up the |match_info| structure called |m|@
>= {
1778 sprintf
(mstr
,"%s%c%c%c",
1779 keywords.data
[i
].match
,match_table
,t
,successful_match
);
1780 m.src
=m.truesrc
=ptr
;
1784 @ @
<Attempt applying this keyword match@
>= {
1785 if
(match_pattern
(&m)) {
1787 best_length
=strlen
(keywords.data
[i
].match
);
1791 @
*Sorting and Grouping. The card lists can be sorted
/grouped using these
1792 commands
, which are generally used by macros that create the records for
1793 the cards in the card areas.
1795 @
<Cases for system commands@
>=
1797 // Add a new list entry
1798 data_index n
=new_record
(card_list
);
1799 card_list.data
[n
].token_ptr
=
1800 card_areas.data
[set_card_area
(registers
['C'
].number
)].used
1805 // Set a field value of the list entry
1806 data_index n
=card_list.used-1
;
1809 if
(n
==none
) program_error
("No card list is available");
1810 card_list.data
[n
].field
[x
&31]=y;
1814 @ Other than the commands to make the list entries above
, there must be
,
1815 of course
, the actual sorting and grouping being done
!
1817 Sorting and grouping are controlled by the \.G table. Starting from a
1818 given offset
(added
), you use thirty-two entries for the thirty-two
1821 @
<Cases for system commands@
>=
1824 sorting_table_offset
=pop_num
();
1825 qsort
(card_list.data
,card_list.used
,sizeof
(list_entry
),list_compare
);
1826 @
<Mark positions in the sorted list@
>;
1830 @ @
<Global variables@
>=
1831 int sorting_table_offset
;
1833 @ This is the compare function for the list sorting. It is also worth to
1834 notice here what values belong in the \.G table.
1837 @d primary_ascending 'A'
1838 @d primary_descending 'Z'
1839 @d secondary_ascending 'a'
1840 @d secondary_descending 'z'
1841 @d record_sorted_position 'R'
1843 @d G_table
(_field
) (tables
['G'
][((sorting_table_offset
+(_field
))&0xFF)])
1844 @d p1s
((list_entry
*)p1
)
1845 @d p2s
((list_entry
*)p2
)
1847 @
-p int list_compare
(const void
*p1
,const void
*p2
) {
1848 @
<Compare using fields indicated by \.G table@
>;
1849 @
<Compare using the card's name and the \.S table@
>;
1850 @
<Compare using the order in which the cards are typed in@
>;
1851 return
0; // This can't
, but will
, happen.
1854 @ @
<Compare using fields indicated by \.G table@
>= {
1856 for
(i
=0;i
<32;i
++) if
(p1s-
>field
[i
]!=p2s-
>field
[i
]) {
1857 if
(G_table
(i
)==primary_ascending
) {
1858 return
(p1s-
>field
[i
]>p2s-
>field
[i
])?
1:-1;
1859 } @
+else if
(G_table
(i
)==primary_descending
) {
1860 return
(p1s-
>field
[i
]<p2s-
>field
[i
])?
1:-1;
1863 for
(i
=0;i
<32;i
++) if
(p1s-
>field
[i
]!=p2s-
>field
[i
]) {
1864 if
(G_table
(i
)==secondary_ascending
) {
1865 return
(p1s-
>field
[i
]>p2s-
>field
[i
])?
1:-1;
1866 } @
+else if
(G_table
(i
)==secondary_descending
) {
1867 return
(p1s-
>field
[i
]<p2s-
>field
[i
])?
1:-1;
1872 @ When all else fails
, \strike
{play dead
} use the order in which the cards
1873 have been typed in. This is how it is made stable
, and that you can get
1874 the same results on any computer.
1878 @
<Compare using the order in which the cards...@
>= {
1879 if
(p1s-
>token_ptr
>p2s-
>token_ptr
) return
1;
1880 if
(p1s-
>token_ptr
<p2s-
>token_ptr
) return
-1;
1883 @ The last thing to do after sorting
, is mark positions in the list if it
1884 is requested to do so.
1886 @
<Mark positions in the sorted list@
>= {
1890 if
(G_table
(j
)==record_sorted_position
) {
1891 foreach
(i
,card_list
) card_list.data
[i
].field
[j
]=i
;
1896 @ @
<Display the sorted card list@
>= {
1899 foreach
(i
,card_list
) {
1900 printf
("%d=[ ",card_list.data
[i
].token_ptr
);
1901 for
(j
=0;j
<16;j
++) printf
("%d ",card_list.data
[i
].field
[j
]);
1906 @
*Natural Sorting. A natural compare algorithm is used here. It is a
1907 generalization of Martin Pool's algorithm\biblio
{Pool
, Martin. ``Natural
1908 Order String Comparison''.
{\tt
1909 http
://sourcefrog.net
/projects
/natsort
/}.
}.
1911 The \.S table maps from character tokens to the sorting specifications.
1912 Name tokens are converted to |whatsit| when looking up in this table.
1914 Tokens are grouped into digits
, letters
, and priority letters. There are
1915 also some extras
, such as spaces and radix point. A string of consecutive
1916 digits is treated as numeric
, so a number with more digits comes after a
1917 number with less digits.
1919 Priority letters are used mainly for sorting roman numerals. Two or more
1920 consecutive priority letters are considered as a group
, otherwise they are
1921 treated in the same way as ordinary letters. A group is ranked with the
1922 letters latest in the alphabet
, so for example
, if |'I'| and |'
X'| are
1923 priority
, then |
"IX"| is placed between |
"W"| and |
"X"|. This way
, all
1924 roman numerals from I to XXXIX will be sorted correctly.
1933 @d nat_radix_point
4
1935 @d nat_digit_zero
64 // digits go up to
127
1936 @d nat_first_letter
128 // letters go up to
191
1937 @d nat_first_priority_letter
192 // priority letters go up to
255
1938 @d nat_high_value
256
1940 @
<Compare using the card's name and the \.S table@
>= {
1941 token
*pa
=card_areas.data
[set_card_area
(registers
['C'
].number
)].tokens
1943 token
*pb
=card_areas.data
[set_card_area
(registers
['C'
].number
)].tokens
1945 boolean fractional
=0; // Are we reading digits after a radix point?
1948 begin_natural_compare_loop
: @
/
1949 a
=tables
['S'
][*pa
>=256?whatsit
:*pa
];
1950 @
+ b
=tables
['S'
][*pb
>=256?whatsit
:*pb
];
1951 @
<Skip over leading spaces and
/or zeros@
>;
1952 @
<Process a run of digits@
>;
1953 @
<Check if the end of either string is reached@
>;
1954 @
<Check for a radix point@
>;
1955 @
<Process priority letters@
>;
1956 @
<Check if the current positions of each string sufficiently differ@
>;
1960 @ @
<Skip over leading spaces and
/or zeros@
>= {
1961 while
(a
==nat_space||a
==nat_ignore||
(!fractional
&&a==nat_digit_zero)) {
1962 int aa
=tables
['S'
][pa
[1]>=256?whatsit
:pa
[1]];
1963 if
(a
!=nat_ignore
) fractional
=0;
1964 if
(!fractional
&& a==nat_digit_zero
1965 && aa>=nat_digit_zero && aa<nat_first_letter) break;
1966 pa
++; @
+ a
=tables
['S'
][*pa
>=256?whatsit
:*pa
];
1968 while
(b
==nat_space||b
==nat_ignore||
(!fractional
&&b==nat_digit_zero)) {
1969 int bb
=tables
['S'
][pa
[1]>=256?whatsit
:pa
[1]];
1970 if
(b
!=nat_ignore
) fractional
=0;
1971 if
(!fractional
&& b==nat_digit_zero
1972 && bb>=nat_digit_zero && bb<nat_first_letter) break;
1973 pb
++; @
+ b
=tables
['S'
][*pb
>=256?whatsit
:*pb
];
1977 @ @
<Process a run of digits@
>= {
1978 if
(a
>=nat_digit_zero
&&a<nat_first_letter&&
1979 b
>=nat_digit_zero
&&b<nat_first_letter) {
1980 if
((c
=(fractional?compare_left
:compare_right
)(pa
,pb
))) return c
;
1981 @
<Skip the run of digits
, since they are the same@
>;
1987 @ Compare two left-aligned numbers
: the first to have a different value
1988 wins. This function and |compare_right| are basically equivalent
, there
1989 are only a few differences
(this one is the simpler one
).
1991 @
-p int compare_left
(token
*pa
,token
*pb
) {
1994 a
=tables
['S'
][*pa
>=256?whatsit
:*pa
];
1995 @
+ b
=tables
['S'
][*pb
>=256?whatsit
:*pb
];
1996 @
<Skip over ignored characters@
>;
1997 @
<If neither |a| nor |b| is digit
, |break|@
>;
1998 @
<If one is a digit and the other isn't
, the longest run wins@
>;
1999 @
<If both are different digits
, the greater one wins@
>;
2004 @ The longest run of digits wins. That aside
, the greatest value wins
, but
2005 we can't know that it will until we've scanned both numbers to know they
2006 have the same magnitude
, so we remember it in |bias|.
2008 @
-p int compare_right
(token
*pa
,token
*pb
) {
2012 a
=tables
['S'
][*pa
>=256?whatsit
:*pa
];
2013 @
+ b
=tables
['S'
][*pb
>=256?whatsit
:*pb
];
2014 @
<Skip over ignored characters@
>;
2015 @
<If neither |a| nor |b| is digit
, |break|@
>;
2016 @
<If one is a digit and the other isn't
, the longest run wins@
>;
2017 @
<If both are digits
, set the |bias|@
>;
2022 @ Ignored characters might be commas for grouping digits into thousands.
2024 @
<Skip over ignored characters@
>= {
2025 while
(a
==nat_ignore
) {
2026 pa
++; @
+ a
=tables
['S'
][*pa
>=256?whatsit
:*pa
];
2028 while
(b
==nat_ignore
) {
2029 pb
++; @
+ b
=tables
['S'
][*pb
>=256?whatsit
:*pb
];
2033 @ @
<If neither |a| nor |b| is digit
, |break|@
>= {
2034 if
(!(a
>=nat_digit_zero
&&a<nat_first_letter)&&
2035 !(b
>=nat_digit_zero
&&b<nat_first_letter)) break;
2038 @ @
<If one is a digit and the other isn't
, the longest run wins@
>= {
2039 if
(!(a
>=nat_digit_zero
&&a<nat_first_letter)) return -1;
2040 if
(!(b
>=nat_digit_zero
&&b<nat_first_letter)) return 1;
2043 @ @
<If both are different digits
, the greater one wins@
>= {
2044 if
(a
!=b
) return a-b
;
2047 @ @
<If both are digits
, set the |bias|@
>= {
2048 if
(a
!=b
&& !bias) bias=(a<b)?-1:1;
2051 @ @
<Skip the run of digits
, since they are the same@
>= {
2052 while
(a
>=nat_digit_zero
&&a<nat_first_letter) {
2053 pa
++; @
+ pb
++; @
+ a
=tables
['S'
][*pa
>=256?whatsit
:*pa
];
2055 b
=tables
['S'
][*pb
>=256?whatsit
:*pb
];
2058 @ @
<Check if the end of either string is reached@
>= {
2059 if
(a
==nat_end_low
&& b>nat_end_high) return -1;
2060 if
(b
==nat_end_low
&& a>nat_end_high) return 1;
2061 if
(a
==nat_end_high
&& b>nat_end_high) return 1;
2062 if
(b
==nat_end_high
&& a>nat_end_high) return -1;
2063 if
(a
<=nat_end_high
&& b<=nat_end_high) break; // tied
2066 @ A radix point must be followed by a digit
, otherwise it is considered to
2067 be punctuation
(and ignored
). Radix points come before digits in the
2068 sorting order
(|
".5"| comes before |
"5"|
).
2070 @
<Check for a radix point@
>= {
2071 if
(a
==nat_radix_point
&& b==nat_radix_point) {
2072 int aa
=tables
['S'
][pa
[1]>=256?whatsit
:pa
[1]];
2073 int bb
=tables
['S'
][pb
[1]>=256?whatsit
:pb
[1]];
2074 if
(aa
>=nat_digit_zero
&&aa<nat_first_letter
2075 &&bb>=nat_digit_zero&&bb<nat_first_letter) fractional=1;
2076 } @
+else if
(a
==nat_radix_point
) {
2077 int aa
=tables
['S'
][pa
[1]>=256?whatsit
:pa
[1]];
2078 if
(!(aa
>=nat_digit_zero
&&aa<nat_first_letter)) {
2079 pa
++; goto begin_natural_compare_loop
;
2081 } @
+else if
(b
==nat_radix_point
) {
2082 int bb
=tables
['S'
][pb
[1]>=256?whatsit
:pb
[1]];
2083 if
(!(bb
>=nat_digit_zero
&&bb<nat_first_letter)) {
2084 pb
++; goto begin_natural_compare_loop
;
2089 @ This is used so that |
"IX"| can be sorted between |
"VIII"| and |
"X"|. In
2090 normal alphabetical order
, |
"IX"| sorts before |
"V"|. This algorithm makes
2091 it so that doesn't happen. For example
: |a| is |'I'| and |aa|
(the
2092 character after |a| in the text
) is |'
X'|
(the check |aa
>a| ensures that
2093 it too is priority
, in addition to checking that |a| represents a negative
2094 part of a roman number
), and |b| is |'V'|. Now
, since |'V'| comes between
2095 |'I'| and |'
X'| in the alphabetical order
, the condition is checked to be
2096 valid and it overrides the later check.
2098 @
<Process priority letters@
>= {
2099 if
(a
>=nat_first_priority_letter
) {
2100 int aa
=tables
['S'
][pa
[1]>=256?whatsit
:pa
[1]];
2101 if
(aa
>a
&& b>=nat_first_letter && (b&63)>(a&63) && (b&63)<(aa&63))
2104 if
(b
>=nat_first_priority_letter
) {
2105 int bb
=tables
['S'
][pb
[1]>=256?whatsit
:pb
[1]];
2106 if
(bb
>b
&& a>=nat_first_letter && (a&63)>(b&63) && (a&63)<(bb&63))
2111 @ At this point
, |a| and |b| will both be |@
[@
]>=nat_radix_point|. Numbers
2112 always come after letters
(this rule is designed so that when a radix
2113 point is found after a number
, it will make a larger number
; otherwise it
2114 will be followed by a letter and therefore the one followed by the letter
2115 is lesser since it has no fractional part to make it greater
).
2117 @
<Check if the current positions of each string suffic...@
>= {
2118 if
(a
>=nat_first_priority_letter
) a-
=64;
2119 if
(b
>=nat_first_priority_letter
) b-
=64;
2120 if
(a
<nat_first_letter
) a
+=128;
2121 if
(b
<nat_first_letter
) b
+=128;
2122 if
(a
!=b
) return
(a
<b
)?
-1:1;
2125 @
*Statistics. After the card lists are created and sorted and grouped
, it
2126 can make statistics from them. It can be just a plain list
, or it can be
2127 in summary of groups
, measuring count
, minimum
, maximum
, mean
, median
, and
2130 First we do the simple iteration.
2138 @
<Cases for system commands@
>=
2140 // Iterate the card list
2142 char
*q
=pop_string
();
2143 if
(!stack_ptr
[1].is_string
) program_error
("Type mismatch");
2144 foreach
(i
,card_list
) {
2145 push_num
(card_list.data
[i
].token_ptr
);
2153 // Read a field from the card list
2157 foreach
(i
,card_list
) {
2158 if
(registers
['A'
].number
==card_list.data
[i
].token_ptr
)
2159 y
=card_list.data
[i
].field
[x
];
2165 @ That was simple
, see? Now to do gathering statistics of summary of
2166 groups
, which is a bit more complicated. The list is expected to be sorted
2167 by the group field primary
, and the statistics field ascending as
2168 secondary
, in order to make the correct calculation of the fields.
2170 @
<Cases for system commands@
>=
2172 // Gather statistics of groups
2174 int x
=pop_num
()&31; // field for grouping
2175 int y
=pop_num
()&31; // field to measure statistics with
2176 int sum1
,sum2
; // running totals of $s_1$ and $s_2$
2178 char
*q
=pop_string
(); // code to execute for each group
2179 if
(!stack_ptr
[1].is_string
) program_error
("Type mismatch");
2180 foreach
(i
,card_list
) {
2181 if
(card_list.data
[i
].field
[x
]!=card_list.data
[si
].field
[x
]) {
2182 @
<Send the results of the current group@
>;
2183 sum1
=sum2
=0; @
+ si
=i
;
2185 @
<Add to the running totals@
>;
2187 @
<Send the results of the current group@
>;
2192 @ Running totals are kept for two quantities called $s_1$ and $s_2$. There
2193 is also $s_0$
, but that can be calculated easily using subtraction
, so
2194 there is no need to keep a running total. If the sample values are denoted
2195 $x_k$
, the following equation represents the running totals
:
2196 $$s_j
=\sum_
{k
=1}^N
{x_k^j
}$$
(note that $s_0
=N$.
)
2200 @
<Add to the running totals@
>= {
2201 sum1
+=card_list.data
[i
].field
[y
];
2202 sum2
+=card_list.data
[i
].field
[y
]*card_list.data
[i
].field
[y
];
2205 @ Now we will send the results and call |q|. The results are sent to the
2206 stack in the following order
: $s_0$
, $s_1$
, $s_2$
, $Q_0$
, $
2Q_2$
, $Q_4$
2207 (where $Q_0$ is the minimum
, $Q_2$ the median
, and $Q_4$ the maximum
).
2209 From these results
, it is then possible to calculate the standard
2210 deviation
: $$\sigma
={1\over s_0
}\sqrt
{s_0s_2-s_1^
2}$$ and
2211 $$s
=\sqrt
{s_0s_2-s_1^
2\over s_0
(s_0-1
)}.$$
2215 @
<Send the results of the current group@
>= {
2216 push_num
(i-si
); // $s_0$
2217 push_num
(sum1
); // $s_1$
2218 push_num
(sum2
); // $s_2$
2219 push_num
(card_list.data
[si
].field
[y
]); // $Q_0$
2221 card_list.data
[(si
+i
)/2].field
[y
]+card_list.data
[(si
+i
+1)/2].field
[y
]
2223 push_num
(card_list.data
[i-1
].field
[y
]); // $Q_4$
2224 @# push_num
(card_list.data
[si
].token_ptr
); @
+ store
('A'
);
2228 @
*Random Pack Generation. Now the codes so that it can create random packs
2229 (such as booster packs
) by using the card lists and deck lists.
2231 A command |'P'| is used for evaluation of a deck list. It expects the deck
2232 list number and the code to execute for each card on the list.
2236 @
<Cases for system commands@
>=
2238 // Generate a random pack or deck
2239 data_index s
=set_deck_list
(pop_num
());
2240 data_index n
; // current deck list entry
2241 if
(stack_ptr
[1].is_string
) program_error
("Number expected");
2242 @
<Figure out what cards belong in the pack@
>;
2243 @
<Execute the code on the stack for each card in the pack@
>;
2247 @ @
<Figure out what cards belong in the pack@
>= {
2249 int tries
=1000; // How many times can you retry if it fails?
2251 if
(!--tries
) program_error
("No cards matched the deck criteria");
2253 @
<Reset |amount_in_pack| of each card to zero@
>;
2254 while
(n
!=none
&& (n=(e=deck_lists.data+n)->next)!=none)
2255 @
<Process this deck entry@
>;
2258 @ @
<Reset |amount_in_pack| of each card to zero@
>= {
2260 foreach
(i
,card_list
) card_list.data
[i
].amount_in_pack
=0;
2263 @ The deck entry must be processed according to the flags. Here is a list
2266 \.a
: Use all cards that meet the criteria
, instead of only one. If this is
2267 the case
, it is possible to use negative weights to remove cards from the
2268 pack. Also
, it cannot fail.
2269 [Combine with \.
{x
}]
2271 \.k
: Select without replacement. It is fail if the total weight is not
2272 enough. There are two ways in which this differs from \.u
(below
). One is
2273 that the previous lines in the deck list are not used. The other one is
2274 that if the weight is more than one
, there will be more than one ball for
2275 that card
, therefore the same card can be picked up multiple times.
2276 [Combine with \.
{sux
}]
2278 \.n
: Use the |amount| as a probability. If |amount
<=100| then the
2279 probability is |amount
/100| otherwise it is |
100/amount|. This is a
2280 probability of using the |name| to select another deck list instead of
2282 [Combine with nothing
]
2284 \.s
: Skip the next line if this line does not fail.
(Normally
, if one line
2285 fails
, everything does
, and you have to try again.
)
2286 [Combine with \.
{kux
}]
2288 \.u
: Require unique selection. It is fail if the card is already in this
2290 [Combine with \.
{ksx
}]
2292 \.x
: Pass the |name| as a string to the code in the \.U register
, and then
2293 use the resulting code as the code to determine weights instead of using
2294 the code in the register named by |name| directly. Now you can type things
2295 such as |
"12x Forest"| into your deck list.
2296 [Combine with \.
{aksu
}]
2298 @
<Process this deck entry@
>= {
2299 if
(e-
>flags
&lflag('n')) {
2300 @
<Determine whether or not to skip to another deck list@
>;
2302 char
*c
; // code for weights of each card
2303 int total
; // total weight of cards
2304 data_index
*bag
=malloc
(sizeof
(data_index
));
2305 @
<Get the code |c| for the weights of each card@
>;
2306 @
<Calculate the weights of each card@
>;
2307 if
(!(e-
>flags
&lflag('a')))
2308 @
<Select some of the cards at random and add them to the pack@
>;
2309 if
(e-
>flags
&lflag('x')) free(c);
2314 @ @
<Determine whether or not to skip to another deck list@
>= {
2316 if
(e-
>amount
<=100) {
2317 q
=(gen_random
(100)<e-
>amount
);
2319 q
=(100<gen_random
(e-
>amount
));
2321 if
(q
) n
=set_deck_list
(find_name
(e-
>name
));
2324 @ @
<Get the code |c| for the weights of each card@
>= {
2325 if
(e-
>flags
&lflag('x')) {
2326 execute_program
(registers
['U'
].text
);
2327 if
(stack_ptr-
>is_string
) {
2330 program_error
("Type mismatch");
2333 int n
=find_name
(e-
>name
);
2334 if
(name_info
(n
).value.is_string
) {
2335 c
=name_info
(n
).value.text
;
2337 program_error
("Type mismatch");
2342 @ @
<Calculate the weights of each card@
>= {
2344 foreach
(i
,card_list
) {
2345 registers
['A'
].number
=card_list.data
[i
].token_ptr
;
2347 if
(stack_ptr-
>number
) {
2348 if
(e-
>flags
&lflag('a')) {
2349 card_list.data
[i
].amount_in_pack
+=e-
>amount
*stack_ptr-
>number
;
2350 } @
+else if
(stack_ptr-
>number
>0) {
2351 @
<Add the cards to the |bag|@
>;
2358 @ The |bag| is like
, you put the balls in the bag so that you can mix it
2359 and take one out
, whatever number is on the ball is the card you put into
2360 the pack. Except
, that there is no balls and no bag.
2362 There is one ball per point of weight.
2366 @
<Add the cards to the |bag|@
>= {
2367 int j
=stack_ptr-
>number
;
2368 bag
=realloc
(bag
,(total
+j
)*sizeof
(data_index
));
2369 while
(j--
) bag
[total
+j
]=i
;
2370 total
+=stack_ptr-
>number
;
2373 @ If it is not a line which adds all possibilities at once
, then the cards
2374 must be selected from the |bag| at random to bag them. In some cases it
2377 @
<Select some of the cards at random and add them to the pack@
>= {
2379 int amount
=e-
>amount
;
2381 if
(!total
) @
<Deal with bag failure@
>;
2382 r
=gen_random
(total
);
2383 if
((e-
>flags
&lflag('u')) && card_list.data[bag[r]].amount_in_pack) {
2384 bag
[r
]=bag
[--total
];
2387 card_list.data
[bag
[r
]].amount_in_pack
++;
2388 if
(e-
>flags
&lflag('k')) bag[r]=bag[--total];
2389 if
(amount--
) goto bag_next
;
2390 @#if
(e-
>flags
&lflag('s')) n=deck_lists.data[n].next;
2394 @ @
<Deal with bag failure@
>= {
2395 if
(e-
>flags
&lflag('s')) goto bag_done;
2396 else goto figure_out_again
;
2399 @ Now it must do stuff using the list which is generated. The quantity for
2400 how many of that card is pushed on the stack
, and this is done even for
2401 cards with negative quantity
(but not for zero quantity
).
2403 @
<Execute the code on the stack for each card in the pack@
>= {
2405 char
*q
=pop_string
();
2406 if
(!stack_ptr
[1].is_string
) program_error
("Type mismatch");
2407 foreach
(i
,card_list
) {
2408 if
(card_list.data
[i
].amount_in_pack
) {
2409 push_num
(card_list.data
[i
].amount_in_pack
);
2416 @
*Reading Input Files. Now it is time for the part of the program where
2417 input files are read and processed. The areas of the file
(and other
2418 special commands
) are indicated using \.@@ signs.
2420 At first we have state information. Each state is labeled by uppercase
2421 letters
, or by digits
1 to
9. The high bit is set for the heading state
,
2422 meaning the first line that contains the name and
/or other heading
2428 @d execute_state 'E'
2430 @d include_state 'I'
2431 @d keyword_state 'K'
2432 @d pattern_state 'P'
2433 @d subroutine_state 'S'
2434 @d wordforms_state 'W'
2437 @
<Global variables@
>=
2439 data_index cur_name
;
2440 data_index cur_data
;
2441 boolean omit_line_break
;
2443 @ The next thing that must be kept track of for input files is the stack
2444 of open input files.
2446 @d max_pathname_length
128
2447 @d max_filename_length
128
2448 @d max_input_stack
128
2449 @d max_line_length
256
2453 FILE*fp
; // zero for terminal input
2454 char name
[max_filename_length
+1];
2458 @ @
<Global variables@
>=
2459 input_file_data input_files
[max_input_stack
];
2460 input_file_data
*current_input_file
=input_files
;
2461 char input_buffer
[max_line_length
];
2463 @ Some macros are useful to access the current file data.
2465 @d current_line
(current_input_file-
>line
)
2466 @d current_filename
(current_input_file-
>name
)
2467 @d current_fp
(current_input_file-
>fp
)
2469 @d parsing_error
(_text
) fprintf
(stderr
,"%s on line %d in %s\n",
2470 _text
,current_line
,current_filename
)@
;
2472 @ There is also conditional processing directives
, which uses a single
2473 variable to keep track of the level. If it is greater than zero
, the
2474 condition is false
, and it is increased for nesting conditions
(the
2475 nested conditions have no truth to them
).
2477 @
<Global variables@
>=
2478 int condition_level
=0;
2480 @ This subroutine inputs the next line. True is returned if there is a
2481 line
, or false if it is finished.
2483 It is necessary to check for end of file and if so
, close that file and
2484 try the one it was included from
; and if it is terminal input
, display the
2485 current state when prompting input from the user.
2487 @
-p boolean input_line
(void
) {
2488 input_line_again
: if
(current_fp
) {
2489 @
<Get a line of input from the file@
>;
2491 @
<Get a line of terminal input@
>;
2493 @
<Remove trailing |'\n'|
, |'\r'|
, and spaces@
>;
2498 @ @
<Get a line of input from the file@
>= {
2499 if
(!fgets
(input_buffer
,max_line_length
,current_fp
)) {
2500 memusage_log
("Closing input file",current_input_file-input_files
)@
;
2502 if
(current_input_file
>input_files
) {
2503 --current_input_file
;
2504 goto input_line_again
;
2511 @ @
<Get a line of terminal input@
>= {
2512 printf
("\n%c> ",cur_state?cur_state
:'
>'
);
2514 if
(!fgets
(input_buffer
,max_line_length
,stdin
)) return
0;
2517 @ This function is used in order to open another input file. One way is
2518 that the file might be included from another file
, or it might be the
2521 @
-p void open_input
(char
*name
) {
2522 if
(++current_input_file
>input_files
+max_input_stack
) {
2523 fprintf
(stderr
,"Too many simultaneous input files\n");
2526 memusage_log
("Opening input file",current_input_file-input_files
)@
;
2527 strcpy
(current_filename
,name
);
2529 current_fp
=fopen
(name
,"r");
2531 fprintf
(stderr
,"Cannot open input file: %s\n",name
);
2536 @ Trailing newlines and spaces are removed. On some computers
, there will
2537 be a carriage return before the line feed
, it should be removed
, so that
2538 the same file will work on other computers
, too.
2540 @d last_character_input input_buffer
[strlen
(input_buffer
)-1]
2542 @
<Remove trailing |'\n'|
, |'\r'|
, and spaces@
>= {
2543 if
(last_character_input
=='\n'
) last_character_input
=0;
2544 if
(last_character_input
=='\r'
) last_character_input
=0;
2545 while
(last_character_input
==' '
) last_character_input
=0;
2548 @ The input states start at these values.
2550 @
<Initialize the input states@
>= {
2551 cur_state
=execute_state
;
2552 cur_name
=cur_data
=0;
2555 @ Now it is the time to do the actual processing according to the contents
2556 of the lines of the file. A line starting with \.@@ sign will indicate a
2557 special command
(to operate in all modes
) or a mode switch command.
2559 @d delete_chars
(_buf
,_c
) memmove
((_buf
),(_buf
)+(_c
),strlen
((_buf
)+(_c
))+1)
2561 @
<Process the input files@
>= {
2563 while
(input_line
()) {
2565 if
(condition_level
) {
2566 buf
+=strspn
(buf
," ");
2567 condition_level
+=!strcmp
(buf
,"@@<");
2568 condition_level-
=!strcmp
(buf
,"@@>");
2571 @
<Convert \.@@ commands in the |input_buffer|@
>;
2578 @ @
<Convert \.@@ commands in the |input_buffer|@
>= {
2579 char
*ptr
=input_buffer
;
2582 @
<Convert the current \.@@ command@
>;
2589 @ @
<Convert the current \.@@ command@
>= {
2592 delete_chars
(ptr
,1);
2594 case '.'
: @
<Process \.
{@@.
} command@
>;@
+break
;
2595 case '
&': @<Process \.{@@\&} command@>;@+break;
2596 case '^'
: @
<Process \.
{@@\^
} command@
>;@
+break
;
2597 case '
('
: @
<Process \.
{@@
(} command@
>;@
+break
;
2598 case '
<'
: @
<Process \.
{@@
<} command@
>;@
+break
;
2599 case '
>'
: /* Do nothing
*/ @
+break
;
2601 if
((*ptr
>='A'
&& *ptr<='Z') || (*ptr>='0' && *ptr<='9')) {
2602 @
<Enter a |heading| state@
>;
2604 parsing_error
("Unknown @@ command");
2609 @ Heading states are used for the first line of a section in the file.
2610 After that line is processed
, it becomes the corresponding non-heading
2611 state |
(cur_state
&~heading)|.
2613 Note
: The state |'
0'| is deliberately unused
; you might use it for
2614 documentation areas
, for example.
2616 @^documentation areas@
>
2618 @
<Enter a |heading| state@
>= {
2619 cur_state
=heading|
*ptr--
;
2620 delete_chars
(ptr
,2);
2621 while
(*ptr
==' ' ||
*ptr
=='\t'
) delete_chars
(ptr
,1);
2624 @ @
-p void process_line
(char
*buf
) {
2626 cur_state
&=~heading;
2628 case card_state
: @
<Process card state@
>;@
+break
;
2629 case deck_state
: @
<Process deck state@
>;@
+break
;
2630 case execute_state
: @
<Process execute state@
>;@
+break
;
2631 case file_state
: @
<Process file state@
>;@
+break
;
2632 case keyword_state
: @
<Process keyword state@
>;@
+break
;
2633 case pattern_state
: @
<Process pattern state@
>;@
+break
;
2634 case subroutine_state
: @
<Process subroutine state@
>;@
+break
;
2635 case wordforms_state
: @
<Process word forms state@
>;@
+break
;
2636 case card_state|heading
: @
<Process card heading@
>;@
+break
;
2637 case deck_state|heading
: @
<Process deck heading@
>;@
+break
;
2638 case file_state|heading
: @
<Process file heading@
>;@
+break
;
2639 case include_state|heading
: @
<Process include heading@
>;@
+break
;
2640 case keyword_state|heading
: @
<Process keyword heading@
>;@
+break
;
2641 case pattern_state|heading
: @
<Process pattern heading@
>;@
+break
;
2642 case subroutine_state|heading
: @
<Process subroutine heading@
>;@
+break
;
2643 default
: ; // nothing happens
2647 @ Sometimes you might want a macro which can send a line programmatically.
2648 So
, here is the way that it is done.
2650 @
<Cases for system commands@
>=
2652 // Process a line by programming
2653 if
(stack_ptr-
>is_string
) program_error
("Type mismatch");
2654 cur_state
=pop_num
()|heading
;
2655 if
(!stack_ptr-
>is_string
) program_error
("Type mismatch");
2657 process_line
(stack_ptr-
>text
);
2662 @
*Inner Commands. These are commands other than the section headings.
2664 @ The first command to deal with is simple--it is a comment. The rest of
2665 the current line is simply discarded.
2667 @
<Process \.
{@@.
} command@
>= {
2671 @ This command is a pattern split. It means it will process the part of
2672 the line before this command and then process the stuff after it. The
2673 variable |omit_line_break| is
1 if this command is used
; because it means
2674 there will not be a line break.
(Otherwise
, patterns and so on are split
2677 @
<Process \.
{@@\
&} command@>= {
2683 @ This allows control characters to be inserted into the input. This code
2684 takes advantage of the way the ASCII code works
, in which stripping all
2685 but the five low bits can convert a letter
(uppercase or lowercase
) to its
2686 corresponding control character.
2688 @^control character@
>
2690 @
<Process \.
{@@\^
} command@
>= {
2693 delete_chars
(ptr
,2);
2696 @ The following command is used to execute a code in a different state and
2697 then include the results in this area.
2699 @
<Process \.
{@@
(} command@
>= {
2702 @
<Skip over the name and save the rest of the line@
>;
2703 @
<Execute the code for the named subroutine@
>;
2704 @
<Insert the returned string and fix the line buffer@
>;
2707 @ @
<Skip over the name and save the rest of the line@
>= {
2709 while
(*ptr
&& *ptr!=')') ptr++;
2710 q
=strdup
(ptr
+!!*ptr
);
2714 @ @
<Execute the code for the named subroutine@
>= {
2716 execute_program
(fetch_code
(n
));
2719 @ @
<Insert the returned string and fix the line buffer@
>= {
2720 char
*s
=pop_string
();
2721 sprintf
(p-2
,"%s%s",s
,q
);
2727 @ This command is used for conditional processing. The condition value
2728 comes from the stack. Zero is false
, everything else is true.
2730 @
<Process \.
{@@
<} command@
>= {
2732 delete_chars
(ptr
,2);
2733 condition_level
=!stack_ptr-
>number
;
2737 @
*Card State. Cards are added to the card areas by using the card state.
2738 The \.C register tells which is the current card area
, and \.P register is
2739 used to select the current pattern area. The pattern area is used to match
2740 patterns after reading a line. Please note that it won't work to change
2741 the value of the \.C register during the card state.
2743 @
<Process card heading@
>= {
2744 int n
=find_name
(buf
);
2745 cur_data
=set_card_area
(n
);
2747 push_num
(n
);@
+store
('C'
);
2750 @ @
<Process card state@
>= {
2752 if
(!omit_line_break
) strcpy
(buf
+strlen
(buf
),"\n");
2753 @
<Initialize the \.W register@
>;
2754 b
=do_patterns
(strdup
(buf
),registers
['P'
].number
);
2755 if
(registers
['W'
].is_string
) execute_program
(registers
['W'
].text
);
2756 @
<Send the tokens of |b| and replace whatsits@
>;
2760 @ @
<Initialize the \.W register@
>= {
2761 if
(registers
['W'
].is_string
) free
(registers
['W'
].text
);
2762 registers
['W'
].is_string
=1;
2763 registers
['W'
].text
=strdup
("");
2766 @ @
<Send the tokens of |b| and replace whatsits@
>= {
2770 send_token
(cur_data
,pop_num
());
2772 send_token
(cur_data
,(*p
==1)?
0:*p
);
2777 @ There is one command you might want to send tokens in any other time.
2779 @
<Cases for system commands@
>=
2781 // Add tokens to the card area
2782 if
(stack_ptr-
>is_string
) {
2783 @
<Send tokens from the string on the stack@
>;
2785 send_token
(set_card_area
(registers
['C'
].number
),stack_ptr-
>number
);
2791 @ @
<Send tokens from the string on the stack@
>= {
2793 data_index q
=set_card_area
(registers
['C'
].number
);
2794 for
(p
=stack_ptr-
>text
;*p
;p
++) send_token
(q
,*p
);
2797 @
*Deck State. Deck state is used for creating deck lists and random packs.
2799 @
<Process deck heading@
>= {
2800 cur_name
=find_name
(buf
)-256;
2801 cur_data
=set_deck_list
(cur_name
+256);
2802 @
<Skip to the end of the deck list@
>;
2805 @ @
<Skip to the end of the deck list@
>= {
2806 while
(deck_lists.data
[cur_data
].next
!=none
)
2807 cur_data
=deck_lists.data
[cur_data
].next
;
2810 @ Now to parse each line in turn. Each line consists of a number
, the
2813 @
<Process deck state@
>= {
2814 int n
=strtol
(buf
,&buf,10);
2817 buf
+=strspn
(buf
,"\x20\t");
2818 @
<Read the flags for the deck list@
>;
2819 buf
+=strspn
(buf
,"\x20\t"); // Now we are at the point of the text
2820 @
<Send this line to the deck list@
>;
2821 @
<Create and advance to the new terminator of the deck list@
>;
2825 @ @
<Read the flags for the deck list@
>= {
2826 while
(*buf
>='a'
&& *buf<='z') f |=lflag(*buf++);
2827 buf
++; // Skip terminator of flags
2830 @ If the \.x flag is set
, it will be determined what to do with the text
2831 by the user-defined code. Otherwise
, it is always a name
, so we can save
2832 memory by pointing to the name buffer
(since name buffers never vary
).
2834 @
<Send this line to the deck list@
>= {
2836 deck_lists.data
[cur_data
].name
=strdup
(buf
);
2838 deck_lists.data
[cur_data
].name
=name_info
(find_name
(buf
)).name
;
2842 @ @
<Create and advance to the new terminator of the deck list@
>= {
2843 data_index i
=new_record
(deck_lists
);
2844 deck_lists.data
[cur_data
].next
=i
;
2845 deck_lists.data
[cur_data
=i
].next
=none
;
2848 @
*Execute State. This state is simple
, just execute stack codes. It is the
2849 initial state
; you can use it with terminal input
, too.
2851 @
<Process execute state@
>= {
2852 execute_program
(buf
);
2855 @
*File State. This state is used to make list of output files. Each one is
2856 stored as a string
, like subroutine state. The difference is that newlines
2857 will not be discarded. The other difference is that there is a flag in the
2858 |name_data| record for it that tells it that it is a file that should be
2861 @
<More elements of |name_data|@
>=
2862 boolean is_output_file
;
2864 @ @
<Process file heading@
>= {
2865 cur_name
=find_name
(buf
)-256;
2866 if
(!names.data
[cur_name
].value.is_string
) {
2867 names.data
[cur_name
].value.is_string
=1;
2868 names.data
[cur_name
].value.text
=strdup
("");
2869 names.data
[cur_name
].is_output_file
=1;
2873 @ @
<Process file state@
>= {
2874 int z
=strlen
(names.data
[cur_name
].value.text
);
2875 if
(!omit_line_break
) strcpy
(buf
+strlen
(buf
),"\n");
2876 names.data
[cur_name
].value.text
=realloc
(names.data
[cur_name
].value.text
,
2878 strcpy
(names.data
[cur_name
].value.text
+z
,buf
);
2881 @
*Include State. The include state causes inclusion of another source file
2884 @
<Process include heading@
>= {
2885 cur_state
=execute_state
;
2886 @
<Push the include file onto the input stack@
>;
2887 @
<Attempt to open the include file...@
>;
2890 @ @
<Push the include file onto the input stack@
>= {
2891 ++current_input_file
;
2892 memusage_log
("Opening input file",current_input_file-input_files
)@
;
2893 strcpy
(current_filename
,buf
);
2898 @ Include files are searched using the search path specified in the
2899 environment variable called \.
{TEXNICARDPATH
}, which is a list of paths
2900 delimited by colons on
UNIX systems
(including Cygwin
), but semicolons on
2901 Windows
(colons are used in Windows for drive letters
). A forward slash is
2902 the path separator. Please note that if you want to use include files in
2903 the current directory
, you must include |
"."| as the first entry in the
2910 @
<Set |includepath_separator| depending on operating system@
>=
2912 #define @
!includepath_separator '
;'
2914 #define includepath_separator '
:'
2917 @ @
<Attempt to open the include file by finding it in the search path@
>= {
2918 char searchpath
[max_pathname_length
+max_filename_length
+1];
2920 char
*npath
=getenv
("TEXNICARDPATH");
2921 strcpy
(searchpath
,npath?npath
:".");
2922 npath
=cpath
=searchpath
;
2923 @
<Set |includepath_separator| depending on operating system@
>;
2924 @
<Attempt to open the file from each each directory in the search path@
>;
2925 @
<It is a fatal error if no such file was found@
>;
2928 @ @
<Attempt to open the file from each each directory...@
>= {
2929 while
(!current_fp
) {
2930 char f
[max_pathname_length
+max_filename_length
+1];
2931 @
<Select the next path name into |cpath| and |npath|@
>;
2932 sprintf
(f
,"%s/%s",cpath
,current_filename
);
2933 current_fp
=fopen
(f
,"r");
2937 @ @
<Select the next path name into |cpath| and |npath|@
>= {
2938 if
(!(cpath
=npath
)) break
;
2939 if
((npath
=strchr
(npath
,includepath_separator
))) *npath
++=0;
2942 @ @
<It is a fatal error if no such file was found@
>= {
2944 fprintf
(stderr
,"%s not found in search path.\n",current_filename
);
2949 @
*Keyword State. You can add keywords to the keyword area by using this.
2950 Each keyword heading is one entry in the list.
2952 @
<Process keyword heading@
>= {
2953 cur_data
=new_record
(keywords
);
2954 keywords.data
[cur_data
].match
=strdup
(buf
);
2955 keywords.data
[cur_data
].replacement
=strdup
("");
2958 @ @
<Process keyword state@
>= {
2959 keyword_data
*k
=&keywords.data[cur_data];
2961 k-
>category|
=find_category
(buf
+1);
2963 if
(!omit_line_break
) strcpy
(buf
+strlen
(buf
),"\n");
2964 @
<Append buffer to keyword text@
>;
2968 @ @
<Append buffer to keyword text@
>= {
2970 int z
=strlen
(k-
>replacement
);
2971 k-
>replacement
=realloc
(k-
>replacement
,z
+strlen
(buf
)+1);
2972 strcpy
(k-
>replacement
+z
,buf
);
2976 @
*Pattern State. This state compiles patterns into a pattern area. It
2977 uses its own syntax
, and then is converted into the proper control codes
2978 for the |text| of a pattern.
2980 @
<Process pattern heading@
>= {
2981 cur_name
=find_name
(buf
)-256;
2982 cur_data
=set_pattern_area
(cur_name
+256);
2985 @ The stuff inside the pattern state has its own commands.
2987 @
<Process pattern state@
>= {
2988 char add_buf
[1024]; // buffer of text to add to the current pattern
2989 pattern_data
*pat
=&pattern_areas.data[cur_data];
2992 case '
<'
: @
<Create a new pattern with top priority@
>;@
+break
;
2993 case '
>'
: @
<Create a new pattern with bottom priority@
>;@
+break
;
2994 case '
:'
: @
<Make a pattern text with a marker@
>;@
+break
;
2995 case '
+'
: @
<Add a keyword category to this pattern@
>;@
+break
;
2996 default
: ; // do nothing
2998 @
<Append contents of |add_buf| to the pattern
, if needed@
>;
3001 @ @
<Create a new pattern with top priority@
>= {
3002 cur_data
=new_record
(pattern_areas
);
3003 pattern_areas.data
[cur_data
].text
=strdup
("");
3004 pattern_areas.data
[cur_data
].subroutine
=find_name
(buf
+1)-256;
3005 pattern_areas.data
[cur_data
].next
=names.data
[cur_name
].pattern_area
;
3006 names.data
[cur_name
].pattern_area
=cur_data
;
3009 @ @
<Create a new pattern with bottom priority@
>= {
3011 cur_data
=new_record
(pattern_areas
);
3012 pattern_areas.data
[cur_data
].text
=strdup
("");
3013 pattern_areas.data
[cur_data
].subroutine
=find_name
(buf
+1)-256;
3014 pattern_areas.data
[cur_data
].next
=none
;
3015 @
<Find the bottom pattern and store its index in |n|@
>;
3016 pattern_areas.data
[n
].next
=cur_data
;
3019 @ @
<Find the bottom pattern and...@
>= {
3020 n
=names.data
[cur_name
].pattern_area
;
3021 while
(pattern_areas.data
[n
].next
!=none
&& pattern_areas.data[n].text &&
3022 pattern_areas.data
[pattern_areas.data
[n
].next
].next
!=none
)
3023 n
=pattern_areas.data
[n
].next
;
3026 @ Actually
, the name of this \strike
{cake
} chunk is a lie
, because it does
3027 not always add a marker.
3029 @
<Make a pattern text with a marker@
>= {
3032 @
<Add the pattern marker if applicable@
>;
3033 for
(p
=buf
+2;p
[-1] && *p;p++) {
3035 case '\\'
: *b
++=*++p
; @
+break
;
3036 case '
('
: *b
++=begin_capture
; @
+break
;
3037 case '
)'
: *b
++=end_capture
; @
+break
;
3038 case '
%'
: *b
++=match_keyword
; @
+*b
++=*++p
; @
+break
;
3039 case '
!'
: *b
++=match_table
; @
+*b
++=*++p
; @
+break
;
3040 case '?'
: *b
++=optional_table
; @
+*b
++=*++p
; @
+break
;
3041 case '#'
: *b
++=failed_match
; @
+break
;
3042 case '
&': *b++=jump_table; @+*b++=*++p; @+break;
3043 case '
;'
: *b
++=successful_match
; @
+break
;
3044 case '
<'
: *b
++=back_one_space
; @
+break
;
3045 case '
>'
: *b
++=forward_one_space
; @
+break
;
3046 case '
['
: *b
++=match_left_side
; @
+break
;
3047 case '
]'
: *b
++=match_right_side
; @
+break
;
3048 default
: *b
++=*p
; @
+break
;
3054 @ @
<Add the pattern marker if applicable@
>= {
3055 if
(buf
[1]>' '
) *b
++=buf
[1]|
0x80;
3058 @ @
<Add a keyword category to this pattern@
>= {
3059 pattern_areas.data
[cur_data
].category
=find_category
(buf
+1);
3062 @ @
<Append contents of |add_buf| to the pattern...@
>= {
3064 int z
=strlen
(pat-
>text
);
3065 pat-
>text
=realloc
(pat-
>text
,z
+strlen
(add_buf
)+1);
3066 strcpy
(pat-
>text
+z
,add_buf
);
3070 @
*Subroutine State. This state is used to add a named subroutine.
3072 @
<Process subroutine heading@
>= {
3073 cur_name
=find_name
(buf
)-256;
3074 if
(!names.data
[cur_name
].value.is_string
) {
3075 names.data
[cur_name
].value.is_string
=1;
3076 names.data
[cur_name
].value.text
=strdup
("");
3080 @ @
<Process subroutine state@
>= {
3081 int z
=strlen
(names.data
[cur_name
].value.text
);
3082 names.data
[cur_name
].value.text
=realloc
(names.data
[cur_name
].value.text
,
3084 strcpy
(names.data
[cur_name
].value.text
+z
,buf
);
3087 @
*Word Forms State. You can use the word forms state to enter rules and
3088 exceptions for word forms
, such as plurals.
3090 @
<Global variables@
>=
3091 char wordform_code
[256]; // code to execute at \.
= line
3092 char wordform_kind
; // which kind of word forms is being made now?
3094 @ @
<Process word forms state@
>= {
3096 case '
>'
: @
<Process \.
> line in word forms state@
>; @
+break
;
3097 case '
='
: @
<Process \.
= line in word forms state@
>; @
+break
;
3098 case '#'
: wordform_kind
=buf
[1]; @
+break
;
3099 default
: if
(*buf
>='
0'
&& *buf<='9')
3100 @
<Process numeric line in word forms state@
>;
3104 @ The commands are \.
>, \.
=, and numbers. The command \.
> sets a code for
3105 processing \.
= commands
, and then add to the list.
3107 @
<Process \.
> line in word forms state@
>= {
3108 strcpy
(wordform_code
,buf
+1);
3111 @ @
<Process \.
= line in word forms state@
>= {
3116 execute_program
(wordform_code
);
3117 kind
=pop_num
(); @
+ level
=pop_num
();
3118 dest
=pop_string
(); @
+ orig
=pop_string
();
3119 add_word_form
(kind
,level
,orig
,dest
);
3120 free
(orig
); @
+ free
(dest
);
3123 @ Now the command for numeric forms. You put ``level\.
:orig\.
:dest'' in
3126 @
<Process numeric line in word forms state@
>= {
3127 int level
=strtol
(buf
,&buf,0);
3129 if
(*buf
=='
:'
) buf
++;
3132 add_word_form
(wordform_kind
,level
,buf
,p
+1);
3135 @
*Writing Output Files. Finally
, it will be time to send any output
3136 generated into the files
(if there is any
, which there usually is
).
3140 @d ctrl
(_letter
) (0x1F&(_letter))
3142 @d call_final_subroutine ctrl
('C'
)
3143 @d copy_field ctrl
('F'
)
3144 @d newline ctrl
('J'
)
3145 @d loop_point ctrl
('L'
)
3146 @d next_record ctrl
('N'
)
3147 @d skip_one_character ctrl
('S'
)
3149 @
<Write the output files@
>= {
3152 if
(names.data
[n
].is_output_file
&& names.data[n].value.is_string)
3153 @
<Write this output file@
>;
3157 @ @
<Write this output file@
>= {
3158 FILE*fout
=fopen
(names.data
[n
].name
,"w");
3159 char
*ptr
=names.data
[n
].value.text
;
3160 char
*loopptr
=ptr
; // loop point
3161 if
(!fout
) @
<Error about unable to open output file@
>;
3162 while
(*ptr
) @
<Write the character and advance to the next one@
>;
3166 @ @
<Error about unable to open output file@
>= {
3167 fprintf
(stderr
,"Unable to open output file: %s\n",names.data
[n
].name
);
3171 @ @
<Write the character and advance to the next one@
>= {
3173 case call_final_subroutine
: @
<Do |call_final_subroutine|@
>; @
+break
;
3174 case copy_field
: @
<Do |copy_field|@
>; @
+break
;
3175 case loop_point
: loopptr
=++ptr
; @
+break
;
3176 case next_record
: @
<Do |next_record|@
>; @
+break
;
3177 case skip_one_character
: ptr
+=2; @
+break
;
3178 default
: fputc
(*ptr
++,fout
);
3180 done_writing_one_character
: ;
3183 @ @
<Do |call_final_subroutine|@
>= {
3186 char
*p
=strchr
(ptr
,'
)'
);
3188 v
=&name_info(find_name(ptr+1)).value;
3192 v
=®isters[*ptr++];
3195 execute_program
(v-
>text
);
3196 @
<Write or loop based on result of subroutine call@
>;
3201 @ @
<Write or loop based on result of subroutine call@
>= {
3202 if
(stack_ptr-
>is_string
) {
3203 fprintf
(fout
,"%s",stack_ptr-
>text
);
3204 } @
+else if
(stack_ptr-
>number
) {
3209 @ This command is used to copy the next field.
3211 Look at the definition for the |send_reg_char_or_text| macro. It is
3212 strange
, but it should work wherever a statement is expected. Please note
3213 that a ternary condition operator should have both choices of the same
3218 @d tok_idx
(registers
['A'
].number
)
3220 (card_areas.data
[name_info
(registers
['C'
].number
).value.number
].tokens
)
3222 @d send_reg_char_or_text
(_reg
)
3223 if
(!registers
[_reg
].is_string ||
*registers
[_reg
].text
)
3224 fprintf
(fout
, "%c%s",
3225 registers
[_reg
].is_string?
3226 *registers
[_reg
].text
:registers
[_reg
].number
,
3227 registers
[_reg
].is_string?
3228 registers
[_reg
].text
+1:(unsigned char
*)""
3231 @
<Do |copy_field|@
>= {
3234 switch
(tok_area
[tok_idx
++]) {
3235 case null_char
: @
<Unexpected |null_char|@
>;
3236 case end_transmission
: tok_idx
=0; @
+goto done_writing_one_character
;
3237 case tabulation
: send_reg_char_or_text
('T'
); @
+break
;
3238 case raw_data
: @
<Do |raw_data|@
>; @
+break
;
3239 case escape_code
: send_reg_char_or_text
('E'
); @
+break
;
3240 case record_separator
: tok_idx--
; @
+goto done_writing_one_character
;
3241 case field_separator
: @
+goto done_writing_one_character
;
3243 if
(tok_area
[--tok_idx
]&~0xFF)
3244 @
<Deal with name code@
>@
;
3246 @
<Deal with normal character@
>;
3252 @ @
<Unexpected |null_char|@
>= {
3253 fprintf
(stderr
,"Unexpected null character found in a card area\n");
3257 @ @
<Do |raw_data|@
>= {
3258 while
(tok_area
[tok_idx
]) fputc
(tok_area
[tok_idx
++],fout
);
3262 @ A name code found here is a code to tell it to call the subroutine code
3263 when it is time to write it out to the file. It should return a string on
3264 the stack
(if it is a number
, it will be ignored
).
3266 @
<Deal with name code@
>= {
3267 if
(name_info
(tok_area
[tok_idx
]).value.is_string
)
3268 execute_program
(name_info
(tok_area
[tok_idx
]).value.text
);
3269 if
(stack_ptr-
>is_string
) fprintf
(fout
,"%s",stack_ptr-
>text
);
3273 @ In case of a normal character
, normally just write it out. But some
3274 characters need escaped for \TeX.
3276 @
<Deal with normal character@
>= {
3277 if
(tables
['E'
][tok_area
[tok_idx
]]) send_reg_char_or_text
('E'
);
3278 fputc
(tok_area
[tok_idx
],fout
);
3281 @ This one moves to the next record
, looping if a next record is in fact
3282 available. Otherwise
, just continue. Note that a |record_separator|
3283 immediately followed by a |end_transmission| is assumed to mean there is
3284 no next record
, and that there is allowed to be a optional
3285 |record_separator| just before the |end_transmission|.
3287 @
<Do |next_record|@
>= {
3289 while
(tok_area
[tok_idx
]!=record_separator
&&
3290 tok_area
[tok_idx
]!=end_transmission
) tok_idx
++;
3291 if
(tok_area
[tok_idx
]!=end_transmission
&&
3292 tok_area
[tok_idx
+1]!=end_transmission
) ptr
=loopptr
;
3295 @
*Functions Common to DVI and GF. Numbers for \.
{GF
} and \.
{DVI
} files use
3296 the |dvi_number| data type.
(Change this in the change file if the current
3297 setting is inappropriate for your system.
)
3299 There is also the |dvi_measure| type
, which is twice as long and is used
3300 to compute numbers that can be fractional
(with thirty-two fractional bits
3301 and thirty-two normal bits
).
3304 @q
[Type of DVI numbers
::]@
>
3305 typedef signed int dvi_number
;
3306 typedef signed long long int dvi_measure
;
3307 @q
[::Type of DVI numbers
]@
>
3309 @ There is one subroutine here to read a |dvi_number| from a file. They
3310 come in different sizes and some are signed and some are unsigned.
3315 @
-p dvi_number get_dvi_number
(FILE*fp
,boolean is_signed
,int size
) {
3316 dvi_number r
=is_signed?
-1:0;
3317 while
(size--
) r
=(r
<<8)|fgetc
(fp
);
3321 @ Some macros are defined here in order to deal with |dvi_measure| values.
3325 @d to_measure
(_value
) (((dvi_measure
)(_value
))<<32)
3326 @d floor
(_value
) ((dvi_number
)((_value
)>>32))
3327 @d round
(_value
) ((dvi_number
)(((_value
)+0x8000)>>32))
3328 @d ceiling
(_value
) ((dvi_number
)(((_value
)+0xFFFF)>>32))
3329 @d make_fraction
(_n
,_d
) ((dvi_measure
)(to_measure
(_n
)/(_d
)))
3333 At first
, names will be given for the commands in a \.
{GF
} file. Many
3334 commands have the same numbers as they do in a \.
{DVI
} file
(described in
3335 the next chapter
), which makes it very convenient.
3337 @d paint_0
0 // Paint $d$ pixels black or white
[up to
63]
3338 @d paint1
64 // Take parameter
, paint pixels
[up to
66]
3339 @d boc
67 // Beginning of a character picture
3340 @d boc1
68 // Short form of |boc|
3341 @d eoc
69 // End of a character picture
3342 @d skip0
70 // Skip some rows
[up to
73]
3343 @d new_row_0
74 // Start a new row and move right
[up to
238]
3344 @d yyy
243 // Numeric specials
3345 @d no_op
244 // No operation
3346 @d char_loc
245 // Character locator
3347 @d char_loc0
246 // Short form of |char_loc|
3349 @
*DVI Reading. The device-independent file format is a format invented by
3350 David R.~Fuchs in
1979. The file format need not be explained here
, since
3351 there are other books which explain it\biblio
{Knuth
, Donald. ``\TeX
: The
3352 Program''. Computers
{\char`\
&} Typesetting. ISBN 0-201-13437-3.}\biblio{%
3353 Knuth
, Donald. ``\TeX ware''. Stanford Computer Science Report
1097.
}.
3355 \edef\TeXwareBiblio
{\the\bibliocount
}
3358 @^device independent@
>
3360 At first
, names will be given for the commands in a \.
{DVI
} file.
3362 @d set_char_0
0 // Set a character and move
[up to
127]
3363 @d set1
128 // Take one parameter to set character
[up to
131]
3364 @d set_rule
132 // Set a rule and move down
, two parameters
3365 @d put1
133 // As |set1| but no move
[up to
136]
3366 @d put_rule
137 // As |set_rule| but no move
3367 @d nop
138 // No operation
3368 @d bop
139 // Beginning of a page
3369 @d eop
140 // End of a page
3370 @d push
141 // Push $
(h
,v
,w
,x
,y
,z
)$ to the stack
3371 @d pop
142 // Pop $
(h
,v
,w
,x
,y
,z
)$ from the stack
3372 @d right1
143 // Take one parameter
, move right
[up to
146]
3373 @d w0
147 // Move right $w$ units
3374 @d w1
148 // Set $w$ and move right
[up to
151]
3375 @d x0
152 // Move right $x$ units
3376 @d x1
153 // Set $x$ and move right
[up to
156]
3377 @d down1
157 // Take one parameter
, move down
[up to
160]
3378 @d y0
161 // Move down $y$ units
3379 @d y1
162 // Set $y$ and move down
[up to
165]
3380 @d z0
166 // Move down $z$ units
3381 @d z1
167 // Set $z$ and move down
[up to
170]
3382 @d fnt_num_0
171 // Select font
0 [up to
234]
3383 @d fnt1
235 // Take parameter to select font
[up to
238]
3384 @d xxx1
239 // Specials
[up to
242]
3385 @d fnt_def1
243 // Font definitions
[up to
246]
3386 @d pre
247 // Preamble
3387 @d post
248 // Postamble
3388 @d post_post
249 // Postpostamble
3390 @ We should now start reading the \.
{DVI
} file. Filenames of fonts being
3391 used will be sent to standard output.
3393 @
-p void read_dvi_file
(char
*filename
) {
3394 FILE*fp
=fopen
(filename
,"rb");
3395 if
(!fp
) dvi_error
(fp
,"Unable to open file");
3396 @#@
<Skip the preamble of the \.
{DVI
} file@
>;
3397 @
<Compute the conversion factor@
>;
3398 @
<Skip to the next page@
>;
3399 @
<Read the metapage heading@
>;
3401 @
<Skip to and read the postamble@
>;
3402 @
<Read the font definitions and load the fonts@
>;
3406 @ @
-p void dvi_error
(FILE*fp
,char
*text
) {
3407 fprintf
(stderr
,"DVI error");
3408 if
(fp
) fprintf
(stderr
," at %08X",ftell
(fp
));
3409 fprintf
(stderr
,": %s\n",text
);
3413 @ Please note the version number of the \.
{DVI
} file must be
2.
3415 @
<Skip the preamble of the \.
{DVI
} file@
>= {
3416 if
(fgetc
(fp
)!=pre
) dvi_error
(fp
,"Bad preamble");
3417 if
(fgetc
(fp
)!=2) dvi_error
(fp
,"Wrong DVI version");
3418 @
<Read the measurement parameters@
>;
3421 @ @
<Read the measurement parameters@
>= {
3422 unit_num
=get_dvi_number
(fp
,0,4);
3423 unit_den
=get_dvi_number
(fp
,0,4);
3424 unit_mag
=get_dvi_number
(fp
,0,4);
3427 @ From the postamble we can read the pointer for the last page.
3429 @
<Global variables@
>=
3430 dvi_number last_page_ptr
;
3432 @ @
<Skip to and read the postamble@
>= {
3434 fseek
(fp
,-4,SEEK_END
);
3435 while
(fgetc
(fp
)==223) fseek
(fp
,-2,SEEK_CUR
);
3436 fseek
(fp
,-5,SEEK_CUR
);
3437 fseek
(fp
,get_dvi_number
(fp
,0,4)+1,SEEK_SET
);
3438 last_page_ptr
=get_dvi_number
(fp
,0,4);
3439 fseek
(fp
,24,SEEK_CUR
); // Skipped parameters of |post|
3442 @ Between the preamble and the first page can be |nop| commands and font
3443 definitions
, so these will be skipped. The same things can occur between
3444 the end of one page and the beginning of the next page.
3446 @
<Skip to the next page@
>= {
3451 if
(c
>=fnt_def1
&& c<fnt_def1+4) {
3452 fseek
(fp
,c
+13-fnt_def1
,SEEK_CUR
);
3453 @
<Skip the font filename before a page@
>;
3454 } @
+else if
(c
!=nop
) {
3455 fprintf
(stderr
,"Bad command between pages.\n");
3461 @ @
<Skip the font filename before a page@
>= {
3464 fseek
(fp
,a
+l
,SEEK_CUR
);
3467 @ The metapage includes the resolution and other things which must be set
,
3468 such as subroutine codes. The resolution must be read before fonts can be
3469 read. Please note that no characters can be typeset on the metapage
, since
3470 fonts have not been loaded yet. You can still place empty boxes. The DPI
3471 setting
(resolution
) is read from the \.
{\\count1
} register.
3473 @
<Read the metapage heading@
>= {
3474 dvi_number n
=get_dvi_number
(fp
,0,4);
3476 fprintf
(stderr
,"Metapage must be numbered zero (found %d).\n",n
);
3479 push_num
(get_dvi_number
(fp
,0,4)); @
+ store
('D'
);
3480 fseek
(fp
,9*4,SEEK_CUR
); // Skip other parameters
3483 @ A stack is kept of the page registers
, for use with the |push| and |pop|
3484 commands of a \.
{DVI
} file. This stack is used by the |read_dvi_page|
3485 subroutine and stores the |quan| registers
(described in the next
3500 @ @
<Global variables@
>=
3501 dvi_stack_entry
*dvi_stack
;
3502 int dvi_stack_count
;
3504 @ Here is the subroutine to read commands from a DVI page. The file
3505 position should be at the beginning of the page after the |bop| command.
3509 @
-p void read_dvi_page
(void
) {
3512 @ @
<Reset the page registers@
>= {
3513 quan
('A'
)=quan
('B'
)=quan
('H'
)=quan
('I'
)=quan
('J'
)=quan
('L'
)=quan
('V'
)=
3514 quan
('W'
)=quan
('
X'
)=quan
('
Y'
)=quan
('Z'
)=0;
3517 @
*Layer Computation. Now is the chapter for actually deciding rendering on
3518 the page
, where everything should go
, etc.$^
{[\TeXwareBiblio
]}$
3520 @
<Global variables@
>=
3521 dvi_number unit_num
; // Numerator for units of measurement
3522 dvi_number unit_den
; // Denominator for units of measurement
3523 dvi_number unit_mag
; // Magnification for measurement
3524 dvi_measure unit_conv
; // Conversion factor
3526 @ There are also a number of ``internal typesetting quantities''. These
3527 are parameters stored in a separate array
, and are used to keep track of
3528 the current state of the typesetting. They are labeled with letters from
3529 \.A to \.Z. They can be modified inside of specials
, although some of them
3530 probably shouldn't be modified in this way. Here is the list of them
:
3532 \.A
, \.B
: Horizontal and vertical offset added to \.I and \.J.
3534 \.D
: Maximum horizontal drift
(in pixels
), meaning how far away the \.I
3535 and \.J parameters are allowed to be from the correctly rounded values.
3537 \.E
: Maximum vertical drift.
3539 \.F
: The current font.
3541 \.H
: The horizontal position on the page
, in DVI units.
3543 \.I
: The horizontal position on the page
, in pixels.
3545 \.J
: The vertical position on the page
, in pixels.
3547 \.L
: The current layer number. If this is zero
, nothing is placed on the
3548 page
, although the positions can still be changed and specials can still
3551 \.P
: Page number. This is used to determine the filename of output.
3553 \.R
, \.S
: The limits for when horizontal motion should add the number of
3554 pixels or when it should recalculate the pixels entirely.
3556 \.T
, \.U
: Like \.R and \.S
, but for vertical motions.
3558 \.V
: The vertical position on the page
, in DVI units.
3560 \.W
, \.
X, \.
Y, \.Z
: The current spacing amounts
, in DVI units.
3562 @d quan
(_name
) (type_quan
[(_name
)&0x1F])
3564 @
<Global variables@
>=
3565 dvi_number type_quan
[32];
3567 @ @
<Cases for system commands@
>=
3569 // Modify an internal typesetting quantity
3570 if
(stack_ptr-
>is_string
) program_error
("Type mismatch");
3571 quan
(*++ptr
)=pop_num
();
3575 @ The conversion factor |unit_conv| is figured as follows
: There are
3576 exactly |unit_num
/unit_den| decimicrons per DVI unit
, and
254000
3577 decimicrons per inch
, and |resolution
/100| pixels per inch. Then we have
3578 to adjust this by the magnification |unit_mag|.
3580 @d resolution
(registers
['D'
])
3582 @
<Compute the conversion factor@
>= {
3583 unit_conv
=make_fraction
(unit_num
*resolution
*unit_mag
,unit_den
*100000);
3586 @
*Layer Rendering. Please note
, these numbers are |short|
, which means
3587 that you cannot have more than
65536 pixels in width or in height. This
3588 should not be a problem
, because even if you have
3000 dots per inch
, and
3589 each card is
10 inches long
, that is still only
30000 which is less than
3590 half of the available width.
(All units here are in pixels.
)
3592 In order to save memory
, all typeset nodes are stored in one list at
3593 first
, and then rendered to a pixel buffer as each layer is being written
3594 out to the \.
{PBM
} file
, and then the buffer can be freed
(or reset to
3595 zero
) afterwards to save memory.
3599 unsigned short x
; // X position on page
3600 unsigned short y
; // Y position on page
3601 unsigned short f
; // Font number
, |
0xFFFF| for a rule
3604 unsigned short w
; // Width of rule
3605 unsigned short h
; // Height of rule
3607 unsigned int c
; // Character number in current font
3609 unsigned char l
; // Layer
3612 @ @
<Global variables@
>=
3613 memory_of
(typeset_node
) typeset_nodes
;
3615 @ @
<Initialize memory@
>= init_memory
(typeset_nodes
,8);
3617 @ Here are the subroutines which typeset characters and rules onto the
3618 page buffer. They are not rendered into a picture yet.
3620 @d typeset_new_page
() (typeset_nodes.used
=0)
3622 @
-p void typeset_rule
(int x
,int y
,int w
,int h
) {
3623 data_index n
=new_record
(typeset_nodes
);
3624 typeset_nodes.data
[n
].x
=x
;
3625 typeset_nodes.data
[n
].y
=y
;
3626 typeset_nodes.data
[n
].f
=0xFFFF;
3627 typeset_nodes.data
[n
].w
=w
;
3628 typeset_nodes.data
[n
].h
=h
;
3629 typeset_nodes.data
[n
].l
=quan
('L'
);
3632 @ @
-p void typeset_char
(int x
,int y
,int f
,int c
) {
3633 data_index n
=new_record
(typeset_nodes
);
3634 typeset_nodes.data
[n
].x
=x
;
3635 typeset_nodes.data
[n
].y
=y
;
3636 typeset_nodes.data
[n
].f
=f
;
3637 typeset_nodes.data
[n
].c
=c
;
3638 typeset_nodes.data
[n
].l
=quan
('L'
);
3641 @
*Layer Output. Layer files are output in \.
{PBM
} format
, which is very
3642 similar to the format which this program uses internally. ImageMagick is
3643 capable of reading this format.
3650 @
<Send the current layer to a file@
>= {
3652 fprintf
(fp
,"P4%d %d ",layer_width
,layer_height
);
3656 @
*Process of ImageMagick.
3660 @
*Main Program. This is where the program starts and ends. Everything else
3661 in the other chapters is started from here.
3670 @ @
-p int main
(int argc
,char
**argv
) {
3671 @
<Initialize memory@
>;
3672 @
<Display the banner message@
>;
3673 @
<Open the main input file@
>;
3674 @
<Initialize the input states@
>;
3675 @
<Initialize the tables and registers@
>;
3676 @
<Initialize the random number generator@
>;
3677 @
<Set registers according to command-line parameters@
>;
3678 @
<Process the input files@
>;
3679 @
<Call program in \.Z register if necessary@
>;
3680 @
<Send |end_transmission| to each card area@
>;
3681 @
<Write the output files@
>;
3685 @ @
<Display the banner message@
>= {
3686 fprintf
(stderr
,"TeXnicard version %s\n",version_string
);
3688 "This program is free software and comes with NO WARRANTY.\n");
3692 @ @
<Set registers according to command-line parameters@
>= {
3694 for
(i
=2;i
<argc
;i
++) {
3695 registers
[i
+('
0'
-2)].is_string
=1;
3696 registers
[i
+('
0'
-2)].text
=strdup
(argv
[i
]);
3700 @ The main input file will be either the terminal
, or another file if the
3701 command-line argument is given.
3703 @
<Open the main input file@
>= {
3704 if
(argc
>1 && strcmp(argv[1],"-")!=0) {
3705 --current_input_file
;
3706 open_input
(argv
[1]);
3709 strcpy
(current_filename
,"<Teletype>");
3713 @ @
<Call program in \.Z register if necessary@
>= {
3714 if
(registers
['Z'
].is_string
) execute_program
(registers
['Z'
].text
);
3717 @
*The Future. Here are some ideas for future versions of this program
:
3719 $\bullet$ A customizable Inform7-like parser
, that would compile into a C
3720 code
, so that you can play the cards on rule-enforcing computer programs.
3723 $\bullet$ A database to keep track of how many copies of a card have been
3724 sold
, for inventory purposes.
3725 @^commercial viability@
>
3727 $\bullet$ Full text search
, for things such as the Oracle text search.
3730 $\bullet$ Big spider
!
3737 \long\def\Par
{\csname par\endcsname
}%
3738 \loop\ifnum\count255
<\bibliocount
%
3739 \advance\count255 by
1
3740 \Par$^
{[\the\count255
]}$\csname biblio \the\count255\endcsname\Par
%
3743 @
*Index. Here you can find references to the definition and use of all the
3744 variables
, subroutines
, etc.\ used in this program
, as well as a few other
3745 things of interest. Underlined entries indicate where it is defined.
3747 {\bf Important note
:} All the numbers in this index are section numbers
,
3750 % End of file
"texnicard.w"