diff options
Diffstat (limited to 'src/sdlang/parser.d')
-rw-r--r-- | src/sdlang/parser.d | 307 |
1 files changed, 192 insertions, 115 deletions
diff --git a/src/sdlang/parser.d b/src/sdlang/parser.d index ed8084a..c9b8d4f 100644 --- a/src/sdlang/parser.d +++ b/src/sdlang/parser.d @@ -6,6 +6,7 @@ module sdlang.parser; import std.file; import libInputVisitor; +import taggedalgebraic; import sdlang.ast; import sdlang.exception; @@ -21,8 +22,8 @@ Tag parseFile(string filename) return parseSource(source, filename); } -/// Returns root tag. The optional 'filename' parameter can be included -/// so that the SDL document's filename (if any) can be displayed with +/// Returns root tag. The optional `filename` parameter can be included +/// so that the SDLang document's filename (if any) can be displayed with /// any syntax error messages. Tag parseSource(string source, string filename=null) { @@ -36,12 +37,19 @@ Parses an SDL document using StAX/Pull-style. Returns an InputRange with element type ParserEvent. The pullParseFile version reads a file and parses it, while pullParseSource -parses a string passed in. The optional 'filename' parameter in pullParseSource -can be included so that the SDL document's filename (if any) can be displayed +parses a string passed in. The optional `filename` parameter in pullParseSource +can be included so that the SDLang document's filename (if any) can be displayed with any syntax error messages. -Warning! The FileStartEvent and FileEndEvent events *might* be removed later. -See $(LINK https://github.com/Abscissa/SDLang-D/issues/17) +Note: The old FileStartEvent and FileEndEvent events +$(LINK2 https://github.com/Abscissa/SDLang-D/issues/17, were deemed unnessecary) +and removed as of SDLang-D v0.10.0. + +Note: Previously, in SDLang-D v0.9.x, ParserEvent was a +$(LINK2 http://dlang.org/phobos/std_variant.html#.Algebraic, std.variant.Algebraic). +As of SDLang-D v0.10.0, it is now a +$(LINK2 https://github.com/s-ludwig/taggedalgebraic, TaggedAlgebraic), +so usage has changed somewhat. Example: ------------------ @@ -55,85 +63,147 @@ lastTag The ParserEvent sequence emitted for that SDL document would be as follows (indented for readability): ------------------ -FileStartEvent - TagStartEvent (parent) - ValueEvent (12) - AttributeEvent (attr, "q") - TagStartEvent (childA) - ValueEvent (34) - TagEndEvent - TagStartEvent (childB) - ValueEvent (56) - TagEndEvent +TagStartEvent (parent) + ValueEvent (12) + AttributeEvent (attr, "q") + TagStartEvent (childA) + ValueEvent (34) TagEndEvent - TagStartEvent (lastTag) + TagStartEvent (childB) + ValueEvent (56) TagEndEvent -FileEndEvent ------------------- - -Example: +TagEndEvent +TagStartEvent (lastTag) +TagEndEvent ------------------ -foreach(event; pullParseFile("stuff.sdl")) ++/ +auto pullParseFile(string filename) { - import std.stdio; + auto source = cast(string)read(filename); + return parseSource(source, filename); +} - if(event.peek!FileStartEvent()) - writeln("FileStartEvent, starting! "); +///ditto +auto pullParseSource(string source, string filename=null) +{ + auto lexer = new Lexer(source, filename); + auto parser = PullParser(lexer); + return inputVisitor!ParserEvent( parser ); +} - else if(event.peek!FileEndEvent()) - writeln("FileEndEvent, done! "); +/// +@("pullParseFile/pullParseSource example") +unittest +{ + // stuff.sdl + immutable stuffSdl = ` + name "sdlang-d" + description "An SDL (Simple Declarative Language) library for D." + homepage "http://github.com/Abscissa/SDLang-D" + + configuration "library" { + targetType "library" + } + `; + + import std.stdio; - else if(auto e = event.peek!TagStartEvent()) + foreach(event; pullParseSource(stuffSdl)) + final switch(event.kind) + { + case ParserEvent.Kind.tagStart: + auto e = cast(TagStartEvent) event; writeln("TagStartEvent: ", e.namespace, ":", e.name, " @ ", e.location); + break; - else if(event.peek!TagEndEvent()) + case ParserEvent.Kind.tagEnd: + auto e = cast(TagEndEvent) event; writeln("TagEndEvent"); + break; - else if(auto e = event.peek!ValueEvent()) + case ParserEvent.Kind.value: + auto e = cast(ValueEvent) event; writeln("ValueEvent: ", e.value); + break; - else if(auto e = event.peek!AttributeEvent()) + case ParserEvent.Kind.attribute: + auto e = cast(AttributeEvent) event; writeln("AttributeEvent: ", e.namespace, ":", e.name, "=", e.value); - - else // Shouldn't happen - throw new Exception("Received unknown parser event"); -} ------------------- -+/ -auto pullParseFile(string filename) -{ - auto source = cast(string)read(filename); - return parseSource(source, filename); + break; + } } -///ditto -auto pullParseSource(string source, string filename=null) +private union ParserEventUnion { - auto lexer = new Lexer(source, filename); - auto parser = PullParser(lexer); - return inputVisitor!ParserEvent( parser ); + TagStartEvent tagStart; + TagEndEvent tagEnd; + ValueEvent value; + AttributeEvent attribute; } -/// The element of the InputRange returned by pullParseFile and pullParseSource: -alias ParserEvent = std.variant.Algebraic!( - FileStartEvent, - FileEndEvent, - TagStartEvent, - TagEndEvent, - ValueEvent, - AttributeEvent, -); - -/// Event: Start of file -struct FileStartEvent +/++ +The element of the InputRange returned by pullParseFile and pullParseSource. + +This is a tagged union, built from the following: +------- +alias ParserEvent = TaggedAlgebraic!ParserEventUnion; +private union ParserEventUnion { - Location location; + TagStartEvent tagStart; + TagEndEvent tagEnd; + ValueEvent value; + AttributeEvent attribute; } +------- + +Note: The old FileStartEvent and FileEndEvent events +$(LINK2 https://github.com/Abscissa/SDLang-D/issues/17, were deemed unnessecary) +and removed as of SDLang-D v0.10.0. + +Note: Previously, in SDLang-D v0.9.x, ParserEvent was a +$(LINK2 http://dlang.org/phobos/std_variant.html#.Algebraic, std.variant.Algebraic). +As of SDLang-D v0.10.0, it is now a +$(LINK2 https://github.com/s-ludwig/taggedalgebraic, TaggedAlgebraic), +so usage has changed somewhat. ++/ +alias ParserEvent = TaggedAlgebraic!ParserEventUnion; -/// Event: End of file -struct FileEndEvent +/// +@("ParserEvent example") +unittest { - Location location; + // Create + ParserEvent event1 = TagStartEvent(); + ParserEvent event2 = TagEndEvent(); + ParserEvent event3 = ValueEvent(); + ParserEvent event4 = AttributeEvent(); + + // Check type + assert(event1.kind == ParserEvent.Kind.tagStart); + assert(event2.kind == ParserEvent.Kind.tagEnd); + assert(event3.kind == ParserEvent.Kind.value); + assert(event4.kind == ParserEvent.Kind.attribute); + + // Cast to base type + auto e1 = cast(TagStartEvent) event1; + auto e2 = cast(TagEndEvent) event2; + auto e3 = cast(ValueEvent) event3; + auto e4 = cast(AttributeEvent) event4; + //auto noGood = cast(AttributeEvent) event1; // AssertError: event1 is a TagStartEvent, not AttributeEvent. + + // Use as base type. + // In many cases, no casting is even needed. + event1.name = "foo"; + //auto noGood = event3.name; // AssertError: ValueEvent doesn't have a member 'name'. + + // Final switch is supported: + final switch(event1.kind) + { + case ParserEvent.Kind.tagStart: break; + case ParserEvent.Kind.tagEnd: break; + case ParserEvent.Kind.value: break; + case ParserEvent.Kind.attribute: break; + } } /// Event: Start of tag @@ -184,7 +254,7 @@ private struct PullParser private void error(Location loc, string msg) { - throw new SDLangParseException(loc, "Error: "~msg); + throw new ParseException(loc, "Error: "~msg); } private InputVisitor!(PullParser, ParserEvent) v; @@ -207,15 +277,27 @@ private struct PullParser //trace(__FUNCTION__, ": <Root> ::= <Tags> EOF (Lookaheads: Anything)"); auto startLocation = Location(lexer.filename, 0, 0, 0); - emit( FileStartEvent(startLocation) ); parseTags(); auto token = lexer.front; - if(!token.matches!"EOF"()) - error("Expected end-of-file, not " ~ token.symbol.name); - - emit( FileEndEvent(token.location) ); + if(token.matches!":"()) + { + lexer.popFront(); + token = lexer.front; + if(token.matches!"Ident"()) + { + error("Missing namespace. If you don't wish to use a namespace, then say '"~token.data~"', not ':"~token.data~"'"); + assert(0); + } + else + { + error("Missing namespace. If you don't wish to use a namespace, then omit the ':'"); + assert(0); + } + } + else if(!token.matches!"EOF"()) + error("Expected a tag or end-of-file, not " ~ token.symbol.name); } /// <Tags> ::= <Tag> <Tags> (Lookaheads: Ident Value) @@ -241,7 +323,8 @@ private struct PullParser } else if(token.matches!"{"()) { - error("Anonymous tags must have at least one value. They cannot just have children and attributes only."); + error("Found start of child block, but no tag name. If you intended an anonymous "~ + "tag, you must have at least one value before any attributes or child tags."); } else { @@ -274,7 +357,8 @@ private struct PullParser error("Expected tag name or value, not " ~ token.symbol.name); if(lexer.front.matches!"="()) - error("Anonymous tags must have at least one value. They cannot just have attributes and children only."); + error("Found attribute, but no tag name. If you intended an anonymous "~ + "tag, you must have at least one value before any attributes."); parseValues(); parseAttributes(); @@ -477,43 +561,33 @@ private struct DOMParser auto parser = PullParser(lexer); auto eventRange = inputVisitor!ParserEvent( parser ); + foreach(event; eventRange) + final switch(event.kind) { - if(auto e = event.peek!TagStartEvent()) - { - auto newTag = new Tag(currTag, e.namespace, e.name); - newTag.location = e.location; - - currTag = newTag; - } - else if(event.peek!TagEndEvent()) - { - currTag = currTag.parent; + case ParserEvent.Kind.tagStart: + auto newTag = new Tag(currTag, event.namespace, event.name); + newTag.location = event.location; + + currTag = newTag; + break; - if(!currTag) - parser.error("Internal Error: Received an extra TagEndEvent"); - } - else if(auto e = event.peek!ValueEvent()) - { - currTag.add(e.value); - } - else if(auto e = event.peek!AttributeEvent()) - { - auto attr = new Attribute(e.namespace, e.name, e.value, e.location); - currTag.add(attr); - } - else if(event.peek!FileStartEvent()) - { - // Do nothing - } - else if(event.peek!FileEndEvent()) - { - // There shouldn't be another parent. - if(currTag.parent) - parser.error("Internal Error: Unexpected end of file, not enough TagEndEvent"); - } - else - parser.error("Internal Error: Received unknown parser event"); + case ParserEvent.Kind.tagEnd: + currTag = currTag.parent; + + if(!currTag) + parser.error("Internal Error: Received an extra TagEndEvent"); + break; + + case ParserEvent.Kind.value: + currTag.add((cast(ValueEvent)event).value); + break; + + case ParserEvent.Kind.attribute: + auto e = cast(AttributeEvent) event; + auto attr = new Attribute(e.namespace, e.name, e.value, e.location); + currTag.add(attr); + break; } return currTag; @@ -522,30 +596,33 @@ private struct DOMParser // Other parser tests are part of the AST's tests over in the ast module. -// Regression test, issue #16: https://github.com/Abscissa/SDLang-D/issues/16 -version(sdlangUnittest) +// Regression test, issue #13: https://github.com/Abscissa/SDLang-D/issues/13 +// "Incorrectly accepts ":tagname" (blank namespace, tagname prefixed with colon)" +@("parser: Regression test issue #13") unittest { - import std.stdio; - writeln("parser: Regression test issue #16..."); - stdout.flush(); + import std.exception; + assertThrown!ParseException(parseSource(`:test`)); + assertThrown!ParseException(parseSource(`:4`)); +} +// Regression test, issue #16: https://github.com/Abscissa/SDLang-D/issues/16 +@("parser: Regression test issue #16") +unittest +{ // Shouldn't crash foreach(event; pullParseSource(`tag "data"`)) { - event.peek!FileStartEvent(); + if(event.kind == ParserEvent.Kind.tagStart) + auto e = cast(TagStartEvent) event; } } // Regression test, issue #31: https://github.com/Abscissa/SDLang-D/issues/31 // "Escape sequence results in range violation error" -version(sdlangUnittest) +@("parser: Regression test issue #31") unittest { - import std.stdio; - writeln("parser: Regression test issue #31..."); - stdout.flush(); - // Shouldn't get a Range violation parseSource(`test "\"foo\""`); } |