File Coverage

blib/lib/TM/CTM/Parser.pm
Criterion Covered Total %
statement 20 22 90.9
branch 3 6 50.0
condition n/a
subroutine 4 4 100.0
pod 0 2 0.0
total 27 34 79.4


line stmt bran cond sub pod time code
1             package TM::CTM::Parser;
2              
3 1     1   1548 use TM::Literal;
  1         4  
  1         489  
4              
5             our $ctm_grammar = q {
6             {
7             my $store;
8             my $log;
9             my $implicits;
10             use Data::Dumper;
11             use TM;
12             use TM::Literal;
13             my %prefixes;
14             my %prefixes_backup;
15             my %wildcards;
16             my %wildcards_backup;
17             my %templates;
18              
19             my $lid;
20             }
21              
22             # comments are handled outside
23              
24             startrule : { $store = $arg[0];
25             $log = $arg[1];
26             $implicits = $arg[2];
27             %prefixes = ('xsd' => TM::Literal->XSD);
28             %templates = ();
29             %wildcards = ();
30             }
31             topicmap
32              
33             topicmap : prolog directive(s?)
34             ( directive
35             | template
36             | template_invocation
37             | topic
38             | association
39             )(s?)
40              
41             prolog : encoding(?) version(?)
42              
43             directive : prefix_directive
44             | restore_directive
45             | backup_directive
46             | include_directive
47             #| version_directive |
48             #topicmapid_directive |
49             # mergemap_directive |
50             # baseuri_directive
51             # # INCLUDE is handled outside
52              
53             # this is NOT visible from the outside
54             backup_directive : '%backup' {
55             %prefixes_backup = %prefixes;
56             %wildcards_backup = %wildcards;
57             %wildcards = ();
58             }
59             restore_directive : '%restore' {
60             %prefixes = %prefixes_backup;
61             %wildcards = %wildcards_backup;
62             }
63              
64             prefix_directive : '%prefix' identifier reference {
65             my $uri = $item[3];
66             $prefixes{$item[2]} = $uri;
67             }
68             reference : iri
69             | /\S+/
70              
71             include_directive : '%include' ( /^inline:.*?\n/ | iri ) {
72             my $src = $item[2];
73             my $include; # we are trying to figure that one out
74             if ($src =~ /^inline:(.*)/s) {
75             $include = $1;
76             } else { # we try our luck with LWP
77             use LWP::Simple;
78             $include = get($1) or
79             $TM::log->logdie (__PACKAGE__ .": unable to load '$1'\n");
80             }
81             $text = $include . $text;
82             }
83              
84              
85             encoding : 'TODO' '@' string # not analyzed here, but capture in the calling program
86             # no good here if we would have to translate the encoding
87              
88             version : 'TODO'
89              
90             #-- template ------------------------------------------------------------------------------------------------
91              
92             template: 'def' identifier parameters /(.*?)(?=\bend\n)/s 'end' {
93             my $return = {
94             name => $item[2],
95             params => $item[3],
96             body => $item[4],
97             };
98             $templates{$return->{name}}
99             and die "template '".$return->{name}."' already defined";
100             $templates{$return->{name}} = $return;
101             }
102              
103             parameters : '(' variable(s? /,/) ')' { $return = $item[2]; }
104              
105             variable : /\$\w[\w-]*/
106              
107             topic_template_invocation:
108             identifier { $templates{$item[1]} }
109             '(' argument(s /,/) ')' {
110             my $tmpl = $templates{$item[1]};
111             # warn Dumper $templates{$item[1]};
112             # warn Dumper $item[4];
113              
114             my $bu = $store->baseuri;
115             $arg[0] =~ s/^$bu//; # pretend internal identifier
116             unshift @{ $item[4] }, $arg[0]; # add topic as first param
117              
118             $text .= "\n\n%backup\n\n" . _expand_tmpl ($tmpl, $item[4]) . "\n\n%restore\n\n";
119             $return = 1;
120              
121             sub _expand_tmpl {
122             my $tmpl = shift;
123             my $name = $tmpl->{name};
124             my $body = $tmpl->{body};
125             my $params = $tmpl->{params};
126             my $args = shift;
127             my %P; # formal -> actual
128             foreach my $fp (@$params) {
129             $P{$fp} = shift @$args
130             or die "too few arguments for '$name'";
131             }
132             die "too many arguments for '$name'" if @$args;
133              
134             foreach my $p (keys %P) {
135             $p =~ s/\$//; # remove $, so that regexp below works
136             $body =~ s/\$$p/$P{'$'.$p}/g;
137             }
138             return "\n" . $body . "\n"; # extend the text at the end;
139             }
140             }
141              
142             template_invocation:
143             identifier { $templates{$item[1]} }
144             '(' argument(s? /,/) ')' {
145             my $tmpl = $templates{$item[1]}; # we know we have something
146             $text .= "\n\n%backup\n\n" . _expand_tmpl ($tmpl, $item[4]) . "\n\n%restore\n\n";
147             $return = 1;
148             }
149              
150             argument : literal { $return = $item[1]->[0]; # get only the string value
151             }
152             | topic_ref {
153             my $bu = $store->baseuri;
154             ($return = $item[1]) =~ s/^$bu//; # pretend internal identifier
155             }
156              
157             #-- association ---------------------------------------------------------------------------------------------
158              
159             association : topic_identity '(' roles ')' scope(?) reifier(?)
160             {
161             my $scope = $item[5]->[0] ? $item[5]->[0] : 'us';
162             my ($a) = $store->assert (bless [ undef, # LID
163             $scope, # SCOPE
164             $item[1], # TYPE
165             TM->ASSOC, # KIND
166             [ map { $_->[0] } @{$item[3]} ], # ROLES
167             [ map { $_->[1] } @{$item[3]} ], # PLAYERS
168             undef ], 'Assertion');
169             $return = $a;
170             $store->assert(Assertion->new(kind => TM->ASSOC,
171             type => 'isa',
172             roles => [ qw(instance class) ],
173             players => [ $scope, 'scope' ],
174             scope => undef)) if $scope ne 'us';
175             $store->internalize ($item[6]->[0], $a->[TM->LID]) if $item[6]->[0];
176             $return;
177             }
178             roles : role(s /,/)
179              
180             role : typing player { $return = [ $item[1], $item[2] ]; } # reifier(?)
181              
182             player : topic_ref
183              
184             #-- topic ---------------------------------------------------------------------------------------------------
185              
186             topic : topic_identity { $lid = $item[1]; }
187             topic_tail[$lid](s?)
188             '.'
189              
190             topic_identity : subject_identifier { $return = $store->internalize (undef, $item[1]); }
191             | identifier { $return = $store->internalize ($item[1]); }
192             | subject_locator { $return = $store->internalize (undef, $item[1]); }
193             #| item_identifier
194             | wildcard
195             #| variable
196              
197             wildcard : named_wildcard
198             | anonymous_wildcard
199              
200             anonymous_wildcard : '?' { $return = $store->internalize (sprintf "uuid-%010d", $TM::toplet_ctr++); }
201              
202             named_wildcard : /\?(\w[\w-]*)/ {
203             my $id = $1;
204             $wildcards{$id} ||=
205             $store->internalize (sprintf "uuid-%010d", $TM::toplet_ctr++);
206             $return = $wildcards{$id};
207             }
208              
209             identifier : /\w[\w-]*/
210              
211             topic_ref : topic_identity { $return = ref ($item[1]) ? $store->internalize (undef, $item[1]) : $item[1]; }
212             | embedded_topic
213              
214             embedded_topic :
215              
216             embedded_topic : '[' { $llid = sprintf "uuid-%010d", $TM::toplet_ctr++; }
217             topic_tail[$llid]
218             ']' { $return = $llid; }
219              
220             subject_identifier : iri { $return = \ $item[1]; }
221              
222             subject_locator : '=' iri { $return = $item[2]; }
223              
224             qname : /(\w[\w-]+):(\w[\w-]+)/ {
225             # ^^^^^^^^^^ ^^^^^^^^^^
226             # identifier:identifier , but wo blanks
227             die "undefined prefix '$item[1]'" unless $prefixes{$1};
228             $return = $prefixes{$1}.$2;
229             }
230             topic_tail :
231             ( instance_of[$arg[0]]
232             | kind_of[$arg[0]]
233             | topic_template_invocation[$arg[0]] # must be before assignment, otherwise would auto-register
234             #| identity[$arg[0]]
235             | subject_identifier { $return = undef if $text =~ /^\s*:/s } # a : in front ?
236             { $store->internalize ($arg[0], $item[1]); }
237             | subject_locator { $return = undef if $text =~ /^\s*:/s }
238             { $store->internalize ($arg[0], $item[1]); }
239             #| item_identifier
240             | assignment[$arg[0]]
241             ) /;?/
242              
243              
244             assignment : name[$arg[0]]
245             | occurrence[$arg[0]]
246              
247             #-- name -------------------------------------------------------------------------------------------------------------
248              
249             name : '-' typing(?) string scope(?) reifier(?) #variant(s?)
250             {
251             my $type = $item[2]->[0] ? $item[2]->[0] : 'name';
252             my $scope = $item[4]->[0] ? $item[4]->[0] : 'us';
253              
254             my ($a) = $store->assert ( bless [ undef, # LID
255             $scope, # SCOPE
256             $type, # TYPE
257             TM->NAME, # KIND
258             [ 'thing', 'value' ], # ROLES
259             [ $arg[0], $item[3] ],# PLAYERS
260             undef ], 'Assertion' );
261             $store->assert(Assertion->new(kind => TM->ASSOC,
262             type => 'is-subclass-of',
263             roles => [ qw(subclass superclass) ],
264             players => [ $type, 'name' ],
265             scope => undef)) if $type ne 'name';
266             $store->assert(Assertion->new(kind => TM->ASSOC,
267             type => 'isa',
268             roles => [ qw(instance class) ],
269             players => [ $scope, 'scope' ],
270             scope => undef)) if $scope ne 'us';
271             $store->internalize ($item[5]->[0], $a->[TM->LID]) if $item[5]->[0];
272             $return = $a;
273             }
274              
275             occurrence : type ':' iri_literal scope(?) reifier(?)
276             {
277             my $type = $item[1];
278             my $scope = $item[4]->[0] ? $item[4]->[0] : 'us';
279              
280             my ($a) = $store->assert ( bless [ undef, # LID
281             $scope, # SCOPE
282             $type, # TYPE
283             TM->OCC, # KIND
284             [ 'thing', 'value' ], # ROLES
285             [ $arg[0], $item[3] ],# PLAYERS
286             undef ], 'Assertion' );
287             $store->assert(Assertion->new(kind => TM->ASSOC,
288             type => 'is-subclass-of',
289             roles => [ qw(subclass superclass) ],
290             players => [ $type, 'occurrence' ],
291             scope => undef)) if $type ne 'occurrence';
292             $store->assert(Assertion->new(kind => TM->ASSOC,
293             type => 'isa',
294             roles => [ qw(instance class) ],
295             players => [ $scope, 'scope' ],
296             scope => undef)) if $scope ne 'us';
297             $store->internalize ($item[5]->[0], $a->[TM->LID]) if $item[5]->[0];
298             $return = $a;
299             }
300              
301             typing : type ':' { $return = $item[1]; }
302              
303             iri_literal : literal
304             | iri { $return = new TM::Literal ($item[1], TM::Literal->URI); }
305              
306             type : topic_ref
307              
308             scope : '@' topic_ref { $return = $item[2]; }
309              
310             reifier : '~' topic_ref { $return = $item[2]; }
311              
312             #-- isa and ako ---------------------------------------------------------------------------------------------
313              
314             instance_of : 'isa' topic_ref { $store->assert ( [ undef,
315             undef,
316             'isa',
317             undef,
318             [ 'class', 'instance' ],
319             [ $item[2], $arg[0] ],
320             ] ); }
321             kind_of : 'ako' topic_ref { $store->assert ( [ undef,
322             undef,
323             'is-subclass-of',
324             undef,
325             [ qw(subclass superclass) ],
326             [ $arg[0], $item[2] ],
327             ] ); }
328            
329              
330              
331              
332              
333              
334             #-- old junk to be deleted
335              
336             version_directive : '#VERSION' string
337             {
338             my $version = $item[2];
339             $log->logdie (__PACKAGE__ . ": VERSION not supported '$version'") unless $version =~ /^1\.[23]$/;
340             }
341              
342             topicmapid_directive : '#TOPICMAP' ( name | reify )
343             {
344             $log->logdie (__PACKAGE__ . ": TOPICMAP directive ignored (use proper means)");
345             }
346              
347             mergemap_directive : '#MERGEMAP' uri tm_format(?)
348             {
349             my $uri = $item[2];
350             #warn "uri is $uri";
351             my $format = $item[3]->[0] ? $item[3]->[0] : 'ltm';
352             my $tm;
353             if ($format =~ /^ltm$/i) {
354             $tm = new TM::Materialized::LTM (url => $uri);
355             } elsif ($format =~ /^xtm$/i) {
356             $tm = new TM::Materialized::XTM (url => $uri);
357             } elsif ($format =~ /^astma$/i) {
358             $tm = new TM::Materialized::AsTMa (url => $uri);
359             } else {
360             $log->logdie (__PACKAGE__ . ": unsupported TM format '$format'");
361             }
362             $tm->sync_in;
363             $store->add ($tm);
364             #warn "after merged in".Dumper $store;
365             $return = $uri;
366             }
367              
368             tm_format : string
369              
370             baseuri_directive : '#BASEURI' uri
371              
372             xtopic : '[' name types(?) topname(?) reify(?) subject(?) indicator(s?) ']'
373             {
374             #warn "topic ".Dumper \@item;
375             my $id = $store->internalize ($item[2] => $item[6]->[0]); # maybe there is a subject addr, maybe not
376              
377             # add the subject indicators
378             map { $store->internalize ($id => $_ ) } @{$item[7]};
379              
380              
381             if ($item[3] and $item[3]->[0]) {
382             $store->assert ( map {
383             [ undef,
384             undef,
385             'isa',
386             undef,
387             [ 'class', 'instance' ],
388             [ $_, $id ],
389             ] }
390             @{$item[3]->[0]} );
391             map { $implicits->{'isa-thing'}->{$_}++ } @{$item[3]->[0]}; # the types and the ID are declared implicitely
392             }
393             #warn "item 4".Dumper $item[4];
394             if ($item[4] and @{$item[4]}) {
395             my $topnames = $item[4]->[0];
396             #warn "topnames ".Dumper $topnames;
397             my ($a) = $store->assert ( map {[ undef, # LID
398             $topnames->{scope}->[0], # SCOPE
399             'name', # TYPE
400             TM->NAME, # KIND
401             [ 'thing', 'value' ], # ROLES
402             [ $id, $_ ], # PLAYERS
403             undef ] }
404             @{$topnames->{names}}[0] ); # use the first for a name
405             $return = $a;
406             # TODO (2..3) for the variants
407              
408             #warn "basename reify ".Dumper $item[5];
409             # reification of the basename
410             $store->internalize ($item[5]->[0], $a->[TM->LID]) if $item[5]->[0];
411              
412             {
413             map { $implicits->{'isa-scope'}->{ $_ }++ } @{$topnames->{scope}};
414             }
415             }
416              
417             $return = $id;
418             }
419              
420             types : ':' name(s) { $return = $item[2]; }
421              
422             subject : '%' uri { $return = $item[2]; } # for subject addrs the encoding is 'no-reference'
423              
424             indicator : '@' uri { $return = \ $item[2]; } # for indicators it is 'send as string reference'
425              
426             topname : '=' basesortdispname scope(?)
427             {
428             #warn "basenames".Dumper \@item;
429             $return = {
430             scope => $item[3],
431             names => $item[2],
432             };
433             }
434              
435             basesortdispname:
436              
437             basename : string { $return = new TM::Literal ($item[1], 'xsd:string'); }
438              
439              
440              
441             occur : '{' occ_topic ',' occ_type ',' resource '}' scope(?) reify(?)
442             {
443             my $id = $store->internalize ($item[2]);
444             my ($a) = $store->assert ([ undef, # LID
445             $item[8]->[0], # SCOPE
446             $item[4], # TYPE (MUST BE DEFINED!)
447             TM->OCC, # KIND
448             [ 'thing', 'value' ], # ROLES
449             [ $id, $item[6] ], # PLAYERS
450             undef ]);
451              
452             { # memorize basename types and scopes as implicitely defined
453             $implicits->{'isa-scope'}-> { $item[8]->[0] }++ if $item[8]->[0]; # get the bloody scopes and tuck them away
454             $implicits->{'subclasses'}->{ 'occurrence' }->{ $item[4] }++;
455             }
456              
457             #warn "reify ".Dumper $item[9];
458             $store->internalize ($item[9]->[0], $a->[TM->LID]) if $item[9]->[0];
459              
460             $return = $a;
461             }
462              
463             occ_topic: name
464              
465             occ_type : name
466              
467             reify : '~' name
468              
469             resource : uri { $return = new TM::Literal ($item[1], 'xsd:uri') }
470             |
471             DATA { $return = new TM::Literal ($item[1], 'xsd:string') }
472              
473             DATA : '[[' /.*(?=\]\])/sx ']]' { $return = $item[2]; }
474              
475             uri : string
476              
477             comment : '/*' /.+?/s '*/'
478              
479              
480             xname : /^\w[:\-\w]*/
481             {
482             my $name = $item[1];
483             if ($name =~ /^(\w+):/) {
484             my $prefix = $1;
485             if ($prefixes{$prefix}) {
486             $name =~ s/^$prefix:/$prefixes{$prefix}/;
487             $return = $name;
488             } else {
489             $return = undef;
490             }
491             } else {
492             $return = $name;
493             }
494             }
495            
496             | /^\w[-\w]*/
497             {
498             $return = $item[1];
499             }
500              
501             };
502              
503             sub new {
504 50     50 0 486 my $class = shift;
505 50         198 my %options = @_;
506 50         230 my $self = bless \%options, $class;
507              
508 50         96 $::RD_HINT = 1;
509 50         82 eval {
510 50         8351 require TM::CTM::CParser;
511 50         358 $self->{parser} = TM::CTM::CParser->new();
512 50 50       2034 }; if ($@) {
513 0         0 warn "could not find CParser ($@)";
514 1     1   2218 use Parse::RecDescent;
  1         50433  
  1         9  
515 0 0       0 $self->{parser} = new Parse::RecDescent ($ctm_grammar . $TM::Literal::grammar) or $TM::log->logdie (scalar __PACKAGE__ .": problem in grammar ($@)");
516             };
517 50         246 return $self;
518             }
519              
520             sub parse {
521 50     50 0 404 my $self = shift;
522 50         144 my $text = shift;
523            
524             # we not only capture what is said EXPLICITELY in the map, we also collect implicit knowledge
525             # we could add this immediately into the map at parsing, but it would slow the process down and
526             # it would probably duplicate/complicate things
527 50         304 my $implicits = {
528             'isa-thing' => undef, # just let them spring into existence
529             'isa-scope' => undef, # just let them spring into existence
530             'subclasses' => undef
531             };
532              
533              
534             # while ($text =~ /\#INCLUDE\s+\"(.+)\"/s) { # find first
535             # my $src = $1;
536             # my $include; # we are trying to figure that one out
537             # if ($src =~ /^inline:(.*)/s) {
538             # $include = $1;
539             # } else { # we try our luck with LWP
540             # use LWP::Simple;
541             # $include = get($1) || die "unable to load '$1'\n";
542             # }
543             ## use TM::Utils;
544             ## my $include = TM::Utils::get_content ($1);
545             # $text =~ s/\#INCLUDE\s+\"(.+)\"/\n$include\n/s; # replace first to find
546             # }
547              
548             # encoding
549             # NOTE: currently ignored
550              
551             # remove comment
552             # NOTE: LTM comments are extremely complex as they may appear anywhere
553             # I ignored this and get rid of them on a syntactic level, even risking to throw away /* */ within string. So what.
554             # $text =~ s|/\*.*?\*/||sg; # global multiline
555              
556             # $::RD_TRACE = 1;
557 50         748 $self->{parser}->startrule (\$text, 1, $self->{store}, $TM::log, $implicits);
558 47 100       1876 $TM::log->logdie ( scalar __PACKAGE__ . ": Found unparseable '".substr($text,0,40)."....'" ) unless $text =~ /^\s*$/s;
559              
560             # { # resolving implicit stuff
561             # my $store = $self->{store};
562              
563             # { # all super/subclasses
564             # foreach my $superclass (keys %{$implicits->{'subclasses'}}) {
565             # $store->assert ( map {
566             # [ undef, undef, 'is-subclass-of', TM->ASSOC, [ 'superclass', 'subclass' ], [ $superclass, $_ ] ]
567             # } keys %{$implicits->{'subclasses'}->{$superclass}});
568             # }
569             # #warn "done with subclasses";
570             # }
571             # { # all things in isa-things are THINGS, simply add them
572             # ##warn "isa things ".Dumper [keys %{$implicits->{'isa-thing'}}];
573             # $store->internalize (map { $_ => undef } keys %{$implicits->{'isa-thing'}});
574             # }
575             # { # establishing the scoping topics
576             # $store->assert (map {
577             # [ undef, undef, 'isa', TM->ASSOC, [ 'class', 'instance' ], [ 'scope', $_ ] ]
578             # } keys %{$implicits->{'isa-scope'}});
579             # }
580             # }
581             }
582              
583              
584             =pod
585              
586             =head1 SEE ALSO
587              
588             L
589              
590             =head1 AUTHOR INFORMATION
591              
592             Copyright 200[8], Robert Barta , All rights reserved.
593              
594             This library is free software; you can redistribute it and/or modify it under the same terms as Perl
595             itself. http://www.perl.com/perl/misc/Artistic.html
596              
597             =cut
598              
599             our $VERSION = '0.2';
600              
601             1;
602              
603             __END__