File Coverage

blib/lib/Parse/GLSL.pm
Criterion Covered Total %
statement 125 150 83.3
branch 18 34 52.9
condition 1 8 12.5
subroutine 43 49 87.7
pod 22 22 100.0
total 209 263 79.4


line stmt bran cond sub pod time code
1             package Parse::GLSL;
2             # ABSTRACT: Parse OpenGL Shader Language files into an abstract syntax tree
3 4     4   118412 use strict;
  4         10  
  4         181  
4 4     4   23 use warnings FATAL => 'all', NONFATAL => 'redefine';
  4         4  
  4         205  
5 4     4   3120 use parent qw(Parser::MGC);
  4         1205  
  4         21  
6 4     4   97245 use Text::Tabs ();
  4         3025  
  4         7855  
7              
8             our $VERSION = '0.002';
9              
10             =head1 NAME
11              
12             Parse::GLSL - extract an Abstract Syntax Tree from GLSL text
13              
14             =head1 VERSION
15              
16             version 0.002
17              
18             =head1 SYNOPSIS
19              
20             use Parse::GLSL;
21             use Data::Dumper;
22             my $parser = Parse::GLSL->new;
23             my $txt = '';
24             while(my $line = ) {
25             if($line =~ /^\s*#\s*(?:(.*))$/) {
26             my $cmd = $1;
27             warn "Had directive [$cmd]\n";
28             } else {
29             $txt .= $line;
30             }
31             }
32             print Dumper $parser->from_string($txt);
33              
34             =head1 DESCRIPTION
35              
36             Warning: This is a preview release, and the entire API is subject to change several times over the next
37             few releases.
38              
39             This module provides basic parsing for the OpenGL Shading Language (currently dealing with fragment and
40             vertex shaders, eventually will be expanded to cover geometry, tesselation and compute shaders as well).
41              
42             The GLSL document is parsed using L, rather than the reference 3D Labs GLSL parser implementation,
43             and a Perl data structure is generated as output.
44              
45             Currently very basic variable checking is performed - vars must be declared before use, so this would raise
46             an error:
47              
48             void main(void) {
49             vec2 tex_coord;
50             vec2 c = unknown_var * tex_coord.st;
51             }
52              
53             Further checks are planned in future versions.
54              
55             The exact nature of the data structure returned is subject to change, so it's not currently documented.
56              
57             =head1 METHODS
58              
59             =cut
60              
61             =head2 new
62              
63             Constructor - see L for full details. This is defined here to override the standard
64             string delimiter to " since C strings don't use the ' delimiter. Not that strings have much place
65             in GLSL per se...
66              
67             =cut
68              
69             sub new {
70 3     3 1 215 my $class = shift;
71 3         7 my %args = @_;
72 3 50       30 $args{string_delim} = qr/"/ unless exists $args{string_delim};
73 3         10 my $debug = delete $args{debug};
74 3         44 my $self = $class->SUPER::new(%args);
75 3   50     254 $self->{debug} = $debug || 0;
76             # Predefined vars
77             # FIXME These are type-specific and version-specific - fragment vars and vertex vars are not the
78             # same, should differentiate depending on how that's going to be handled (subclasses?)
79 3         60 $self->{variables}->{$_} = { 'defined' => 1 } for qw(gl_Position gl_NormalMatrix gl_Normal gl_Vertex gl_ModelViewMatrix);
80              
81             # Predefined macros - each takes $self as the first parameter, and maybe some other stuff if we end up supporting those.
82             # Note that these are the literal strings, *not* the built-in perl vars of the same names.
83             $self->{macro} = {
84 0     0   0 '__FILE__' => sub { my $self = shift; $self->current_file },
  0         0  
85 0     0   0 '__LINE__' => sub { my $self = shift; $self->current_line },
  0         0  
86 0     0   0 '__VERSION__' => sub { my $self = shift; $self->current_version },
  0         0  
87 3         39 };
88 3         19 $self;
89             }
90              
91             =head2 where_am_i
92              
93             Reports current position in line if $self->{debug} is set to 2 or above.
94              
95             =cut
96              
97             sub where_am_i {
98 750     750 1 899 my $self = shift;
99 750 50       1746 return unless $self->{debug} > 1;
100              
101 0   0     0 my $note = shift || (caller(1))[3];
102 0         0 my ($lineno, $col, $text) = $self->where;
103 0         0 my $len = length($text);
104 0         0 my $target_pos = $col;
105 0   0     0 $target_pos++ while $target_pos < length($text) && substr($text, $target_pos, 1) =~ /^\s/;
106 0         0 $target_pos++;
107 0 0       0 substr $text, ($target_pos >= length($text) ? length($text) : $target_pos), 0, "\033[01;00m";
108 0         0 substr $text, $col, 0, "\033[01;44m";
109 0         0 $text = sprintf '%-80.80s', Text::Tabs::expand($text);
110 0         0 printf "%s %d,%d %d %s\n", $text, $col, $lineno, $len, $note;
111             }
112              
113             =head2 parse
114              
115             Parse the given GLSL string.
116              
117             =cut
118              
119             sub parse {
120 2     2 1 2118 my $self = shift;
121              
122 2         10 $self->where_am_i;
123             $self->sequence_of(sub {
124 11     11   540 $self->parse_item
125 2         24 });
126             }
127              
128             =head2 parse_item
129              
130             Parse an "entry" in the GLSL text. Currently this consists of top-level variable declarations and function
131             declarations.
132              
133             =cut
134              
135             sub parse_item {
136 11     11 1 15 my $self = shift;
137              
138 11         22 $self->where_am_i;
139             $self->any_of(
140             sub {
141             # Try to extract a variable definition
142 11 50   11   151 my $decl = $self->parse_declaration or return;
143 11         949 $self->expect(';');
144 9         411 $decl
145             },
146 2     2   184 sub { $self->parse_function },
147 11         71 );
148             }
149              
150             =head2 parse_declaration
151              
152             Parse a variable declaration.
153              
154             =cut
155              
156             sub parse_declaration {
157 30     30 1 31 my $self = shift;
158              
159 30         48 $self->where_am_i;
160             $self->sequence_of(sub {
161 46     46   1807 [ $self->maybe(sub { $self->token_kw(qw(uniform varying in out)); }), $self->parse_type, $self->parse_definition(1) ]
  46         446  
162 30         135 });
163             }
164              
165             =head2 parse_definition
166              
167             Parse a "definition" (x = y), currently this also includes bare identifiers so that it works with
168             L but this behaviour is subject to change.
169              
170             =cut
171              
172             sub parse_definition {
173 28     28 1 1002 my $self = shift;
174 28         34 my $defining = shift;
175              
176             $self->list_of(',', sub {
177 29     29   1026 $self->where_am_i;
178 29 50       63 my $var = $self->token_ident or return;
179 29 100       1495 $self->{variables}->{$var} = {
180             'defined' => 1
181             } if $defining;
182 29 50       63 print "Parsing the definition for [$var]\n" if $self->{debug};
183 29 50       63 die "Variable $var not defined?" unless $self->{variables}->{$var};
184 29         36 my $expr;
185             my $assignment;
186             $self->maybe(sub {
187 29 100       272 if($defining) {
188 17         46 ($assignment) = $self->expect('=');
189             } else {
190 12         47 ($assignment) = $self->expect(qr{([*/+-]?)=});
191             }
192 17 50       925 $expr = $self->parse_expression or return;
193 17         2350 $self->{variables}->{$var}->{expression} = $expr;
194 29         149 });
195 29 100       1021 if(defined $expr) {
196 17         57 return [ $var, $assignment, $expr ];
197             } else {
198 12         41 return [ $var ];
199             }
200 28         135 });
201             }
202              
203             =head2 parse_type
204              
205             Parse the variable type.
206              
207             =cut
208              
209             sub parse_type {
210 46     46 1 3763 my $self = shift;
211 46         110 $self->token_kw(qw(int float vec2 vec3 vec4));
212             }
213              
214             =head2 parse_parameter
215              
216             Parse the parameters for a function definition.
217              
218             =cut
219              
220             sub parse_parameter {
221 2     2 1 4 my $self = shift;
222              
223 2         44 $self->where_am_i;
224             $self->list_of(
225             ',',
226             sub {
227             $self->any_of(
228             # FIXME there are other parameter types!
229 2         51 sub { $self->token_kw(qw(void)) },
230             )
231 2     2   83 }
232 2         15 );
233             }
234              
235             =head2 parse_function
236              
237             Parse a function definition and code block.
238              
239             =cut
240              
241             sub parse_function {
242 2     2 1 6 my $self = shift;
243              
244 2         85 $self->where_am_i;
245             $self->sequence_of(sub {
246             [
247             $self->token_kw(qw(int float vec2 vec3 vec4 void)),
248             $self->token_ident,
249             $self->scope_of('(', sub {
250 2         352 $self->where_am_i;
251 2         76 $self->list_of(',', sub { $self->parse_parameter })
252 2     2   78 }, ')'),
  2         11  
253             $self->parse_block
254             ]
255 2         14 });
256             }
257              
258             =head2 parse_block
259              
260             Parse a block of statements (including the { ... } delimiters).
261              
262             =cut
263              
264             sub parse_block {
265 3     3 1 368 my $self = shift;
266 3         9 $self->where_am_i;
267 3     3   36 $self->scope_of( "{", sub { $self->parse_statements }, "}" );
  3         162  
268             }
269              
270             =head2 parse_statement
271              
272             Parse a single statement. This includes the trailing ; since it's a terminator not a
273             separator in GLSL.
274              
275             =cut
276              
277             sub parse_statement {
278 20     20 1 22 my $self = shift;
279              
280 20         34 $self->where_am_i;
281             $self->any_of(
282 20     20   207 sub { $self->parse_loopy_thing; },
283 19 50   19   2442 sub { my $decl = $self->parse_declaration or return; $self->expect(';'); $decl },
  19         1664  
  7         281  
284 12     12   663 sub { my $def = $self->parse_definition; $self->expect(';'); $def },
  12         317  
  12         547  
285 20         153 );
286             }
287              
288             =head2 parse_loopy_thing
289              
290             Handle control statements like if, while, that sort of thing. Any comments about the name of
291             this method should consider the first line in the L.
292              
293             =cut
294              
295             sub parse_loopy_thing {
296 20     20 1 19 my $self = shift;
297             $self->any_of(
298             sub {
299 20 50   20   198 my $kw = $self->token_control_keyword or return;
300 1 50       113 print "Have statement [$kw]\n" if $self->{debug};
301 1         2 [ $kw, $self->parse_expression, $self->parse_block ]
302             }
303 20         96 );
304             }
305              
306             =head2 token_control_keyword
307              
308             Parse a 'control keyword' (or 'loopy_thing' if you've been reading the other methods). That means
309             if, while, and anything else that C.
310              
311             =cut
312              
313             sub token_control_keyword {
314 20     20 1 22 my $self = shift;
315 20         44 $self->token_kw(qw(
316             if while
317             ));
318             }
319              
320             =head2 parse_expression
321              
322             Parse an expression, such as fract(c) or 1 + 3 / 12 * vec3(1.0).
323              
324             =cut
325              
326             sub parse_expression {
327 96     96 1 27729 my $self = shift;
328              
329 96         197 $self->where_am_i;
330             $self->any_of(
331 96     96   1137 sub { $self->scope_of('(', sub { $self->commit; $self->parse_expression }, ')') },
  1         53  
  1         7  
332             sub {
333 95     95   6683 [ $self->parse_nested_expression, $self->token_operator, $self->parse_nested_expression ]
334             },
335 74     74   6967 sub { $self->parse_nested_expression },
336 96         596 );
337             }
338              
339             =head2 parse_nested_expression
340              
341             Parse the bit inside an expression ... so not really a nested expression, more like an
342             expression atom or component maybe?
343              
344             =cut
345              
346             sub parse_nested_expression {
347 190     190 1 1736 my $self = shift;
348 190         330 $self->where_am_i;
349             $self->any_of(
350             sub {
351 190     190   1663 $self->where_am_i;
352 190 50       370 my $func = $self->token_function or return;
353 51 50       7550 print "Using function [$func]\n" if $self->{debug};
354             [
355             $func,
356             $self->scope_of('(', sub {
357 51         2598 $self->commit;
358             $self->list_of(',', sub {
359 72         4568 $self->parse_expression
360 51         456 });
361 51         275 }, ')')
362             ]
363             },
364 139     139   19162 sub { $self->token_float },
365 78     78   13869 sub { $self->token_glsl_ident },
366 190         1175 );
367             }
368              
369              
370             =head2 token_operator
371              
372             hey look it's a binary operator
373              
374             =cut
375              
376             sub token_operator {
377 95     95 1 9816 my $self = shift;
378 95         182 $self->where_am_i;
379 95         369 $self->expect(qr{[*+/-]|>=|<=|>|<|==|!=});
380             }
381              
382             =head2 token_function
383              
384             Known built-in functions. Eventually these will be extracted to a separate definition block
385             so that parameter types + counts can be verified.
386              
387             =cut
388              
389             sub token_function {
390 190     190 1 208 my $self = shift;
391 190         748 $self->token_kw(qw(
392             radians degrees sin cos tan asin acos atan sinh cosh tanh asinh acosh atanh pow exp
393             log exp2 log2 sqrt inversesqrt abs sign floor trunc round roundEven ceil fract mod
394             min max clamp mix step smoothstep isnan isinf length distance dot cross normalize
395             faceforward reflect refract matrixCompMult transpose inverse outerProduct lessThan
396             lessThanEqual greaterThan greaterThanEqual equal notEqual any all not textureSize
397             texture textureProj textureLod textureGrad textureOffset texelFetch texelFetchOffset
398             textureProjLod textureProjGrad textureProjOffset textureLodOffset textureGradOffset
399             textureProjLodOffset textureProjGradOffset dFdx dFdy fwidth noise1 noise2 noise3
400             noise4 vec2 vec3 vec4 ftransform
401             ));
402             }
403              
404             =head2 token_preprocessor_directive
405              
406             Parse a preprocessor directive.
407              
408             Note that '#' is perfectly valid as a directive.
409              
410             =cut
411              
412             sub token_preprocessor_directive {
413 0     0 1 0 my $self = shift;
414 0         0 $self->token_kw(qw(
415             define
416             undef
417             if
418             ifdef
419             ifndef
420             else
421             elif
422             endif
423             error
424             pragma
425             extension
426             version
427             line
428             ));
429             }
430              
431             =head2 token_macro
432              
433             Pick up on #defined (macro) values.
434              
435             =cut
436              
437             sub token_macro {
438 0     0 1 0 my $self = shift;
439 0         0 $self->token_kw(keys %{$self->{macro}});
  0         0  
440             }
441              
442             =head2 expand_macro
443              
444             Attempt to expand the given macro.
445              
446             =cut
447              
448             sub expand_macro {
449 0     0 1 0 my $self = shift;
450 0         0 my $macro = shift;
451 0 0       0 my $code = $self->macro($macro, @_) or return undef;
452 0         0 return $code->($self);
453             }
454              
455             =head2 token_glsl_ident
456              
457             A GLSL identifier. Somewhat vague term, currently includes variables and
458             qualified pieces (colour.r).
459              
460             =cut
461              
462             sub token_glsl_ident {
463 78     78 1 281 my $self = shift;
464 78         124 $self->where_am_i;
465 78     78   166 [ $self->token_ident, $self->maybe(sub { $self->expect('.'); $self->token_ident }) ]
  78         7061  
  10         400  
466             }
467              
468             =head2 parse_statements
469              
470             Parse more than one statement.
471              
472             =cut
473              
474             sub parse_statements {
475 3     3 1 5 my $self = shift;
476 3     20   16 $self->sequence_of(sub { $self->parse_statement });
  20         891  
477             }
478              
479             1;
480              
481             __END__