aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/sdlang/parser.d
diff options
context:
space:
mode:
Diffstat (limited to 'src/sdlang/parser.d')
-rw-r--r--src/sdlang/parser.d307
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\""`);
}