Formal review of std.lexer

Alix Pexton via Digitalmars-d digitalmars-d at puremagic.com
Tue Apr 15 02:28:30 PDT 2014


On 14/04/2014 10:34 PM, Brian Schott wrote:
> ubyte[] is required for the lexer to work quickly. That lexer range is
> designed to keep track of the column and line numbers.

I can understand that speed requires the input to be handled as bytes 
instead of chars, but the restriction to an ubyte[] over an 
randomAccessRange seems to me un-Dlang-y.

If the LexerRange is only there to optionally add line/column numbering 
support then I think it needs a much more descriptive name and much 
better documentation.

> That function's purpose is to determine if the current code unit
> signifies the end of an identifier/keyword. When lexing "fortunate", the
> lexer would spot "for", and then call the separating function with the
> index of "u". In the case of D, it would say that this does NOT end a
> word, and "fortunate" should be lexed as an identifier. If it was called
> with an index of a space or parenthesis, it should return true.

Somehow I had skipped the salient part of the documentation for this, so 
my apologies (at first I thought that the DDoc output I was reading must 
have been out of sync with the source (it was, but not that much), but 
further investigations suggest some form of temporary blindness).

This description squares with what I had surmised from reading the code, 
and I can see why it would be more efficient than the common technique 
of comparing every matched identifier to the list of keywords. I do 
wonder however if there might be another way to attain the same 
efficiency without the need for the separating function (I should have 
replied to this last night when my ideas on the matter were clearer, 
sleep seems to have stolen my theoretical alternative ><).

I'm also curious about the decision that the signature of the separating 
function should take the offset to the character than needs to be 
tested. A more Dlang-y thing to do in my mind would be to pass a range 
that begins at the first character after the matched keyword.

> If more than one prefix is dispatched to the same handler, the handler
> cannot blindly call popFront or popFrontN. If the lexer author knows
> that the handler will only be called in certain places, than checking
> !empty is a waste of cycles. I don't think it's possible for the lexer
> generator to enforce this.

I don't think the lexer needs to do anything extra to ensure that a 
handler can begin its work both without repeating calls to .empty or 
blindly calling popFront. To do so requires that the input be treated a 
a forward range. Before reading a token, store the .save of the input, 
then advance the input as the token is matched counting the consumed 
elements. When the handler is called it will have the option of 
including the token by adding to the count and returning a slice that 
begins at the .save'd position or ignoring the length of the match and 
returning a slice that begins at the position after the matched token.

I strongly believe that a model that requires the user to reason about a 
library providing ranges that are "logicaly !empty" is a misstep.

> XML doesn't seem to have very distinct lexing/parsing phases like JSON
> markup or Java code does. This lexer generator may not be the best way
> to deal with XML.

I don't really know how the grammar in the XML standard compares to 
others in general. It is certainly more convoluted than the published 
grammar for Dlang, but we all know that that doesn't quite match the 
language. Some criticise XML for being too complicated, but in some 
respects it is even more complicated than that, it seems ironic that it 
was supposed to be simple.

But, I might have typed too soon when I wrote that this lexer might not 
be able to fully tokenize XML in a single pass. As I was drifting off to 
sleep last night I had the idea of using the extraFields of 
TokenStructure to add a token pointer (or equivalent construct) to the 
tokens, making a singly linked list and allowing me to return multiple 
tokens from a token handler. I just need to convince myself that it is a 
sane thing to do.

> If you have specific problems, please list them.

I think the calculator example should be improved or replaced so that 
the documentation's first example uses all the string[]s that can be 
passed to the lexer. Perhaps a simple symbolic calculator that accepts 
letter sequences as variables and reserves pi as a keyword?

I think that the section that describes the token handlers needs to 
fully document the primitives that the user has access to in order to 
lex a token and what state to expect them to be in when the handler is 
called and what state they should be in when it returns.

If tracking lines and columns is optional and supported only when 
wrapping the input with LexerRange then I think that by default Tokens 
should not contain fields for them. Perhaps an enum qstring that 
declares the required fields can be introduced and an example shown 
where it is concatenated with user declarations to be passed in via 
extraFields.

I'll again restate that I think LexerRange needs a name that better 
reflects its functionality. From reading the code I see that it does do 
a little more that simply track line/column by providing more efficient 
implementations of some of the range primitives. I Think that the user 
should be able to take advantage of these savings without having to also 
commit to line/column tracking. I think there should also be the option 
of tracking only lines, as column information is not always as accurate 
or useful.

I think that is all my concerns for now, I'll leave my crazy ideas for 
"improvements" for another day.

> If you do find issues with the lexer or parser, please file them here:
> https://github.com/Hackerpilot/Dscanner/issues

Will do!

A...



More information about the Digitalmars-d mailing list