2 * Copyright (c) 2000-2001, Thaddeus Covert <sahuagin@mediaone.net>
3 * Copyright (c) 2002 Matthias Veit <matthias_veit@yahoo.de>
4 * Copyright (c) 2004 Elliott Hughes <enh@acm.org>
6 * This source code is released for free distribution under the terms of the
7 * GNU General Public License version 2 or (at your option) any later version.
9 * This module contains functions for generating tags for Ruby language
16 #include "general.h" /* must always come first */
24 #include "nestlevel.h"
46 RUBY_LIBRARY_REQUIRED
,
47 RUBY_LIBRARY_REQUIRED_REL
,
55 static roleDefinition RubyLibraryRoles
[] = {
56 { true, "required", "loaded by \"require\" method" },
57 { true, "requiredRel", "loaded by \"require_relative\" method" },
58 { true, "loaded", "loaded by \"load\" method" },
61 static kindDefinition RubyKinds
[] = {
62 { true, 'c', "class", "classes" },
63 { true, 'f', "method", "methods" },
64 { true, 'm', "module", "modules" },
65 { true, 'S', "singletonMethod", "singleton methods" },
66 { true, 'C', "constant", "constants" },
67 { true, 'A', "accessor", "accessors" },
68 { true, 'a', "alias", "aliases" },
69 { true, 'L', "library", "libraries",
70 .referenceOnly
= true, ATTACH_ROLES(RubyLibraryRoles
) },
77 static fieldDefinition RubyFields
[] = {
79 .description
= "how the class or module is mixed in (mixin:HOW:MODULE)",
87 static NestingLevels
* nesting
= NULL
;
89 #define SCOPE_SEPARATOR '.'
92 * FUNCTION DEFINITIONS
95 static void enterUnnamedScope (void);
98 * Returns a string describing the scope in 'nls'.
99 * We record the current scope as a list of entered scopes.
100 * Scopes corresponding to 'if' statements and the like are
101 * represented by empty strings. Scopes corresponding to
102 * modules and classes are represented by the name of the
105 static vString
* nestingLevelsToScope (const NestingLevels
* nls
)
108 unsigned int chunks_output
= 0;
109 vString
* result
= vStringNew ();
110 for (i
= 0; i
< nls
->n
; ++i
)
112 NestingLevel
*nl
= nestingLevelsGetNthFromRoot (nls
, i
);
113 tagEntryInfo
*e
= getEntryOfNestingLevel (nl
);
114 if (e
&& strlen (e
->name
) > 0 && (!e
->placeholder
))
116 if (chunks_output
++ > 0)
117 vStringPut (result
, SCOPE_SEPARATOR
);
118 vStringCatS (result
, e
->name
);
125 * Attempts to advance 's' past 'literal'.
126 * Returns true if it did, false (and leaves 's' where
129 static bool canMatch (const unsigned char** s
, const char* literal
,
130 bool (*end_check
) (int))
132 const int literal_length
= strlen (literal
);
133 const int s_length
= strlen ((const char *)*s
);
135 if (s_length
< literal_length
)
138 const unsigned char next_char
= *(*s
+ literal_length
);
139 if (strncmp ((const char*) *s
, literal
, literal_length
) != 0)
143 /* Additionally check that we're at the end of a token. */
144 if (! end_check (next_char
))
148 *s
+= literal_length
;
152 static bool isIdentChar (int c
)
154 return (isalnum (c
) || c
== '_');
157 static bool notIdentChar (int c
)
159 return ! isIdentChar (c
);
162 static bool isOperatorChar (int c
)
164 return (c
== '[' || c
== ']' ||
165 c
== '=' || c
== '!' || c
== '~' ||
166 c
== '+' || c
== '-' ||
167 c
== '@' || c
== '*' || c
== '/' || c
== '%' ||
168 c
== '<' || c
== '>' ||
169 c
== '&' || c
== '^' || c
== '|');
172 static bool notOperatorChar (int c
)
174 return ! isOperatorChar (c
);
177 static bool isSigilChar (int c
)
179 return (c
== '@' || c
== '$');
182 static bool isWhitespace (int c
)
184 return c
== 0 || isspace (c
);
188 * Advance 's' while the passed predicate is true. Returns true if
189 * advanced by at least one position.
191 static bool advanceWhile (const unsigned char** s
, bool (*predicate
) (int))
193 const unsigned char* original_pos
= *s
;
197 if (! predicate (**s
))
199 return *s
!= original_pos
;
205 return *s
!= original_pos
;
208 static bool canMatchKeyword (const unsigned char** s
, const char* literal
)
210 return canMatch (s
, literal
, notIdentChar
);
214 * Extends canMatch. Works similarly, but allows assignment to precede
215 * the keyword, as block assignment is a common Ruby idiom.
217 static bool canMatchKeywordWithAssign (const unsigned char** s
, const char* literal
)
219 const unsigned char* original_pos
= *s
;
221 if (canMatchKeyword (s
, literal
))
226 advanceWhile (s
, isSigilChar
);
228 if (! advanceWhile (s
, isIdentChar
))
234 advanceWhile (s
, isWhitespace
);
236 if (! (advanceWhile (s
, isOperatorChar
) && *(*s
- 1) == '='))
242 advanceWhile (s
, isWhitespace
);
244 if (canMatchKeyword (s
, literal
))
254 * Attempts to advance 'cp' past a Ruby operator method name. Returns
255 * true if successful (and copies the name into 'name'), false otherwise.
257 static bool parseRubyOperator (vString
* name
, const unsigned char** cp
)
259 static const char* RUBY_OPERATORS
[] = {
262 "!", "~", "+@", "-@",
268 "<=", "<", ">", ">=",
269 "<=>", "==", "===", "!=", "=~", "!~",
274 for (i
= 0; RUBY_OPERATORS
[i
] != NULL
; ++i
)
276 if (canMatch (cp
, RUBY_OPERATORS
[i
], notOperatorChar
))
278 vStringCatS (name
, RUBY_OPERATORS
[i
]);
286 * Emits a tag for the given 'name' of kind 'kind' at the current nesting.
288 static int emitRubyTagFull (vString
* name
, rubyKind kind
, bool pushLevel
, bool clearName
)
292 tagEntryInfo
*parent
;
293 rubyKind parent_kind
= K_UNDEFINED
;
295 const char *unqualified_name
;
296 const char *qualified_name
;
299 if (!RubyKinds
[kind
].enabled
) {
303 scope
= nestingLevelsToScope (nesting
);
304 lvl
= nestingLevelsGetCurrent (nesting
);
305 parent
= getEntryOfNestingLevel (lvl
);
307 parent_kind
= parent
->kindIndex
;
309 qualified_name
= vStringValue (name
);
310 unqualified_name
= strrchr (qualified_name
, SCOPE_SEPARATOR
);
311 if (unqualified_name
&& unqualified_name
[1])
313 if (unqualified_name
> qualified_name
)
315 if (vStringLength (scope
) > 0)
316 vStringPut (scope
, SCOPE_SEPARATOR
);
317 vStringNCatS (scope
, qualified_name
,
318 unqualified_name
- qualified_name
);
319 /* assume module parent type for a lack of a better option */
320 parent_kind
= K_MODULE
;
325 unqualified_name
= qualified_name
;
327 initTagEntry (&tag
, unqualified_name
, kind
);
329 /* Don't fill the scope field for a tag entry representing
330 * a global variable. */
331 if (unqualified_name
[0] != '$'
332 && vStringLength (scope
) > 0) {
333 Assert (0 <= parent_kind
&&
334 (size_t) parent_kind
< (ARRAY_SIZE (RubyKinds
)));
336 tag
.extensionFields
.scopeKindIndex
= parent_kind
;
337 tag
.extensionFields
.scopeName
= vStringValue (scope
);
339 r
= makeTagEntry (&tag
);
342 nestingLevelsPush (nesting
, r
);
346 vStringDelete (scope
);
351 static int emitRubyTag (vString
* name
, rubyKind kind
)
353 return emitRubyTagFull (name
, kind
, kind
!= K_CONST
, true);
356 /* Tests whether 'ch' is a character in 'list'. */
357 static bool charIsIn (char ch
, const char* list
)
359 return (strchr (list
, ch
) != NULL
);
362 /* Advances 'cp' over leading whitespace. */
363 static void skipWhitespace (const unsigned char** cp
)
365 while (isspace (**cp
))
372 * Copies the characters forming an identifier from *cp into
373 * name, leaving *cp pointing to the character after the identifier.
375 static rubyKind
parseIdentifier (
376 const unsigned char** cp
, vString
* name
, rubyKind kind
)
378 /* Method names are slightly different to class and variable names.
379 * A method name may optionally end with a question mark, exclamation
380 * point or equals sign. These are all part of the name.
381 * A method name may also contain a period if it's a singleton method.
383 bool had_sep
= false;
385 if (kind
== K_METHOD
)
389 else if (kind
== K_SINGLETON
)
400 /* Check for an anonymous (singleton) class such as "class << HTTP". */
401 if (kind
== K_CLASS
&& **cp
== '<' && *(*cp
+ 1) == '<')
406 /* Check for operators such as "def []=(key, val)". */
407 if (kind
== K_METHOD
|| kind
== K_SINGLETON
)
409 if (parseRubyOperator (name
, cp
))
415 /* Copy the identifier into 'name'. */
416 while (**cp
!= 0 && (**cp
== ':' || isIdentChar (**cp
) || charIsIn (**cp
, also_ok
)))
418 char last_char
= **cp
;
420 if (last_char
== ':')
426 vStringPut (name
, SCOPE_SEPARATOR
);
429 vStringPut (name
, last_char
);
433 if (kind
== K_METHOD
)
435 /* Recognize singleton methods. */
436 if (last_char
== '.')
439 return parseIdentifier (cp
, name
, K_SINGLETON
);
443 if (kind
== K_METHOD
|| kind
== K_SINGLETON
)
445 /* Recognize characters which mark the end of a method name. */
446 if (charIsIn (last_char
, "?!="))
455 static void parseString (const unsigned char** cp
, unsigned char boundary
, vString
* vstr
)
457 while (**cp
!= 0 && **cp
!= boundary
)
460 vStringPut (vstr
, **cp
);
464 /* skip the last found '"' */
465 if (**cp
== boundary
)
469 static void parseSignature (const unsigned char** cp
, vString
* vstr
)
476 * - handle string literals including ( or ), and
479 while (! (depth
== 0 || **cp
== '\0'))
481 if (**cp
== '(' || **cp
== ')')
483 depth
+= (**cp
== '(')? 1: -1;
484 vStringPut (vstr
, **cp
);
486 else if (**cp
== '#')
493 else if (**cp
== '\'' || **cp
== '"')
495 unsigned char b
= **cp
;
496 vStringPut (vstr
, b
);
498 parseString (cp
, b
, vstr
);
499 vStringPut (vstr
, b
);
502 else if (isspace (vStringLast (vstr
)))
504 if (! (isspace (**cp
)))
508 vStringPut (vstr
, **cp
);
512 vStringPut (vstr
, **cp
);
518 const unsigned char *line
= readLineFromInputFile ();
526 static int readAndEmitTagFull (const unsigned char** cp
, rubyKind expected_kind
,
527 bool pushLevel
, bool clearName
)
532 vString
*name
= vStringNew ();
533 rubyKind actual_kind
= parseIdentifier (cp
, name
, expected_kind
);
535 if (actual_kind
== K_UNDEFINED
|| vStringLength (name
) == 0)
538 * What kind of tags should we create for code like this?
540 * %w(self.clfloor clfloor).each do |name|
541 * module_eval <<-"end;"
542 * def #{name}(x, y=1)
554 * For now, we don't create any.
556 enterUnnamedScope ();
560 r
= emitRubyTagFull (name
, actual_kind
, pushLevel
, clearName
);
562 vStringDelete (name
);
567 static int readAndEmitTag (const unsigned char** cp
, rubyKind expected_kind
)
569 return readAndEmitTagFull (cp
, expected_kind
, expected_kind
!= K_CONST
, true);
572 static void readAndStoreMixinSpec (const unsigned char** cp
, const char *how_mixin
)
575 NestingLevel
*nl
= NULL
;
576 tagEntryInfo
*e
= NULL
;
579 for (ownerLevel
= 0; ownerLevel
< nesting
->n
; ownerLevel
++)
581 nl
= nestingLevelsGetNthParent (nesting
, ownerLevel
);
582 e
= nl
? getEntryOfNestingLevel (nl
): NULL
;
584 /* Ignore "if", "unless", "while" ... */
585 if ((nl
&& (nl
->corkIndex
== CORK_NIL
)) || (e
&& e
->placeholder
))
593 if (e
->kindIndex
== K_SINGLETON
)
595 nl
= nestingLevelsGetNthParent (nesting
,
599 e
= getEntryOfNestingLevel (nl
);
605 if (! (e
->kindIndex
== K_CLASS
|| e
->kindIndex
== K_MODULE
))
608 if (isspace (**cp
) || (**cp
== '('))
615 vString
*spec
= vStringNewInit (how_mixin
);
616 vStringPut(spec
, ':');
618 size_t len
= vStringLength (spec
);
619 parseIdentifier (cp
, spec
, K_MODULE
);
620 if (len
== vStringLength (spec
))
622 vStringDelete (spec
);
626 struct blockData
*bdata
= nestingLevelGetUserData (nl
);
627 if (bdata
->mixin
== NULL
)
628 bdata
->mixin
= stringListNew ();
629 stringListAdd (bdata
->mixin
, spec
);
633 static void enterUnnamedScope (void)
636 NestingLevel
*parent
= nestingLevelsGetCurrent (nesting
);
637 tagEntryInfo
*e_parent
= getEntryOfNestingLevel (parent
);
642 initTagEntry (&e
, "", e_parent
->kindIndex
);
644 r
= makeTagEntry (&e
);
646 nestingLevelsPush (nesting
, r
);
649 static void attachMixinField (int corkIndex
, stringList
*mixinSpec
)
651 vString
*mixinField
= stringListItem (mixinSpec
, 0);
652 for (unsigned int i
= 1; i
< stringListCount (mixinSpec
); i
++)
654 vStringPut (mixinField
, ',');
655 vStringCat (mixinField
, stringListItem (mixinSpec
, i
));
658 attachParserFieldToCorkEntry (corkIndex
, RubyFields
[F_MIXIN
].ftype
,
659 vStringValue (mixinField
));
662 static void deleteBlockData (NestingLevel
*nl
)
664 struct blockData
*bdata
= nestingLevelGetUserData (nl
);
666 if (nl
->corkIndex
!= CORK_NIL
667 && bdata
->mixin
!= NULL
668 && stringListCount (bdata
->mixin
) > 0)
669 attachMixinField (nl
->corkIndex
, bdata
->mixin
);
671 tagEntryInfo
*e
= getEntryInCorkQueue (nl
->corkIndex
);
672 if (e
&& !e
->placeholder
)
673 e
->extensionFields
.endLine
= getInputLineNumber ();
676 stringListDelete (bdata
->mixin
);
679 static bool doesLineIncludeConstant (const unsigned char **cp
, vString
*constant
)
681 const unsigned char **p
= cp
;
688 while (**p
!= 0 && isIdentChar (**p
))
690 vStringPut (constant
, **p
);
701 vStringClear (constant
);
707 static void emitRubyAccessorTags (vString
*a
, bool reader
, bool writer
)
709 if (vStringLength (a
) == 0)
713 emitRubyTagFull (a
, K_ACCESSOR
, false, !writer
);
717 emitRubyTagFull (a
, K_ACCESSOR
, false, true);
721 static void readAttrsAndEmitTags (const unsigned char **cp
, bool reader
, bool writer
)
723 vString
*a
= vStringNew ();
734 if (K_METHOD
== parseIdentifier (cp
, a
, K_METHOD
))
736 emitRubyAccessorTags (a
, reader
, writer
);
745 else if (**cp
== '"' || **cp
== '\'')
747 unsigned char b
= **cp
;
749 parseString (cp
, b
, a
);
751 emitRubyAccessorTags (a
, reader
, writer
);
765 static int readAliasMethodAndEmitTags (const unsigned char **cp
)
768 vString
*a
= vStringNew ();
778 if (K_METHOD
!= parseIdentifier (cp
, a
, K_METHOD
))
781 else if (**cp
== '"' || **cp
== '\'')
783 unsigned char b
= **cp
;
785 parseString (cp
, b
, a
);
788 if (vStringLength (a
) > 0)
789 r
= emitRubyTagFull (a
, K_ALIAS
, false, false);
795 static int readStringAndEmitTag (const unsigned char **cp
, rubyKind kind
, int role
)
805 if (**cp
== '"' || **cp
== '\'')
807 unsigned char b
= **cp
;
810 parseString (cp
, b
, s
);
813 if (s
&& vStringLength (s
) > 0)
814 r
= makeSimpleRefTag (s
, kind
, role
);
820 static int readAndEmitDef (const unsigned char **cp
)
822 rubyKind kind
= K_METHOD
;
823 NestingLevel
*nl
= nestingLevelsGetCurrent (nesting
);
824 tagEntryInfo
*e_scope
= getEntryOfNestingLevel (nl
);
826 /* if the def is inside an unnamed scope at the class level, assume
827 * it's from a singleton from a construct like this:
837 if (e_scope
&& e_scope
->kindIndex
== K_CLASS
&& strlen (e_scope
->name
) == 0)
839 int corkIndex
= readAndEmitTag (cp
, kind
);
840 tagEntryInfo
*e
= getEntryInCorkQueue (corkIndex
);
842 /* Fill signature: field. */
845 vString
*signature
= vStringNewInit ("(");
850 parseSignature (cp
, signature
);
851 if (vStringLast(signature
) != ')')
853 vStringDelete (signature
);
858 vStringPut (signature
, ')');
859 e
->extensionFields
.signature
= vStringDeleteUnwrap (signature
);
861 vStringDelete (signature
);
866 static void findRubyTags (void)
868 const unsigned char *line
;
869 bool inMultiLineComment
= false;
870 vString
*constant
= vStringNew ();
872 nesting
= nestingLevelsNewFull (sizeof (struct blockData
), deleteBlockData
);
874 /* FIXME: this whole scheme is wrong, because Ruby isn't line-based.
875 * You could perfectly well write:
882 * if you wished, and this function would fail to recognize anything.
884 while ((line
= readLineFromInputFile ()) != NULL
)
886 const unsigned char *cp
= line
;
887 /* if we expect a separator after a while, for, or until statement
888 * separators are "do", ";" or newline */
889 bool expect_separator
= false;
891 if (canMatch (&cp
, "=begin", isWhitespace
))
893 inMultiLineComment
= true;
896 if (canMatch (&cp
, "=end", isWhitespace
))
898 inMultiLineComment
= false;
901 if (inMultiLineComment
)
904 skipWhitespace (&cp
);
906 /* Avoid mistakenly starting a scope for modifiers such as
910 * FIXME: we're fooled if someone does something heinous such as
916 if (canMatchKeywordWithAssign (&cp
, "for") ||
917 canMatchKeywordWithAssign (&cp
, "until") ||
918 canMatchKeywordWithAssign (&cp
, "while"))
920 expect_separator
= true;
921 enterUnnamedScope ();
923 else if (canMatchKeywordWithAssign (&cp
, "case") ||
924 canMatchKeywordWithAssign (&cp
, "if") ||
925 canMatchKeywordWithAssign (&cp
, "unless"))
927 enterUnnamedScope ();
931 * "module M", "class C" and "def m" should only be at the beginning
934 if (canMatchKeywordWithAssign (&cp
, "module"))
936 readAndEmitTag (&cp
, K_MODULE
);
938 else if (canMatchKeywordWithAssign (&cp
, "class"))
940 int r
= readAndEmitTag (&cp
, K_CLASS
);
941 tagEntryInfo
*e
= getEntryInCorkQueue (r
);
945 skipWhitespace (&cp
);
946 if (*cp
== '<' && *(cp
+ 1) != '<')
949 vString
*parent
= vStringNew ();
950 parseIdentifier (&cp
, parent
, K_CLASS
);
951 if (vStringLength (parent
) > 0)
952 e
->extensionFields
.inheritance
= vStringDeleteUnwrap (parent
);
954 vStringDelete (parent
);
958 else if (canMatchKeywordWithAssign (&cp
, "include"))
960 readAndStoreMixinSpec (&cp
, "include");
962 else if (canMatchKeywordWithAssign (&cp
, "prepend"))
964 readAndStoreMixinSpec (&cp
, "prepend");
966 else if (canMatchKeywordWithAssign (&cp
, "extend"))
968 readAndStoreMixinSpec (&cp
, "extend");
970 else if (canMatchKeywordWithAssign (&cp
, "def"))
972 readAndEmitDef (&cp
);
974 else if (canMatchKeywordWithAssign (&cp
, "attr_reader"))
976 readAttrsAndEmitTags (&cp
, true, false);
978 else if (canMatchKeywordWithAssign (&cp
, "attr_writer"))
980 readAttrsAndEmitTags (&cp
, false, true);
982 else if (canMatchKeywordWithAssign (&cp
, "attr_accessor"))
984 readAttrsAndEmitTags (&cp
, true, true);
986 else if (doesLineIncludeConstant (&cp
, constant
))
988 emitRubyTag (constant
, K_CONST
);
989 vStringClear (constant
);
991 else if (canMatchKeywordWithAssign (&cp
, "require"))
993 readStringAndEmitTag (&cp
, K_LIBRARY
, RUBY_LIBRARY_REQUIRED
);
995 else if (canMatchKeywordWithAssign (&cp
, "require_relative"))
997 readStringAndEmitTag (&cp
, K_LIBRARY
, RUBY_LIBRARY_REQUIRED_REL
);
999 else if (canMatchKeywordWithAssign (&cp
, "load"))
1001 readStringAndEmitTag (&cp
, K_LIBRARY
, RUBY_LIBRARY_LOADED
);
1003 else if (canMatchKeywordWithAssign (&cp
, "alias"))
1005 if (!readAndEmitTagFull (&cp
, K_ALIAS
, false, true)
1008 /* Alias for a global variable. */
1010 vString
*alias
= vStringNew ();
1011 vStringPut (alias
, '$');
1012 if (K_METHOD
== parseIdentifier (&cp
, alias
, K_METHOD
)
1013 && vStringLength (alias
) > 0)
1014 emitRubyTagFull (alias
, K_ALIAS
, false, false);
1015 vStringDelete (alias
);
1018 else if (canMatchKeywordWithAssign (&cp
, "alias_method"))
1019 readAliasMethodAndEmitTags (&cp
);
1020 else if ((canMatchKeywordWithAssign (&cp
, "private")
1021 || canMatchKeywordWithAssign (&cp
, "protected")
1022 || canMatchKeywordWithAssign (&cp
, "public")
1023 || canMatchKeywordWithAssign (&cp
, "private_class_method")
1024 || canMatchKeywordWithAssign (&cp
, "public_class_method")))
1026 skipWhitespace (&cp
);
1027 if (canMatchKeywordWithAssign (&cp
, "def"))
1028 readAndEmitDef (&cp
);
1029 /* TODO: store the method for controlling visibility
1030 * to the "access:" field of the tag.*/
1036 /* FIXME: we don't cope with here documents,
1037 * or regular expression literals, or ... you get the idea.
1038 * Hopefully, the restriction above that insists on seeing
1039 * definitions at the starts of lines should keep us out of
1042 if (inMultiLineComment
|| isspace (*cp
))
1046 else if (*cp
== '#')
1048 /* FIXME: this is wrong, but there *probably* won't be a
1049 * definition after an interpolated string (where # doesn't
1054 else if (canMatchKeyword (&cp
, "begin"))
1056 enterUnnamedScope ();
1058 else if (canMatchKeyword (&cp
, "do"))
1060 if (! expect_separator
)
1061 enterUnnamedScope ();
1063 expect_separator
= false;
1065 else if (canMatchKeyword (&cp
, "end") && nesting
->n
> 0)
1067 /* Leave the most recent scope. */
1068 nestingLevelsPop (nesting
);
1070 else if (*cp
== '"' || *cp
== '\'')
1072 unsigned char b
= *cp
;
1073 /* Skip string literals.
1074 * FIXME: should cope with escapes and interpolation.
1077 parseString (&cp
, b
, NULL
);
1079 else if (*cp
== ';')
1082 expect_separator
= false;
1084 else if (*cp
!= '\0')
1088 while (isIdentChar (*cp
));
1092 nestingLevelsFree (nesting
);
1093 vStringDelete (constant
);
1096 extern parserDefinition
* RubyParser (void)
1098 static const char *const extensions
[] = { "rb", "ruby", NULL
};
1099 parserDefinition
* def
= parserNew ("Ruby");
1100 def
->kindTable
= RubyKinds
;
1101 def
->kindCount
= ARRAY_SIZE (RubyKinds
);
1102 def
->extensions
= extensions
;
1103 def
->parser
= findRubyTags
;
1104 def
->fieldTable
= RubyFields
;
1105 def
->fieldCount
= ARRAY_SIZE (RubyFields
);
1106 def
->useCork
= CORK_QUEUE
;