source: text-formbuilder/trunk/lib/Text/FormBuilder/grammar @ 90

Last change on this file since 90 was 88, checked in by peichman, 19 years ago

BUGFIX: stopped crosstalk of data from one object to another (references were getting shared in the parser grammar code)

File size: 8.5 KB
RevLine 
[77]1# line directives
2# block directives
3# field lines
4# comments
5
[66]6{
7    #$::RD_TRACE = 1;
[21]8    my (
[29]9        @sections,     # master data structure
10        $section_head,
11        $section_id,
12        @lines,        # lines in each section
[21]13        %lists,
14        %patterns,
[39]15        %subs,         # validation subs
[21]16        @group,        # current group
17        %groups,       # stored groups of fields
18        $type,
19        @options,
[24]20        $required,
[84]21        $multiple,
[21]22        $list_var,
23        $size,
[56]24        $maxlength,
[21]25        $rows,
26        $cols,
[80]27        @submit,
[21]28    );
[77]29    my $context = 'line';       # start in line context by default
30    my %formspec;
31   
32    # TODO: helper sub?
33    sub alert ($) {
34        warn '[' . (split(/::/, (caller(1))[3]))[-1] . '] ' . shift() . "\n";
35    }
[21]36}
[1]37
[77]38form_spec:
[88]39    {
40        # clear out old data, so we don't end up with old data in new objects
41        @lines    = ();
42        @sections = ();
43        %formspec = ();
[77]44    }
[87]45    (list_def | description_def | group_def | note | fb_params | unknown_block_directive | line)(s)
[1]46    {
[29]47        # grab the last section, if there is any
48        if (@lines) {
49            push @sections,
50                {
51                    id   => $section_id,
52                    head => $section_head,
53                    lines => [ @lines ],
54                };
55        }
56       
[88]57        # make copies instead of taking references, again so we
58        # don't end up with connections between objects
[1]59        $return = {
[87]60            fb_params   => $formspec{fb_params},
61            title       => $formspec{title},
62            author      => $formspec{author},
[77]63            description => $formspec{description},
[88]64            lists       => { %lists },
65            patterns    => { %patterns },
66            subs        => { %subs },
67            groups      => { %groups },
68            sections    => [ @sections ],
69            ( @submit ? (submit => @submit == 1 ? $submit[0] : [ @submit ]) : () ),
[87]70            reset       => $formspec{reset},
[1]71        }
72    }
73
[16]74list_def: '!list' var_name (static_list | dynamic_list)
75    { $lists{$item{var_name}} = [ @options ]; @options = () }
[1]76
[66]77static_list: '{' /\s*/ option(s /\s*,\s*/) /,?/ /\s*/ '}'
[10]78
79dynamic_list: '&' <perl_codeblock>
[77]80    { warn "[Text::FormBuilder] Dynamic lists have been removed from the formspec grammar"; }
[10]81
[57]82description_def: '!description' block
[64]83    {
[77]84        warn "[Text::FormBuilder] Description redefined at input text line $thisline\n" if defined $formspec{description};
85        $formspec{description} = $item{block};
[14]86    }
87
[21]88group_def: '!group' { $context = 'group' } var_name '{' field_line(s) '}' { $context = 'line' }
89    {
90        #warn "$item{var_name} group; context $context\n"
91        $groups{$item{var_name}} = [ @group ];
92        @group = ();
93    }
[1]94
[64]95note: '!note' block  { push @lines, [ 'note', $item{block} ]; }
[50]96
[64]97
[57]98# curly-brace delimited block, that can contain properly
[64]99# nested curly braces, along with any other characters
100# inner blocks return with the '{...}' so that nested
101# blocks get the braces treated as literals
102block: '{' <skip:''> block_content(s) '}' { join('', @{ $item[3] }) }
103inner_block: '{' <skip:''> block_content(s) '}'  { '{' . join('', @{ $item[3] }) . '}' }
104block_content: /[^\{\}]+?/ | inner_block
[50]105
[64]106# square brace delimited block, that can contain properly
107# nested square brackets, along with any other characters
108# inner bracket blocks return with the '[...]' so that nested
109# blocks get the braces treated as literals
110bracket_block: '[' <skip:''> bracket_block_content(s) ']' { join('', @{ $item[3] }) }
111inner_bracket_block: '[' <skip:''> bracket_block_content(s) ']' { '[' . join('', @{ $item[3] }) . ']'; }
112bracket_block_content: /[^\[\]]+?/ | inner_bracket_block
[57]113
114
[64]115# field lines are the subset of lines that are allowed in a !group directive
[21]116field_line: <skip:'[ \t]*'> ( field | comment | blank ) "\n"
[64]117
[81]118line: <skip:'[ \t]*'> ( title | author | pattern_def | section_head | heading | submit | reset | group_field | field_group | unknown_directive | field | comment | blank ) /\n+/
[21]119
[1]120title: '!title' /.*/
[28]121    {
[77]122        warn "[Text::FormBuilder] Title redefined at input text line $thisline\n" if defined $formspec{title};
123        $formspec{title} = $item[2];
[28]124    }
[1]125
126author: '!author' /.*/
[28]127    {
[77]128        warn "[Text::FormBuilder] Author redefined at input text line $thisline\n" if defined $formspec{author};
129        $formspec{author} = $item[2];
[28]130    }
[1]131
[16]132pattern_def: '!pattern' var_name pattern
133    { $patterns{$item{var_name}} = $item{pattern} }
[1]134
135pattern: /.*/
136
[29]137section_head: '!section' identifier /.*/
138    {
139        #warn "starting section $item{identifier}\n";
140        #warn "  with heading $item[3]\n" if $item[3];
141       
142        if (@lines) {
143            push @sections,
144                {
145                    id   => $section_id,
146                    head => $section_head,
147                    lines => [ @lines ],
148                };
149        }
150       
151        $section_id = $item{identifier};
152        $section_head = $item[3];
153        @lines = ();
154    }
155
[28]156heading: '!head' /.*/    { push @lines, [ 'head', $item[2] ] }
[11]157
[80]158submit: '!submit' string(s /\s*,\s*/)
159    {
160        #warn scalar(@{ $item[2] }) . ' submit button(s)';
161        push @submit, @{ $item[2] };
162    }
163
[81]164reset: '!reset' string
165    {
166        warn "[Text::FormBuilder] Reset button redefined at input text line $thisline\n" if defined $formspec{reset};
167        $formspec{reset} = $item{string};
168    }
169
[21]170group_field: '!field' group_name name label(?)
[74]171    {
[77]172        warn "WARNING line $thisline: The '!field' directive has been DEPRECATED. Use the 'name:GROUP' style instead.\n";
[28]173        push @lines, [ 'group', { name => $item{name}, label => $item{'label(?)'}[0], group => $item{group_name} } ];
[21]174    }
175
176group_name: /%[A-Z_]+/
177
[87]178# parameters that get passed to the FB constructor; these are serialized in YAML
179fb_params: '!fb' block
180    { $formspec{fb_params} .= $item{block}; }
181
[73]182field_group: name label(?) hint(?) group_type comment(?)
183    {
[74]184        #warn "[$thisline] comment = $item{'hint(?)'}[0]\n" if $item{'hint(?)'}[0];
[63]185        #warn "[$thisline] field $item{name} is $item{group_type}\n";
[73]186        push @lines, [ 'group', {
187            name    => $item{name},
188            label   => $item{'label(?)'}[0],
189            comment => $item{'hint(?)'}[0],
190            group   => $item{group_type},
191        } ];
[63]192    }
193
194group_type: ':' var_name
195
[64]196# this is the real heart of the thing
[84]197field: name field_size(?) growable(?) label(?) hint(?) type(?) multi(?) other(?) default(?) option_list(?) validate(?) comment(?)
[87]198    {   
[1]199        my $field = {
200            name     => $item{name},
[66]201            growable => $item{'growable(?)'}[0],
[1]202            label    => $item{'label(?)'}[0],
203            comment  => $item{'hint(?)'}[0],
[84]204            multiple => $item{'multi(?)'}[0],
[1]205            type     => $item{'type(?)'}[0],
[66]206            other    => $item{'other(?)'}[0],
[1]207            value    => $item{'default(?)'}[0],
[63]208            list     => $list_var,
209            validate => $item{'validate(?)'}[0],
[68]210            required => $required,
[1]211        };
212       
213        $$field{options} = [ @options ] if @options;
214       
215        $$field{rows} = $rows if defined $rows;
216        $$field{cols} = $cols if defined $cols;
217        $$field{size} = $size if defined $size;
[56]218        $$field{maxlength} = $maxlength if defined $maxlength;
[1]219       
[28]220        #warn "[$thisline] field $item{name}; context $context\n";
[21]221        if ($context eq 'group') {
222            push @group, $field;
223        } else {
224            push @lines, [ 'field', $field ];
225        }
[1]226       
227        $type = undef;
[68]228        $required = undef;
[84]229        $multiple = undef;
[1]230        $list_var = undef;
231        $size = undef;
232        $rows = undef;
233        $cols = undef;
[56]234        $maxlength = undef;
[1]235        @options = ();
[63]236       
[88]237        #warn "$$field{name}: $field";
238       
[62]239        $field;
[1]240    }
241   
242name: identifier
243
[16]244var_name: /[A-Z_]+/
245
[1]246field_size: '[' ( row_col | size ) ']'
247
[56]248size: /\d+/ bang(?)
249    { $maxlength = $item[1] if $item[2][0]; $size = $item[1] }
[1]250
[56]251bang: '!'
252
[1]253row_col: /\d+/ /,\s*/ /\d+/
254    { $rows = $item[1]; $cols = $item[3] }
255
[66]256growable: '*' limit(?) { $item{'limit(?)'}[0] || 1 }
[61]257
[66]258limit: /\d+/
259
[77]260label: '|' string { $item[2] }
[1]261
[64]262hint: bracket_block
[1]263
[63]264type: ':' builtin_field
[1]265
[55]266builtin_field: /textarea|text|password|file|checkbox|radio|select|hidden|static/
267
[84]268multi: '*' { 1 }
269
[66]270other: '+' 'other' { 1 }
[55]271
[77]272default: '=' string { $item[2] }
[1]273
[77]274string: simple_multiword | quoted_string
275
[22]276# for simple multiword values not involving punctuation
[80]277simple_multiword: /\w/ <skip:''> /[\w\t ]*/ { $item[1] . $item[3] }
[22]278
279# my attempt at a single-quoted, non-interpolating string
280# where the backslash can escape literal single quotes
[80]281quoted_string: "'" <skip:''> /(\\'|[^'])*/ "'"
[22]282    { $item[3] =~ s/\\'/'/g; $item[3] }
283
[1]284option_list: options | list_var
285   
[77]286options: '{' option(s /\s*,\s*/) '}'
[1]287
288list_var: /@[A-Z_]+/ { $list_var = $item[1] }
289
[77]290option: string display_text(?)
[23]291    { push @options, { $item[1] => $item{'display_text(?)'}[0] } }
[1]292
293value: identifier
294
[64]295display_text: bracket_block
[1]296
[59]297
[64]298validate: '//' (optional_pattern | required_pattern)
[59]299
[64]300optional_pattern: var_name '?'  { $required = 0; $item[1] }
[1]301
[64]302required_pattern: var_name { $required = 1; $item[1] }
[24]303
[1]304comment: '#' /.*/
305blank:
306
307identifier: /\w+/
[16]308
309# skip unknown directives with a warning
310unknown_directive: /\!\S*/ /.*/
311    { warn "[Text::Formbuilder] Skipping unknown directive '$item[1]' at input text line $thisline\n"; }
[80]312
313unknown_block_directive: /\!\S*/ var_name(?) block
314    { warn "[Text::Formbuilder] Skipping unknown block directive '$item[1]' at input text line $thisline\n"; }
Note: See TracBrowser for help on using the repository browser.