コード例 #1
0
        public void Parse_MissingRightParen()
        {
            var parser = new SExpressionParser();
            var node   = parser.Parse("(");

            node.Should().BeOfType <ExpressionNode>();
            node.Diagnostics.Count.Should().Be(1);
            node.Diagnostics[0].Should().Be("Missing close parenthesis");
        }
コード例 #2
0
        public void Parse_Test()
        {
            var parser = new SExpressionParser();
            var node   = parser.Parse("(first 14 \"test\")");

            node.Should().BeOfType <ExpressionNode>();
            var expr = node as ExpressionNode;

            (expr.Children[0] as AtomNode).Type.Should().Be(ValueType.Symbol);
            (expr.Children[0] as AtomNode).Value.Should().Be("first");

            (expr.Children[1] as AtomNode).Type.Should().Be(ValueType.Number);
            (expr.Children[1] as AtomNode).Value.Should().Be(14);

            (expr.Children[2] as AtomNode).Type.Should().Be(ValueType.QuotedString);
            (expr.Children[2] as AtomNode).Value.Should().Be("\"test\"");
        }
コード例 #3
0
        static void Main(string[] arguments)
        {
            #region Classic Space Object Collision Example

            /*
             * See https://en.wikipedia.org/wiki/Multiple_dispatch#Examples
             */
            var planet1    = new Planet();
            var planet2    = new Planet();
            var asteroid1  = new Asteroid();
            var asteroid2  = new Asteroid();
            var spaceship1 = new Spaceship();
            var spaceship2 = new Spaceship();
            var satellite1 = new TelecomSatellite();
            var satellite2 = new TelecomSatellite();

            System.Diagnostics.Debug.Assert(planet1.CollideWith(planet2) == "the planets destroy each other");
            System.Diagnostics.Debug.Assert(planet1.CollideWith(asteroid1) == "the planet obliterates the asteroid");
            System.Diagnostics.Debug.Assert(asteroid1.CollideWith(planet1) == "the planet obliterates the asteroid");
            System.Diagnostics.Debug.Assert(planet1.CollideWith(spaceship1) == "the planet obliterates the spaceship");
            System.Diagnostics.Debug.Assert(spaceship1.CollideWith(planet1) == "the planet obliterates the spaceship");
            System.Diagnostics.Debug.Assert(planet1.CollideWith(satellite1) == "the planet obliterates the satellite");
            System.Diagnostics.Debug.Assert(satellite1.CollideWith(planet1) == "the planet obliterates the satellite");

            System.Diagnostics.Debug.Assert(asteroid1.CollideWith(asteroid2) == "the asteroids destroy each other");
            System.Diagnostics.Debug.Assert(asteroid1.CollideWith(spaceship1) == "the asteroid obliterates the spaceship");
            System.Diagnostics.Debug.Assert(spaceship1.CollideWith(asteroid1) == "the asteroid obliterates the spaceship");
            System.Diagnostics.Debug.Assert(asteroid1.CollideWith(satellite1) == "the asteroid obliterates the satellite");
            System.Diagnostics.Debug.Assert(satellite1.CollideWith(asteroid1) == "the asteroid obliterates the satellite");

            System.Diagnostics.Debug.Assert(spaceship1.CollideWith(spaceship2) == "the spaceships destroy each other");
            System.Diagnostics.Debug.Assert(spaceship1.CollideWith(satellite1) == "the spaceship obliterates the satellite");
            System.Diagnostics.Debug.Assert(satellite1.CollideWith(spaceship1) == "the spaceship obliterates the satellite");

            System.Diagnostics.Debug.Assert(satellite1.CollideWith(satellite2) == "the satellites destroy each other");
            #endregion

            var testLexicon =
                new Lexicon
                (
                    new[]
            {
                new TokenType("identifier", "[A-Za-z_][A-Za-z_0-9]*", token => token.Match != "null" ? token.Match : null),
                new TokenType("text", "\"[^\"]*\"", token => token.Match),
                new TokenType("number", "[0-9]+", token => int.Parse(token.Match))
            }
                );

            // Anonymous goodness...
            var AnonymousModel = new { Content = default(object) };

            var AnonymousReducer = Reducer(default(object), AnonymousModel);

            var toAnonymousModel =
                NewReducer
                (
                    AnonymousReducer,
                    (context, outer, expression, value) =>
                    expression ?? new { Content = value }
                );

            var aParser = SExpressionParser.Create(testLexicon, AnonymousReducer);
            var aModel  = aParser.Parse("  (  a  123 ( \"foo\" bar ( ) c ) ( null ) ) ", toAnonymousModel);

            System.Diagnostics.Debug.Assert(Is(AnonymousModel, aModel));
            System.Diagnostics.Debug.Assert(Is(ListOf(AnonymousModel), As(AnonymousModel, aModel).Content));
            System.Diagnostics.Debug.Assert(As(ListOf(AnonymousModel), As(AnonymousModel, aModel).Content)[0].Content is string);
            System.Diagnostics.Debug.Assert(As(ListOf(AnonymousModel), As(AnonymousModel, aModel).Content)[0].Content as string == "a");
            System.Diagnostics.Debug.Assert(As(ListOf(AnonymousModel), As(AnonymousModel, aModel).Content)[1].Content is int);
            System.Diagnostics.Debug.Assert((int)As(ListOf(AnonymousModel), As(AnonymousModel, aModel).Content)[1].Content == 123);

            Console.WriteLine();
            Console.Write("Press a key>");
            Console.ReadKey();

            // via extension method : SExpressionBuilder SExpressionBuilder(this object anchor)
            var S = default(object).SExpressionBuilder(); // loosely typed use cases (ie, object / object[] S-Exprs)

            // via extension method : SExpressionBuilder<TExpression> SExpressionBuilder<TExpression>(this TExpression anchor)
            var P = default(PicoNode).SExpressionBuilder(); // strongly typed use cases (ie, PicoNode S-Exprs)

            // equiv S-Expr: ( 123 abc ( x y null z ( null ) ) 456 ( ) )
            // fluently built
            var test1 = S(123, "abc", S("x", "y", null, "z", S(null)), 456, S());
            System.Diagnostics.Debug.Assert(test1 is object[]);
            System.Diagnostics.Debug.Assert((int)(test1 as object[])[0] == 123);                     // ( 123 <-
            System.Diagnostics.Debug.Assert((string)(test1 as object[])[1] == "abc");                // ( ... abc <-
            System.Diagnostics.Debug.Assert((string)((test1 as object[])[2] as object[])[1] == "y"); // ( ... ( ... y <-
            System.Diagnostics.Debug.Assert((int)(test1 as object[])[3] == 456);                     // ( ... ( ... ) 456 <-
            System.Diagnostics.Debug.Assert(((object[])(test1 as object[])[4]).Length == 0);         // ( ... ( ... ) ... ( ) <-

            // equiv S-Expr: ( ( html ) ( ( body ) ( ( p ) "Hello, world!" ( ( br ) ) "Ciao!" ) ) )
            // fluently built
            var test2 =
                P(
                    P("html"),
                    P(
                        P("body"),
                        P
                        (
                            P("p"),
                            "\"Hello, world!\"",
                            P(P("br")),
                            "\"Ciao!\""
                        )
                        )
                    );
            System.Diagnostics.Debug.Assert(test2 is PicoNode);
            System.Diagnostics.Debug.Assert(test2.IsAtom == false);
            System.Diagnostics.Debug.Assert(test2 is IReadOnlyList <PicoNode>);
            System.Diagnostics.Debug.Assert(test2[0].IsAtom == false);
            System.Diagnostics.Debug.Assert(test2[0][0].IsAtom == true);
            System.Diagnostics.Debug.Assert(test2[0][0].Value == "html");
            System.Diagnostics.Debug.Assert(test2[1][1][1].IsAtom == true);
            System.Diagnostics.Debug.Assert(test2[1][1][1].Value == "\"Hello, world!\"");
            System.Diagnostics.Debug.Assert
            (
                test2.ToString()
                ==
                "( ( System.String{html} ) ( ( System.String{body} ) ( ( System.String{p} ) System.String{\"Hello, world!\"} ( ( System.String{br} ) ) System.String{\"Ciao!\"} ) ) )"
            );
            Console.WriteLine();
            Console.WriteLine($"{nameof(test2)}: {test2}");

            // Now for some parsing...

            var parse1 =
                new SExpressionParser(testLexicon)
                .Parse(" ( 123 abc ( x y null z ( null ) ) 456 ( ) ) ");
            System.Diagnostics.Debug.Assert(parse1 is SExpression);
            System.Diagnostics.Debug.Assert((int)parse1[0].Value == 123);        // ( 123 <-
            System.Diagnostics.Debug.Assert((string)parse1[1].Value == "abc");   // ( ... abc <-
            System.Diagnostics.Debug.Assert((string)parse1[2][1].Value == "y");  // ( ... ( ... y <-
            System.Diagnostics.Debug.Assert((string)parse1[2][2].Value == null); // ( ... ( ... null <-
            System.Diagnostics.Debug.Assert((int)parse1[3].Value == 456);        // ( ... ( ... ) 456 <-
            System.Diagnostics.Debug.Assert(parse1[4].Count == 0);               // ( ... ( ... ) ... ( ) <-
            System.Diagnostics.Debug.Assert
            (
                parse1.ToString()
                ==
                "( System.Int32{123} System.String{abc} ( System.String{x} System.String{y} System.Object{null} System.String{z} ( System.Object{null} ) ) System.Int32{456} (  ) )"
            );
            Console.WriteLine();
            Console.WriteLine($"{nameof(parse1)}: {parse1}");

            var parse2 =
                new SExpressionParser <PicoNode>(testLexicon)
                .Parse(" ( ( html ) ( ( body ) ( ( p ) \"Hello, world!\" ( ( br ) ) \"Ciao!\" ) ) ) ");
            System.Diagnostics.Debug.Assert(parse2 is PicoNode); // Duh.
            System.Diagnostics.Debug.Assert(parse2.IsAtom == false);
            System.Diagnostics.Debug.Assert(parse2 is IReadOnlyList <PicoNode>);
            System.Diagnostics.Debug.Assert(parse2[0].IsAtom == false);
            System.Diagnostics.Debug.Assert(parse2[0][0].IsAtom == true);
            System.Diagnostics.Debug.Assert(parse2[0][0].Value == "html");
            System.Diagnostics.Debug.Assert(parse2[1][1][1].IsAtom == true);
            System.Diagnostics.Debug.Assert(parse2[1][1][1].Value == "\"Hello, world!\"");
            System.Diagnostics.Debug.Assert(parse2.ToString() == test2.ToString()); // Yup.
            Console.WriteLine();
            Console.WriteLine($"{nameof(parse2)}: {parse2}");

            // Parse errors reporting...

            string error1 = null;
            try
            {
                new SExpressionParser(testLexicon).Parse(@"  ( 123
                    ( 456 )
)
 garbage");
            }
            catch (Exception ex)
            {
                error1 = ex.Message;
            }
            System.Diagnostics.Debug.Assert(error1.StartsWith("syntax error: end of input expected but found identifier garbage at line 4, column 2"));

            string error2 = null;
            try
            {
                new SExpressionParser(testLexicon).Parse(@"  ( 123 abc
 456 ");
            }
            catch (Exception ex)
            {
                error2 = ex.Message;
            }
            System.Diagnostics.Debug.Assert(error2.StartsWith("syntax error: ) expected but found end of input at line 2, column 6"));

            string error3 = null;
            try
            {
                new SExpressionParser(testLexicon).Parse(@"  (
    abc (
          ?!? )
 ) ");
            }
            catch (Exception ex)
            {
                error3 = ex.Message;
            }
            System.Diagnostics.Debug.Assert(error3.StartsWith("syntax error: ) expected but found unexpected ?!? at line 3, column 11"));

            // And last but not least...

            // via extension method:
            // Reducer<TContext, TExpression> SExpressionReducer<TContext, TExpression>(this TExpression anchor, TContext prototype, Reducer<TContext, TExpression> reducer)
            var reducer =
                default(PicoNode) // expression type
                .SExpressionReducer
                (
                    default(object), // optional context (ignored)
                    (ignored, outer, expression, value) =>
                    (
                        expression != null ?
                        (
                            !expression.IsAtom &&
                            (expression.Count > 0) &&
                            !expression[0].IsAtom &&        // all this to detect the "(tagName)" in "... ( (tagName) ... ) ..."
                            expression[0].Count == 1 &&
                            expression[0][0].IsAtom ?
                            (
                                outer == null ?
                                new PicoDocument(new PicoElement(expression[0][0].Value, expression.Skip(1)))
                                    :
                                (PicoNode) new PicoElement(expression[0][0].Value, expression.Skip(1))
                            )
                                :
                            (
                                expression.IsAtom &&
                                expression.Value.StartsWith("\"") &&
                                expression.Value.EndsWith("\"") ?
                                new PicoText(expression.Value)
                                    :
                                expression
                            )
                        )
                            :
                        (
                            value is IEnumerable <PicoNode>?
                            new PicoNode((IEnumerable <PicoNode>)value)
                                :
                                new PicoNode((string)value)
                        )
                    )
                );

            /*
             * compare to:
             *  <html>
             *      <body>
             *          <p>
             *              Hello, world!
             *              <br/>
             *              Ciao!
             *          </p>
             *  </html>
             *  ...
             */
            var parse3 =
                new SExpressionParser <PicoNode>(testLexicon)
                .Parse(@"
            (
                ( html )
                (
                    ( body )
                    (
                        ( p ) ""Hello, world!""
                        ( ( br ) )
                        ""Ciao!""
                    )
                )
            ) ", reducer); // <- note the passing of the reducer
            System.Diagnostics.Debug.Assert(parse3 is PicoDocument);
            var documentElement = ((PicoDocument)parse3).DocumentElement;
            System.Diagnostics.Debug.Assert(documentElement.TagName == "html");
            System.Diagnostics.Debug.Assert(documentElement[0] is PicoElement);
            System.Diagnostics.Debug.Assert((documentElement[0] as PicoElement).TagName == "body");
            System.Diagnostics.Debug.Assert(documentElement[0][0] is PicoElement);
            System.Diagnostics.Debug.Assert((documentElement[0][0] as PicoElement).TagName == "p");
            System.Diagnostics.Debug.Assert(documentElement[0][0][0] is PicoText);
            System.Diagnostics.Debug.Assert((documentElement[0][0][0] as PicoText).Value == "\"Hello, world!\"");
            System.Diagnostics.Debug.Assert
            (
                parse3.ToString()
                ==
                "(<html><body><p>Hello, world!<br/>Ciao!</p></body></html>)"
            );
            Console.WriteLine();
            Console.WriteLine($"{nameof(parse3)}: {parse3}");

            //var input = System.IO.File.ReadAllText(TEST_SEXPR_FILE_PATH);
            //var parser = new SExpressionParser<PicoNode>();

            //Console.WriteLine();
            //Console.Write("Press a key>");
            //Console.ReadKey();

            //var sw = new System.Diagnostics.Stopwatch();
            //sw.Start();
            //var parse4 = parser.Parse(input, new FastLexer(), reducer);
            //sw.Stop();
            //Console.WriteLine();
            //Console.WriteLine($"{nameof(parse4)}: elapsed: {sw.ElapsedMilliseconds.ToString("0,0")} ms.");

            //Console.WriteLine();
            //Console.Write("Press a key>");
            //Console.ReadKey();

            //var body = (PicoElement)((PicoDocument)parse4).DocumentElement[0];
            //var firstParagraph = (PicoElement)body[0];
            //Console.WriteLine();
            //Console.WriteLine($"{nameof(parse4)}: '{body.TagName}' has {body.Count} child nodes '{firstParagraph.TagName}'");

            //Console.WriteLine();
            //Console.Write("Press a key>");
            //Console.ReadKey();

            //Console.WriteLine();
            //Console.WriteLine($"{nameof(parse4)}: {parse4}");

            Console.WriteLine();
            Console.Write("The end>");
            Console.ReadKey();
        }