jducoeur: (Default)
[personal profile] jducoeur
Here's a question for the Perl and/or regexp experts in the audience; all help is solicited.

ProWiki has a query language built in. Simplifying greatly, the syntax looks like this:
{? [query terms] : [display results] ?}
This translates roughly as "for each page that matches the given query terms, show the display results, interpolating the properties of the page". That all works nicely, and is at the heart of what ProWiki does.

The problem is, I'd really like to be able to do this recursively. That is, I'd like to be able to construct a query like (to take today's example, one of many):
{?~Faction : %%Name%% -- {?~Character && Faction==%%PAGENAME%% : %%Name%% ?} ?}
That would translate as something like, "For each Faction, display the Faction's Name, and then for each Character in that Faction, display the Character's Name". Basically, nested foreach loops.

That's conceptually straightforward, but I'm stuck on how to parse it. ProWiki, being based on UseMod, uses Perl regex for its parsing. That mostly works fine, but I can't figure out how to get it to work recursively. I need to find the *matching* {? ?} pairs, extracting as plaintext any pairs that might be contained inside them. (The Perl code itself will then deal with the recursion into the plaintext subexpression.)

Can this be done straightforwardly in regex? It seems like a fairly common problem -- it's basically a fancy variant of parenthesis matching -- but I'm not hip enough to regex to see the answer. It's not simply a matter of matching first and last delimiters in the string, since a given page might contain several unrelated top-level expressions; therefore, I need to find the genuinely *matching* delimiters.

I know there are a bunch of Perl gurus out there, so if you can outline the solution to me (even the solution to the basic parenthesis-matching problem would probably show me how to do it), I'd be grateful. Thanks...

(no subject)

Date: 2006-12-30 07:46 am (UTC)
siderea: (Default)
From: [personal profile] siderea
I am not studly enough to help you with your regexp question. :(

But I wanted to make a syntax suggestion based on my running "PHP vs. CF, Which Sucks More" log.

I highly recommend having a flavor of loop control which is foreachish, in that it runs once for each member of a set, but when nested, by default, inherits. A sort of loop, in which you won't have to write "&& Faction==%%PAGENAME%%" because that's the default.

Because in a web-results language, you do that all. The. Time.

I find I want to express your second concept in something VB-ish:

%%factions{.name .characters{.name}}%%

That probably wouldn't work with arbitrary objects, because English puralizes poorly.

(no subject)

Date: 2006-12-30 09:24 pm (UTC)
siderea: (Default)
From: [personal profile] siderea
Glad you enjoyed that. Are you unfamiliar with CF? If so, at some point I should explain the CF CFOUTPUT syntax and usage to you. Not that I think you necessarily want to adopt them, but because having that paradigm floating around in your brain colliding with other stuff will probably be usefully inspiring.

there is no *formal* relationship between the Faction objects and the Faction property of the Character objects that happen to point to them

Why not? I would think that asserting all object/property names within a namespace/wikispace must be unique (except some special built-in keywords like "name" and "number" and "length" and "type", perhaps), so that a reference to an class as a property means a the property is a foreign key to the object. I'm trying to imagine a case where that is bad, and not coming up with something, though that may be an insufficiency of caffeine on my part.

(no subject)

Date: 2006-12-30 01:44 pm (UTC)
From: [identity profile] metahacker.livejournal.com
I hacked at this for a while, but it looks like one answer is "wait a few weeks":
http://dev.perl.org/perl6/doc/design/exe/E05.html
search for "Match nested parentheses maintainably"

Given that this is asking a regexp to act as a PDA it's not actually that simple...

(no subject)

Date: 2006-12-30 02:17 pm (UTC)
From: [identity profile] metahacker.livejournal.com
BTW, if all you want is non-recursive out to, say, 2 levels of nested parens, that's much easier. for the above example:


/\{\?~Faction : (\w+) -- (?:\{\?~Character && Faction==(\w+) : \1 \?\})* \?\}/

or something like that. The (?: foo) construct groups without returning as a match; the \1 harks back to the faction name or should.

{? and ?} are, of course, pathological in perl patterns and have to be escaped...who picked those delimiters? ;)

(no subject)

Date: 2006-12-30 04:31 pm (UTC)
From: [identity profile] metahacker.livejournal.com
You can construct patterns on the fly using variables, but that requires actual perl code; or use alternation plus grouping to return the key as well as the value:
/((faction|organization|company): (\w) )*/
will return things like "faction", "spark", "company", "heterodyne foundation", ...

(no subject)

Date: 2006-12-30 04:04 pm (UTC)
ext_44932: (Default)
From: [identity profile] baavgai.livejournal.com
Not a direct perl guru answer, but rather an observation that may help. Your question looks a whole lot like XSLT processing.

The methodology is that select="//X" returns a node set of all X elements. Further select="//X[@foo]" returns a node set that has attribute foo, and select="//X[@foo='bar']" returns a set where foo exists and equals the scalar bar.

This is a matching syntax and has nothing to do with the display syntax. The display is to work on elements X and show what you would of them. I don't know if this helps in the slightest, but it's what your post made me think of.

(no subject)

Date: 2006-12-30 05:52 pm (UTC)
mindways: (Default)
From: [personal profile] mindways
I *know* that you can't do what you want to do using VB's RegEx object. You can write a regex that will handle a specified depth of nesting, but it is not possible to handle the general case. (I had to deal with this at work.)

I *believe* that current Perl regex syntax is similarly constrained. (Haven't had a chance to follow and review the link posted above re: what's coming in a few weeks.)

However, if you're just looking for chunks enclosed by simple begin-delimiter/end-delimiter pairs, you don't necessarily need regexes - unless I'm grievously misremembering, you can find all such matching pairs and their contents (building up a parse tree of whatever sort you want) in a single beginning-to-end pass through the string. Not as elegant as a one-line regex, but not hideously complex, either, and it'll get the job done whereas the regex (currently) won't.

(Or you could use Lisp. Excessive refactoring to solve a small problem, ahoy! ;)

(no subject)

Date: 2006-12-30 10:17 pm (UTC)
From: [identity profile] asim.livejournal.com
This below assumes you can install modules...you've already seen (and figured out yourself) that the "pure" regex solution is hairy, at best.
I know you don't want to re-write the parser, but...have you looked at the Parse::RecDescent (http://search.cpan.org/~dconway/Parse-RecDescent-1.94/lib/Parse/RecDescent.pod) module?
Otherwise, since you don't want to store names (and you could write a routine that pulls the names for the patterns by just querying the DB), you might want to consider -- if I understand the problem alright -- storing the position, and "rewinding" manually as you go. Regex::Iterator (http://search.cpan.org/~simonw/Regex-Iterator-0.3/lib/Regex/Iterator.pm) has a "rewind" function that can help with that.

You might want to also look into the Text::Balanced (http://search.cpan.org/~dconway/Text-Balanced-v2.0.0/lib/Text/Balanced.pm) module.

Hope this helps!

(no subject)

Date: 2006-12-31 03:42 pm (UTC)
From: [identity profile] http://users.livejournal.com/merle_/
In normal regular expressions, I don't believe you can. It's possible that some languages provide recursion, and Perl's /e modifier goes well beyond what a "normal" regexp can do. As [livejournal.com profile] metahacker noted, if you know the number of levels, you can hardcode it, but it won't do an infinite number of levels of recursion.

I'm surprised, though, that Perl5 can match strings of validly nested parentheses. That was the canonical example in my coursework of "something a regexp cannot do, because a FSA cannot do it".

(no subject)

Date: 2007-01-02 06:27 pm (UTC)
From: [identity profile] learnedax.livejournal.com
The paren-matching in perl5 is basically the same mechanism as hardcoding a finite number of levels, it just makes clever use of inserting a variable to be expanded lazily as a regex, where that variable can be the main pattern itself.

Note, [livejournal.com profile] jducoeur, that matching multi-character delimiters also complicates things a fair amount, as per the classic problem of matching C comments with regex.

(no subject)

Date: 2007-01-03 01:13 am (UTC)
From: [identity profile] learnedax.livejournal.com
Oh, I wasn't saying you should use a single-character delimiter, just providing another reason why the regex could be complicated to maintain - whereas a relatively simple (even actually iterative with pushdown) recursive descent would work fine. OTOH, it does look like you now have enough to push off a final decision on the parsing model.

Personally, I think verbosity would kill adoption. I'm also not sure that XML is a good metaphorical match for a query-and-render blob. Certainly the qualities you need to balance are simplicity and brevity.

(no subject)

Date: 2007-01-02 06:32 pm (UTC)
mneme: (Default)
From: [personal profile] mneme
the /e modifier isn't really relevant, becuase it's a modifier to the right-hand side of a substitution, not the regular expression.

This is actually a good textbook example of "things that are problematic in perl regular expressions. That said, perl regexpes -do- go far beyond textbook regexps, due to experimental constructs like (?{ code }) (embed perl code in a regexp) and more functionally, (??{ re }), etc (see the perlre manpage).

(??{re}) is probably a usable answer -- this worked well in my testcase, though it might need some tweaking:

my $re;
$re = qr/({\?\s*([^:]+)\s*:\s*((?:(??{$re})|.)*?)\s*\?})/;

($1 is the full matched expression, $2 is the left hand side, $3 is the right hand side).


(no subject)

Date: 2007-01-02 06:45 pm (UTC)
From: [identity profile] http://users.livejournal.com/merle_/
No, the /e modifier doesn't alter much in terms of the original problem. It does extend regular expressions way past what an FSA can do -- even a HMM can't simulate it.

I clearly need to read more about Perl5 regexps. Although perhaps not, because then I would be upset when the same functionality does not work in my editors...

(no subject)

Date: 2007-01-02 08:03 pm (UTC)
mneme: (Default)
From: [personal profile] mneme
Only if you consider a substitute pattern a regular expression (as opposed to a match).

At least for me, the perl extended regular expressions (anything starting with (? ) avoid contaminating my memory, and I can use them without wanting to use them in emacs or whatnot. (But then, the big difference between perl-style regular expressions and many others -- that the magical meanings of parentheses and pipses are default, to be turned off by escaping them, not turned on, does help avoid confusion).

(no subject)

Date: 2007-01-02 09:09 pm (UTC)
From: [identity profile] http://users.livejournal.com/merle_/
Oh, I have very un-fond memories of the days of yore, when sed, grep, egrep, fgrep, and awk were all highly incompatible. *shudder*

(no subject)

Date: 2007-01-03 01:10 am (UTC)
From: [identity profile] http://users.livejournal.com/merle_/
*shakes head* No wonder Perl 6 has been "in the wings" for several years now.

(no subject)

Date: 2007-01-03 02:22 pm (UTC)
mneme: (Default)
From: [personal profile] mneme
Excellent.

I'll note that while it's a pretty solid regexp, which -should- work in all or most cases, that I'd be very tempted to tighten it up were I to try to use it in production use, ie, something like:


  qr{ ( {\?\s*( [^:]+ ) \s* : \s* 
      ( (?: (??{$re} ) | [^?]+ | \?(?!}}+ })*)\s*\?}) 
  }x;



(which is to say, the alternation is "things matching this token, or things that can't close this token"). The problem with the original regexp is that it may be able to exhibit fairly bad worst-case alternation behavior, and regardless, isn't nearly as predictable as this one is in how it will match.

Profile

jducoeur: (Default)
jducoeur

June 2025

S M T W T F S
12 34567
891011121314
15161718192021
22232425262728
2930     

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags