Esempio n. 1
0
        /// <summary>
        /// Performs a match that guards against infinite recursion
        /// </summary>
        /// <param name="enumerator">The enumerator to use</param>
        /// <param name="visited">The visited items</param>
        /// <returns>The match</returns>
        protected virtual Match DoProtectedMatch(IBufferedEnumerator <ParseToken> enumerator, List <Match> visited)
        {
            // Use this statment to debug where tokens are matched with the BNF

            if (DEBUG_BNF_OUTPUT)
            {
                Console.WriteLine("Trying: {1} with {0}", this.BuildString(new HashSet <BNFItem>(), 1), enumerator.Current);
            }

            // Quick return, if we do not need the recursion guards
            if (DISABLE_INFINITE_RECURSION_DETECTION)
            {
                return(DoUnprotectedMatch(enumerator, visited));
            }

            var start = enumerator.Current;

            // Prevent infinite recursion
            if (visited.Count(x => x.Token == this && object.Equals(x.Item, start)) > 3)
            {
                return(new Match(this, start, new BNF.Match[0], false));
            }

            visited.Add(new BNF.Match(this, start, null, false));
            var res = DoUnprotectedMatch(enumerator, visited);

            visited.RemoveAt(visited.Count - 1);

            return(res);
        }
Esempio n. 2
0
        /// <summary>
        /// Performs a match on a sequence
        /// </summary>
        /// <param name="enumerator">The sequence of items</param>
        /// <param name="visited">The visited items</param>
        /// <returns>A match or <c>null</c></returns>
        protected virtual Match DoUnprotectedMatch(IBufferedEnumerator <ParseToken> enumerator, List <Match> visited)
        {
            if (enumerator.Empty)
            {
                return(new Match(this, default(ParseToken), new Match[0], false));
            }

            var start = enumerator.Current;
            var text  = start.Text;


            if (this is Literal literalToken)
            {
                var res = new Match(this, start, null, text == literalToken.Value);
                if (res.Matched)
                {
                    enumerator.MoveNext();
                }
                return(res);
            }
            else if (this is RegEx regExToken)
            {
                var m   = regExToken.Expression.Match(text);
                var res = new Match(this, start, null, m.Success && m.Length == text.Length);

                if (res.Matched)
                {
                    enumerator.MoveNext();
                }
                return(res);
            }
            else if (this is CustomItem customToken)
            {
                var res = new Match(this, start, null, customToken.Matcher(text));

                if (res.Matched)
                {
                    enumerator.MoveNext();
                }
                return(res);
            }
            else if (this is Composite compositeToken)
            {
                enumerator.Snapshot();
                var subItems = new List <Match>();

                foreach (var n in compositeToken.Items)
                {
                    var s = n.DoProtectedMatch(enumerator, visited);
                    subItems.Add(s);

                    if (!s.Matched)
                    {
                        enumerator.Rollback();
                        return(new Match(this, start, subItems.ToArray(), false));
                    }
                }

                enumerator.Commit();
                return(new Match(this, start, subItems.ToArray(), true));
            }
            else if (this is Choice choiceToken)
            {
                var subItems = new List <Match>();
                foreach (var n in choiceToken.Choices)
                {
                    enumerator.Snapshot();
                    var s = n.DoProtectedMatch(enumerator, visited);

                    subItems.Add(s);
                    if (s.Matched)
                    {
                        enumerator.Commit();
                        return(new Match(this, start, new Match[] { s }, true));
                    }

                    enumerator.Rollback();
                }

                return(new Match(this, start, subItems.ToArray(), false));
            }
            else if (this is Optional optionalToken)
            {
                enumerator.Snapshot();
                var s = optionalToken.Item.DoProtectedMatch(enumerator, visited);
                if (!s.Matched)
                {
                    enumerator.Rollback();
                    return(new Match(this, start, new Match[] { s }, true));
                }

                enumerator.Commit();
                return(new Match(this, start, new Match[] { s }, true));
            }
            else if (this is Sequence sequenceToken)
            {
                var items = new List <Match>();
                while (true)
                {
                    enumerator.Snapshot();
                    var n = sequenceToken.Items.DoProtectedMatch(enumerator, visited);
                    if (!n.Matched)
                    {
                        enumerator.Rollback();
                        items.Add(n);
                        break;
                    }
                    enumerator.Commit();
                    items.Add(n);
                }

                return(new Match(this, start, items.ToArray(), true));
            }
            else if (this.MapperType != null)
            {
                var prop  = this.GetType().GetField(nameof(Mapper <int> .Token));
                var token = prop.GetValue(this);
                var match = ((BNFItem)token).DoProtectedMatch(enumerator, visited);
                return(new Match(this, start, new BNF.Match[] { match }, match.Matched));
            }

            throw new Exception($"Unable to match for {this.GetType()}, make sure you override the {nameof(Match)} method");
        }
Esempio n. 3
0
 /// <summary>
 /// Performs a match on a sequence
 /// </summary>
 /// <param name="enumerator">The sequence of items</param>
 /// <returns>A match or <c>null</c></returns>
 public virtual Match Match(IBufferedEnumerator <ParseToken> enumerator)
 {
     return(DoProtectedMatch(enumerator, new List <Match>()));
 }
Esempio n. 4
0
        /// <summary>
        /// Parses the token stream and returns an abstract syntax tree
        /// </summary>
        /// <param name="tokens">The tokens to parse</param>
        /// <returns>The parsed syntax tree</returns>
        public static AST.Module Parse(IBufferedEnumerator <ParseToken> tokens)
        {
            // Basic identifier definition
            var ident = Mapper(RegEx(@"\w[\w\d\-_]*"), x => new AST.Identifier(x.Item));

            var integer = Mapper(
                RegEx(@"[0-9]+|(0x[0-9|a-f|A-F]+)|(0o[0-7]+)"),
                x => new AST.IntegerConstant(x.Item, x.Item.Text)
                );

            // The int32 limitation is not in the BNF, but we use it here
            // to limit the size of generated arrays and types
            // to something that can be expressed in hardware
            var int32literal = Mapper(
                CustomItem(x => int.TryParse(x, out var _)),
                x => int.Parse(x.Item.Text)
                );

            var int64literal = Mapper(
                CustomItem(x => long.TryParse(x, out var _)),
                x => long.Parse(x.Item.Text)
                );

            var floating = Mapper(
                Composite(
                    Optional(integer),
                    ".",
                    integer
                    ),

                x => new AST.FloatingConstant(
                    x.Item,

                    x.FindSubMatch(0, 0) == null || !x.FindSubMatch(0, 0).Matched
                        ? new AST.IntegerConstant(x.Item, "0")
                        : x.SubMatches[0].FirstMapper(integer),

                    x.SubMatches[2].FirstMapper(integer)
                    )
                );

            var stringliteral = Mapper(
                Composite(
                    "\"",
                    Sequence(RegEx(@"[^\x00-\x19""]*")),
                    "\""
                    ),

                x => new AST.StringConstant(x.Item, string.Join(" ", x.FindSubMatch(0, 1).Flat.Where(n => n.Token is BNF.RegEx).Select(n => n.Item.Text)))
                );

            var booleanliteral = Mapper(
                Choice(
                    "true",
                    "false"
                    ),

                x => new AST.BooleanConstant(x.Item, x.Item.Text == "true")
                );

            var arrayIndexLiteral = Mapper(
                Composite("[", integer, "]"),
                x => new AST.ArrayIndexLiteral(x.Item, x.FirstMapper(integer))
                );

            var specialLiteral = Mapper(Literal("U"), x => new AST.SpecialLiteral(x.Item));

            var simpletypename = Mapper(
                Choice(
                    CustomItem(x => AST.DataType.IsValidIntrinsicType(x))
                    ),

                x => AST.DataType.Parse(x.Item)
                );

            var literal = Mapper <AST.Constant>(
                Choice(
                    integer,
                    floating,
                    stringliteral,
                    arrayIndexLiteral,
                    booleanliteral,
                    specialLiteral
                    ),

                x => x.FirstDerivedMapper <AST.Constant>()
                );

            var unaryOperation = Mapper(
                Choice(
                    "-",
                    "+",
                    "!",
                    "~"
                    ),

                x => new AST.UnaryOperation(
                    x.Item,
                    AST.UnaryOperation.Parse(
                        x.Flat
                        .First(n => n.Token is BNF.Literal)
                        .Item
                        )
                    )
                );

            // Create the upper mapper to allow referencing it
            // in the recursive definition below
            var expression = Mapper(
                null,
                x => x.FirstDerivedMapper <AST.Expression>()
                );

            var arrayIndex = Mapper(
                Composite(
                    "[",
                    expression,
                    "]"
                    ),

                x => {
                return(new AST.ArrayIndex(x.Item, x.FirstMapper(expression)));
            }
                );

            var nameitem = Mapper(
                Composite(
                    ident,
                    Optional(
                        arrayIndex
                        )
                    ),

                x => {
                return(new
                {
                    Name = x.FirstMapper(ident),
                    Index = x.FirstOrDefaultMapper(arrayIndex)
                });
            }
                );

            var dotprefixedname = Mapper(
                Composite(
                    ".",
                    nameitem
                    ),

                x => x.FindSubMatch(0).InvokeFirstLevelMappers(nameitem).First()
                );

            var name = Mapper(
                Composite(
                    nameitem,
                    Sequence(dotprefixedname)
                    ),

                x => {
                var entries = new [] {
                    x.FindSubMatch(0).InvokeFirstLevelMappers(nameitem).First()
                }.Concat(x.FindSubMatch(0, 1).InvokeFirstLevelMappers(dotprefixedname));


                return(new AST.Name(
                           x.Item,
                           entries.Select(y => y.Name).ToArray(),
                           entries.Select(y => y.Index).ToArray()
                           ));
            }
                );

            var typename = Mapper(
                Composite(
                    Choice(
                        simpletypename,
                        name
                        ),
                    Optional(
                        Composite("[", expression, "]")
                        )
                    ),

                x => {
                var subitem = x.FindSubMatch(0, 0, 0);

                return(subitem?.Token == simpletypename
                        ? new AST.TypeName(new AST.DataType(x.Item, x.FirstMapper(simpletypename)), x.FirstOrDefaultMapper(expression))
                        : new AST.TypeName(subitem.FirstMapper(name), x.FirstOrDefaultMapper(expression)));
            }
                );

            // Mapping expressions to parameters
            var parammap = Mapper(
                Composite(
                    Optional(
                        Composite(
                            ident,
                            ":"
                            )
                        ),
                    expression
                    ),

                x => new AST.ParameterMap(
                    x.Item,
                    x.FindSubMatch(0, 0, 0) == null || !x.FindSubMatch(0, 0, 0).Matched
                        ? null
                        : x.FirstMapper(ident),
                    x.FirstMapper(expression)
                    )
                );


            // Simple wrapping expressions
            var nameExpression        = Mapper(name, x => new AST.NameExpression(x.Item, x.FirstMapper(name)));
            var literalExpression     = Mapper(literal, x => new AST.LiteralExpression(x.Item, x.FirstMapper(literal)));
            var parenthesisExpression = Mapper(Composite("(", expression, ")"), x => new AST.ParenthesizedExpression(x.Item, x.FirstMapper(expression)));

            // We allow typecasts and unary operations only on
            // terminating expressions
            var highBindExpr = Mapper(
                null,
                x => x.FirstDerivedMapper <AST.Expression>()
                );

            var unaryExpresssion = Mapper(
                Composite(unaryOperation, highBindExpr),

                x => new AST.UnaryExpression(
                    x.Item,
                    x.FirstMapper(unaryOperation),
                    x.FirstMapper(highBindExpr)
                    )
                );

            var typeCastExpression = Mapper(
                Composite(
                    "(",
                    typename,
                    ")",
                    highBindExpr
                    ),

                x => new AST.TypeCast(
                    x.Item,
                    x.FirstMapper(highBindExpr),
                    x.FirstMapper(typename),
                    true
                    )
                );

            // Terminating expressions have highest precedence
            highBindExpr.Token =
                Choice(
                    literalExpression,
                    nameExpression,
                    unaryExpresssion,
                    typeCastExpression,
                    parenthesisExpression
                    );

            // Create expressions to capture the operator precedence
            var binopPrecedence = new string[][] {
                new string[] { "*", "/", "%" },        // lvl1
                new string[] { "-", "+" },             // lvl2
                new string[] { "<<", ">>" },           // lvl3
                new string[] { "<", ">", "<=", ">=" }, // lvl4
                new string[] { "==", "!=" },           // lvl5
                new string[] { "&", "^", "|" },        // lvl6
                new string[] { "&&" },                 // lvl7
                new string[] { "||" },                 // lvl8
            };

            // The BNF simply has "expression ::= expression op expression",
            // but this is a left-recursive non-terminal and does not encode
            // operator precedence.
            // To solve this, we re-factor each precedence level
            // to avoid the left-recursive non-terminal,
            // and chain the precedence levels

            // Group the terminal expressions for easy reference
            var terminatingExpression = highBindExpr;

            // The prev variable is the "next" level, starting with a terminal
            var prev     = terminatingExpression;
            var binOpSeq = binopPrecedence.Select(
                (x, i) => {
                // Copy into this scope
                var prev_locked = prev;

                // Match any character from the operation list
                var opMap = Mapper(
                    Choice(x.Select(y => new BNF.Literal(y)).ToArray()),
                    y => new AST.BinaryOperation(y.Item)
                    );

                // Create a term where the starting token is a terminal,
                // namely the operation literal
                var tmp     = Mapper <BinOpMatch>(null, null);
                tmp.Matcher =
                    y => {
                    var op  = y.FirstOrDefaultMapper(opMap);
                    var exp = y.FirstOrDefaultMapper(prev_locked);

                    // If we matched the rhs non-terminal,
                    // check for the optional trailing component (of same precedence)
                    if (exp != null)
                    {
                        var trailing = y.FindSubMatch(0, 0).InvokeFirstLevelMappers(tmp).FirstOrDefault();
                        if (trailing.Expression != null)
                        {
                            exp = new AST.BinaryExpression(exp.SourceToken, exp, trailing.Operation, trailing.Expression);
                        }
                    }

                    return(new BinOpMatch()
                    {
                        Operation = op,
                        Expression = exp,
                    });
                };

                // Recursive definition, but starting with a terminal
                tmp.Token = Optional(
                    Composite(
                        opMap,
                        prev_locked,
                        tmp
                        )
                    );

                // The start item in each level matches the non-terminal
                // a single time and then matches the recusive part
                var topItem = Mapper <AST.Expression>(
                    Composite(
                        prev_locked,
                        tmp
                        ),
                    n =>
                {
                    // If this is a single terminal expression, just return it
                    var pm    = n.FirstMapper(prev_locked);
                    var subOp = n.FindSubMatch(0).InvokeFirstLevelMappers(tmp).FirstOrDefault();
                    if (subOp.Operation == null)
                    {
                        return(pm);
                    }

                    // Otherwise re-wire into a composite expression
                    return(new AST.BinaryExpression(
                               n.Item,
                               pm,
                               subOp.Operation,
                               subOp.Expression
                               ));
                }
                    );

                return(prev = topItem);
            }
                ).ToArray();

            // The final token will recurse into the others
            // and trail out in the terminals
            expression.Token = prev;

            var statement = Mapper(
                null,
                x => x.FirstDerivedMapper <AST.Statement>()
                );

            var assignmentStatement = Mapper(
                Composite(
                    name,
                    "=",
                    expression,
                    ";"
                    ),

                x => new AST.AssignmentStatement(
                    x.Item,
                    x.FirstMapper(name),
                    x.FindSubMatch(0, 2).FirstMapper(expression)
                    )
                );

            var elifBlock = Mapper(
                Composite(
                    "elif",
                    expression,
                    "{",
                    Sequence(statement),
                    "}"
                    ),

                x => new Tuple <AST.Expression, AST.Statement[]>(
                    x.FirstMapper(expression),
                    x.FindSubMatch(0, 3).InvokeFirstLevelMappers(statement).ToArray()
                    )
                );

            var elseBlock = Mapper(
                Composite(
                    "else",
                    "{",
                    Sequence(statement),
                    "}"
                    ),

                x => x.FindSubMatch(0, 2).InvokeFirstLevelMappers(statement).ToArray()
                );

            var ifStatement = Mapper(
                Composite(
                    "if",
                    expression,
                    "{",
                    Sequence(statement),
                    "}",
                    Sequence(elifBlock),
                    Optional(
                        elseBlock
                        )
                    ),

                x => new AST.IfStatement(
                    x.Item,
                    x.FirstMapper(expression),
                    x.FindSubMatch(0, 3).InvokeFirstLevelMappers(statement).ToArray(),
                    x.FindSubMatch(0, 5).InvokeFirstLevelMappers(elifBlock).ToArray(),
                    x.FindSubMatch(0, 6).InvokeFirstLevelMappers(elseBlock).FirstOrDefault()
                    )
                );

            var forStatement = Mapper(
                Composite(
                    "for",
                    ident,
                    "=",
                    expression,
                    "to",
                    expression,
                    "{",
                    Sequence(statement),
                    "}"
                    ),

                x => new AST.ForStatement(
                    x.Item,
                    x.FirstMapper(ident),
                    x.FindSubMatch(0, 3).FirstMapper(expression),
                    x.FindSubMatch(0, 5).FirstMapper(expression),
                    x.FindSubMatch(0, 7).InvokeFirstLevelMappers(statement).ToArray()
                    )
                );

            // A simple expression is either a literal or a name
            var simpleExpression = Mapper(Choice(literalExpression, nameExpression), x => x.InvokeDerivedMappers <AST.Expression>().First());

            var switchCase = Mapper(
                Composite(
                    "case",
                    simpleExpression,
                    "{",
                    Sequence(statement),
                    "}"
                    ),

                x => new Tuple <AST.Expression, AST.Statement[]>(
                    x.FirstMapper(simpleExpression),
                    x.FindSubMatch(0, 3).InvokeFirstLevelMappers(statement).ToArray()
                    )
                );

            var switchStatement = Mapper(
                Composite(
                    "switch",
                    simpleExpression,
                    "{",
                    // The BNF specifies at least one, but we allow
                    // zero here, and throw a more descriptive error
                    Sequence(switchCase),
                    Optional(
                        Composite(
                            "default",
                            "{",
                            Sequence(statement),
                            "}"
                            )
                        ),
                    "}"
                    ),

                x =>
            {
                var defaultCase = x.FindSubMatch(0, 4, 0, 2).InvokeFirstLevelMappers(statement).ToArray();
                // we cannot use the normal recursive mapper as we may have embedded switch statements
                var cases = x.FindSubMatch(0, 3).InvokeFirstLevelMappers(switchCase);

                if (defaultCase.Length > 0)
                {
                    cases = cases.Append(new Tuple <AST.Expression, AST.Statement[]>(null, defaultCase));
                }

                return(new AST.SwitchStatement(
                           x.Item,
                           x.FirstMapper(simpleExpression),
                           cases.ToArray()
                           ));
            }
                );

            var functionStatement = Mapper(
                Composite(
                    name,
                    "(",
                    Optional(
                        Composite(
                            parammap,
                            Sequence(
                                Composite(
                                    ",",
                                    parammap
                                    )
                                )
                            )
                        ),
                    ")",
                    ";"
                    ),

                x => new AST.FunctionStatement(
                    x.Item,
                    x.FirstMapper(name),
                    x.InvokeMappers(parammap).ToArray()
                    )
                );

            var traceStatement = Mapper(
                Composite(
                    "trace",
                    "(",
                    stringliteral,
                    Sequence(
                        Composite(
                            ",",
                            expression
                            )
                        ),
                    ")",
                    ";"
                    ),

                x => new AST.TraceStatement(
                    x.Item,
                    x.FirstMapper(stringliteral).Value,
                    x.InvokeFirstLevelMappers(expression).ToArray()
                    )
                );

            var assertStatement = Mapper(
                Composite(
                    "assert",
                    "(",
                    expression,
                    Optional(stringliteral),
                    ")",
                    ";"
                    ),

                x => new AST.AssertStatement(
                    x.Item,
                    x.FirstMapper(expression),
                    x.FirstOrDefaultMapper(stringliteral)?.Value
                    )
                );

            var breakStatement = Mapper(
                Composite("break", ";"),
                x => new AST.BreakStatement(x.Item)
                );

            statement.Token = Choice(
                assignmentStatement,
                ifStatement,
                forStatement,
                switchStatement,
                traceStatement,
                assertStatement,
                breakStatement,
                functionStatement
                );

            var varDecl = Mapper(
                Composite(
                    "var",
                    ident,
                    ":",
                    typename,
                    Optional(
                        Composite(
                            "=",
                            expression
                            )
                        ),
                    ";"
                    ),

                x => new AST.VariableDeclaration(
                    x.Item,
                    x.FirstMapper(ident),
                    x.FirstMapper(typename),
                    x.FirstOrDefaultMapper(expression)
                    )
                );

            var arrayindex = Mapper(
                Choice(
                    "*",
                    expression
                    ),

                x => x.SubMatches[0].Token is BNF.Literal
                    ? new AST.ArrayIndex(x.Item)
                    : new AST.ArrayIndex(x.Item, x.FirstMapper(expression))
                );

            var direction = Mapper(
                Choice(
                    "in",
                    "out",
                    "const"
                    ),
                x => (AST.ParameterDirection)Enum.Parse(typeof(AST.ParameterDirection), x.Item.Text, true)
                );

            // Note: we restrict index to 32bit, but specs say unlimited
            var parameter = Mapper(
                Composite(
                    direction,
                    ident,
                    Optional(
                        Composite(
                            ":",
                            typename
                            )
                        )
                    ),

                x => new AST.Parameter(
                    x.Item,
                    x.FirstMapper(direction),
                    x.FirstMapper(ident),
                    x.FirstOrDefaultMapper(typename)
                    )
                );

            var parameters = Mapper(
                Composite(parameter, Sequence(Composite(",", parameter))),
                x => x.InvokeMappers(parameter).ToArray()
                );

            var enumFieldDeclaration = Mapper(
                Composite(
                    ident,
                    Optional(
                        Composite(
                            "=",
                            int32literal
                            )
                        )
                    ),

                x => new AST.EnumField(
                    x.Item,
                    x.FirstMapper(ident),
                    x.FindSubMatch(0, 1, 0, 1) == null
                        ? -1
                        : x.FirstOrDefaultMapper(int32literal)
                    )
                );

            var enumDecl = Mapper(
                Composite(
                    "enum",
                    ident,
                    "{",
                    // The BNF specifies at least one, but we allow
                    // zero in the parsing to give better error messages
                    Optional(
                        Composite(
                            enumFieldDeclaration,
                            Sequence(Composite(",", enumFieldDeclaration))
                            )
                        ),
                    "}",
                    ";"
                    ),

                x => new AST.EnumDeclaration(
                    x.Item,
                    x.FirstMapper(ident),
                    x.InvokeMappers(enumFieldDeclaration).ToArray()
                    )
                );

            var constDecl = Mapper(
                Composite(
                    "const",
                    ident,
                    ":",
                    typename,
                    "=",
                    expression,
                    ";"
                    ),

                x => new AST.ConstantDeclaration(
                    x.Item,
                    x.FirstMapper(ident),
                    x.FirstMapper(typename),
                    x.FirstMapper(expression)
                    )
                );

            var funcDecls = Mapper(
                Choice(
                    varDecl,
                    constDecl,
                    enumDecl
                    ),

                x => x.InvokeDerivedMappers <AST.Declaration>().First()
                );

            var funcDef = Mapper(
                Composite(
                    "function",
                    ident,
                    "(",
                    parameters,
                    ")",
                    Sequence(funcDecls),
                    "{",
                    Sequence(statement),
                    "}"
                    ),

                x => new AST.FunctionDefinition(
                    x.Item,
                    x.FirstMapper(ident),
                    x.FirstMapper(parameters),
                    x.InvokeMappers(funcDecls).ToArray(),
                    x.InvokeMappers(statement).ToArray()
                    )
                );

            var busSignalDirection = Mapper(
                Choice(
                    "normal",
                    "inverse"
                    ),

                x => string.Equals("inverse", x.Item.Text, StringComparison.OrdinalIgnoreCase)
                    ? AST.SignalDirection.Inverse
                    : AST.SignalDirection.Normal
                );

            var busSignalDeclaration = Mapper(
                Composite(
                    ident,
                    ":",
                    typename,
                    Optional(
                        Composite(
                            "=",
                            expression
                            )
                        ),
                    Optional(
                        Composite(
                            ",",
                            busSignalDirection
                            )
                        ),
                    ";"
                    ),

                x => new AST.BusSignalDeclaration(
                    x.Item,
                    x.FirstMapper(ident),
                    x.FirstMapper(typename),
                    x.FindSubMatch(0, 3).FirstOrDefaultMapper(expression),
                    x.FindSubMatch(0, 4).FirstOrDefaultMapper(busSignalDirection)
                    )
                );

            var busDecl = Mapper(
                Composite(
                    "bus",
                    ident,
                    ":",
                    Choice(
                        Composite(
                            "{",
                            // The BNF specifies at least one signal, but
                            // we allow zero, and throw a descriptive error
                            Sequence(busSignalDeclaration),
                            "}"
                            ),
                        typename
                        ),
                    ";"
                    ),

                x => {
                var usingTypeName = x.FindSubMatch(0, 3, 0).Token == typename;

                return(new AST.BusDeclaration(
                           x.Item,
                           x.FirstMapper(ident),
                           usingTypeName ? null : x.FindSubMatch(0, 3).InvokeMappers(busSignalDeclaration).ToArray(),
                           usingTypeName ? x.FindSubMatch(0, 3).FirstOrDefaultMapper(typename) : null
                           ));
            }
                );

            var qualifiedspec = Composite("as", ident);

            var importname = Mapper(
                Composite(
                    ident,
                    Sequence(
                        Composite(
                            ".",
                            ident
                            )
                        )
                    ),

                x => new AST.ImportName(
                    x.Item,
                    x.InvokeMappers <AST.Identifier>().ToArray()
                    )
                );

            var multipleidents = Composite(ident, Sequence(Composite(",", ident)));

            var fullimport    = Composite("import", importname, qualifiedspec, ";");
            var limitedimport = Composite(
                "from", importname,
                "import", multipleidents,
                qualifiedspec, ";");

            var importstatement = Mapper(
                Choice(fullimport, limitedimport),
                x =>
            {
                return(new AST.ImportStatement(
                           x.Item,
                           // The import module name
                           x.FirstMapper(importname),

                           // If we have multiple idents, extract them
                           x.FirstOrDefault(multipleidents)
                           ?.InvokeMappers(ident)
                           .ToArray(),

                           // Get then name we map to
                           x.First(qualifiedspec)
                           .FirstMapper(ident)
                           ));
            }
                );

            var typedefs = Mapper(
                Composite(
                    "type",
                    ident,
                    ":",
                    Choice(
                        typename,
                        Composite(
                            "{",
                            Sequence(busSignalDeclaration),
                            "}"
                            )
                        ),
                    ";"
                    ),

                x =>
                x.FindSubMatch(0, 3, 0)?.Token == typename
                        ? new AST.TypeDefinition(x.Item, x.FirstMapper(ident), x.FirstMapper(typename))
                        : new AST.TypeDefinition(x.Item, x.FirstMapper(ident), x.InvokeMappers(busSignalDeclaration))
                );

            var instanceName = Mapper(
                Choice(
                    Composite(ident, "[", expression, "]"),
                    ident,
                    "_"
                    ),
                x => x.SubMatches.First().Token is BNF.Literal
                    ? null // Anonymous is null
                    : new AST.InstanceName(
                    x.Item,
                    x.FirstMapper(ident),
                    x.FirstOrDefaultMapper(expression)
                    )
                );

            var instDecl = Mapper(
                Composite(
                    "instance", instanceName,
                    "of", ident,
                    "(",
                    Optional(
                        Composite(
                            parammap,
                            Sequence(
                                Composite(",", parammap)
                                )
                            )
                        ),
                    ")",
                    ";"
                    ),
                x => new AST.InstanceDeclaration(
                    x.Item,
                    x.FirstMapper(instanceName),
                    x.InvokeMappers(ident).Skip(1).First(),
                    x.InvokeMappers(parammap).ToArray()
                    )
                );

            var connectEntry = Mapper(
                Composite(
                    name,
                    "->",
                    name
                    ),

                x => new AST.ConnectEntry(x.Item, x.FirstMapper(name), x.LastMapper(name))
                );

            var connectDecl = Mapper(
                Composite(
                    "connect",
                    connectEntry,
                    Sequence(
                        Composite(
                            ",",
                            connectEntry
                            )
                        ),
                    ";"
                    ),

                x => new AST.ConnectDeclaration(x.Item, x.InvokeMappers(connectEntry).ToArray())
                );

            var networkDecl = Mapper(
                null,
                x => x.FirstDerivedMapper <AST.NetworkDeclaration>()
                );

            var genDecl = Mapper(
                Composite(
                    "generate",
                    ident,
                    "=",
                    expression,
                    "to",
                    expression,
                    "{",
                    Sequence(networkDecl),
                    "}"
                    ),

                x => new AST.GeneratorDeclaration(
                    x.Item,
                    x.FirstMapper(ident),
                    x.FindSubMatch(0, 3).FirstMapper(expression),
                    x.FindSubMatch(0, 5).FirstMapper(expression),
                    x.FindSubMatch(0, 7).InvokeMappers(networkDecl).ToArray()
                    )
                );

            var declaration = Mapper(
                Choice(
                    varDecl,
                    constDecl,
                    busDecl,
                    enumDecl,
                    funcDef,
                    instDecl,
                    genDecl
                    ),

                x => x.FirstDerivedMapper <AST.Declaration>()
                );

            networkDecl.Token = Choice(
                instDecl,
                busDecl,
                constDecl,
                genDecl,
                connectDecl
                );

            var process = Mapper(
                Composite(
                    Optional(
                        "clocked"
                        ),
                    "proc",
                    ident,
                    "(",
                    Optional(parameters),
                    ")",
                    Sequence(declaration),
                    "{",
                    Sequence(statement),
                    "}"
                    ),

                x => new AST.Process(
                    x.Item,
                    x.FindSubMatch(0, 0).Item.Text == "clocked",
                    x.FirstMapper(ident),
                    x.FirstOrDefaultMapper(parameters),
                    x.FindSubMatch(0, 6).InvokeMappers(declaration).ToArray(),
                    x.FindSubMatch(0, 8).InvokeFirstLevelMappers(statement).ToArray()
                    )
                );


            var network = Mapper(
                Composite(
                    "network",
                    ident,
                    "(",
                    Optional(parameters),
                    ")",
                    "{",
                    Sequence(networkDecl),
                    "}"
                    ),

                x => new AST.Network(
                    x.Item,
                    x.FirstMapper(ident),
                    x.FirstOrDefaultMapper(parameters),
                    x.InvokeMappers(networkDecl).ToArray()
                    )
                );

            var entity = Mapper(
                Choice(
                    network,
                    process
                    ),

                x => x.FirstDerivedMapper <AST.Entity>()
                );

            var moduleDecl = Mapper(
                Choice(
                    typedefs,
                    constDecl,
                    enumDecl,
                    funcDef
                    ),

                x => x.FirstDerivedMapper <AST.Declaration>()
                );

            var module = Mapper(
                Composite(
                    Sequence(
                        importstatement
                        ),
                    Sequence(
                        moduleDecl
                        ),
                    Sequence(entity)
                    ),

                x => new AST.Module(
                    x.Item,
                    x.InvokeMappers(importstatement).ToArray(),
                    x.InvokeMappers(moduleDecl).ToArray(),
                    x.InvokeMappers(entity).ToArray()
                    )
                );

            //var match = module.Match(tokens);
            var match = module.Match(tokens);

            // If we have trailing unparsed text, make a guess as to why it fails to parse
            if (!tokens.Empty)
            {
                var start = match;
                while (start != null && start.Matched)
                {
                    start = start.SubMatches.LastOrDefault();
                }

                if (start != null && !start.Matched)
                {
                    ThrowParserError(start);
                }

                // General error message
                throw new ParserException($"Unable to parse item \"{tokens.Current}\"", tokens.Current);
            }

            if (!match.Matched)
            {
                ThrowParserError(match);
            }

            return(match.FirstMapper(module));
        }