diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000000..2ce24899f27 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.dot diff=-astextplain diff --git a/.vscode/launch.json b/.vscode/launch.json index aa506d012ad..833fb54b4da 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -38,7 +38,7 @@ "request": "launch", "mainClass": "org.rascalmpl.shell.RascalShell", "projectName": "rascal", - "cwd" : "${workspaceFolder}/../rascal-tutor", + "cwd": "${workspaceFolder}/../rascal-tutor", "vmArgs": "-Xss80m -Xmx2g -ea" }, { diff --git a/pom.xml b/pom.xml index e8c2c5cd371..fd70088efc0 100644 --- a/pom.xml +++ b/pom.xml @@ -168,6 +168,7 @@ package + diff --git a/src/org/rascalmpl/interpreter/Evaluator.java b/src/org/rascalmpl/interpreter/Evaluator.java index 5183ce362df..1c412acdbcb 100755 --- a/src/org/rascalmpl/interpreter/Evaluator.java +++ b/src/org/rascalmpl/interpreter/Evaluator.java @@ -989,7 +989,7 @@ private IFunction parserForCurrentModule(RascalFunctionValueFactory vf, ModuleEn IMap syntaxDefinition = curMod.getSyntaxDefinition(); IMap grammar = (IMap) getParserGenerator().getGrammarFromModules(getMonitor(), curMod.getName(), syntaxDefinition).get("rules"); IConstructor reifiedType = vf.reifiedType(dummy, grammar); - return vf.parsers(reifiedType, vf.bool(false), vf.bool(false), vf.bool(false), vf.set()); + return vf.parsers(reifiedType, vf.bool(false), vf.bool(false), vf.bool(false), vf.bool(false), vf.set()); } private Result evalMore(String command, ISourceLocation location) diff --git a/src/org/rascalmpl/library/ParseTree.rsc b/src/org/rascalmpl/library/ParseTree.rsc index 3289f88fd2c..cbacbb70c4e 100644 --- a/src/org/rascalmpl/library/ParseTree.rsc +++ b/src/org/rascalmpl/library/ParseTree.rsc @@ -13,14 +13,14 @@ @synopsis{Library functions for parse trees.} @description{ -A _concrete syntax tree_ or [parse tree](http://en.wikipedia.org/wiki/Parse_tree) is an ordered, rooted tree that -represents the syntactic structure of a string according to some formal grammar. +A _concrete syntax tree_ or [parse tree](http://en.wikipedia.org/wiki/Parse_tree) is an ordered, rooted tree that +represents the syntactic structure of a string according to some formal grammar. Most Rascal users will encounter parse trees in the form of concrete values. -Expert users may find the detailed description here useful when writing generic functions on parse trees. +Expert users may find the detailed description here useful when writing generic functions on parse trees. -In Rascal parse trees, the interior nodes are labeled by rules of the grammar, -while the leaf nodes are labeled by terminals (characters) of the grammar. +In Rascal parse trees, the interior nodes are labeled by rules of the grammar, +while the leaf nodes are labeled by terminals (characters) of the grammar. `Tree` is the universal parse tree data type in Rascal and can be used to represent parse trees for any language. @@ -29,26 +29,26 @@ while the leaf nodes are labeled by terminals (characters) of the grammar. * All concrete syntax expressions produce parse trees with a type corresponding to a non-terminal. * Trees can be annotated in various ways. Most importantly the `\loc` annotation always points to the source location of any (sub) parse tree. -_Advanced users_ may want to create tools that analyze any parse tree, regardless of the +_Advanced users_ may want to create tools that analyze any parse tree, regardless of the syntax definition that generated it, you can manipulate them on the abstract level. -A parse tree is of type ((ParseTree-Tree)) using the auxiliary types +A parse tree is of type ((ParseTree-Tree)) using the auxiliary types ((ParseTree-Production)), ((ParseTree-Symbol)), ((ParseTree-Condition)), ((ParseTree-Attr)), ((ParseTree-Associativity)), ((ParseTree-CharRange)). -Effectively, a parse tree is a nested tree structure of type `Tree`. +Effectively, a parse tree is a nested tree structure of type `Tree`. -* Most internal nodes are applications (`appl`) of a `Production` to a list of children `Tree` nodes. +* Most internal nodes are applications (`appl`) of a `Production` to a list of children `Tree` nodes. `Production` is the abstract representation of a rule in a syntax definition. which consists of a definition of an alternative for a `Symbol` by a list of `Symbols`. * The leaves of a parse tree are always -characters (`char`), which have an integer index in the UTF8 table. +characters (`char`), which have an integer index in the UTF8 table. -* Some internal nodes encode ambiguity (`amb`) by pointing to a set of +* Some internal nodes encode ambiguity (`amb`) by pointing to a set of alternative `Tree` nodes. The `Production` and `Symbol` types are an abstract notation for rules in syntax definitions, -while the `Tree` type is the actual notation for parse trees. +while the `Tree` type is the actual notation for parse trees. Parse trees are called parse forests when they contain `amb` nodes. @@ -58,7 +58,7 @@ You can analyze and manipulate parse trees in three ways: * Using concrete syntax expressions and concrete syntax patterns. * Using disambiguation actions (parameters of the `parse` function) -The type of a parse tree is the symbol that it's production produces, i.e. `appl(prod(sort("A"),[],{}),[])` has type `A`. Ambiguity nodes +The type of a parse tree is the symbol that it's production produces, i.e. `appl(prod(sort("A"),[],{}),[])` has type `A`. Ambiguity nodes Each such a non-terminal type has `Tree` as its immediate super-type. } @examples{ @@ -79,7 +79,7 @@ t := appl( {}), [char(97)])]); ``` -You see that the defined non-terminal A ends up as the production for the outermost node. +You see that the defined non-terminal A ends up as the production for the outermost node. As the only child is the tree for recognizing the literal a, which is defined to be a single a from the character-class `[ a ]`. When we use labels in the definitions, they also end up in the trees. @@ -122,7 +122,7 @@ t := appl( [char(98)])])]); ``` -Here you see that the alternative name is a label around the first argument of `prod` while argument labels become +Here you see that the alternative name is a label around the first argument of `prod` while argument labels become labels in the list of children of a `prod`. } @benefits{ @@ -143,6 +143,8 @@ extend Type; extend Message; extend List; +import String; +import Set; @synopsis{The Tree data type as produced by the parser.} @description{ @@ -157,7 +159,7 @@ A `Tree` defines the trees normally found after parsing; additional constructors data Tree //(loc src = |unknown:///|(0,0,<0,0>,<0,0>)) = appl(Production prod, list[Tree] args) // <1> | cycle(Symbol symbol, int cycleLength) // <2> - | amb(set[Tree] alternatives) // <3> + | amb(set[Tree] alternatives) // <3> | char(int character) // <4> ; @@ -170,25 +172,30 @@ construct ordered and un-ordered compositions, and associativity groups. <1> A `prod` is a rule of a grammar, with a defined non-terminal, a list of terminal and/or non-terminal symbols and a possibly empty set of attributes. - + <2> A `regular` is a regular expression, i.e. a repeated construct. <3> `priority` means operator precedence, where the order of the list indicates the binding strength of each rule; <4> `assoc` means all alternatives are acceptable, but nested on the declared side; <5> `reference` means a reference to another production rule which should be substituted there, for extending priority chains and such. -} -data Production +<6> `error` means a node produced by error recovery. +<7> `skipped` means characters skipped during error recovery, always the last child of an `appl` with a `error` production. +} +data Production = prod(Symbol def, list[Symbol] symbols, set[Attr] attributes) // <1> | regular(Symbol def) // <2> ; - -data Production + +data Production = \priority(Symbol def, list[Production] choices) // <3> | \associativity(Symbol def, Associativity \assoc, set[Production] alternatives) // <4> | \reference(Symbol def, str cons) // <5> ; +data Production + = \error(Symbol def, Production prod, int dot) + | \skipped(Symbol symbol); @synopsis{Attributes in productions.} @description{ @@ -196,8 +203,8 @@ An `Attr` (attribute) documents additional semantics of a production rule. Neith brackets are processed by the parser generator. Rather downstream processors are activated by these. Associativity is a parser generator feature though. } -data Attr - = \bracket() +data Attr + = \bracket() | \assoc(Associativity \assoc) ; @@ -205,11 +212,11 @@ data Attr @synopsis{Associativity attribute.} @description{ Associativity defines the various kinds of associativity of a specific production. -} -data Associativity +} +data Associativity = \left() - | \right() - | \assoc() + | \right() + | \assoc() | \non-assoc() ; @@ -254,34 +261,34 @@ data Symbol // <1> = \start(Symbol symbol); // These symbols are the named non-terminals. -data Symbol - = \sort(str name) // <2> +data Symbol + = \sort(str name) // <2> | \lex(str name) // <3> | \layouts(str name) // <4> | \keywords(str name) // <5> | \parameterized-sort(str name, list[Symbol] parameters) // <6> | \parameterized-lex(str name, list[Symbol] parameters) // <7> - ; + ; // These are the terminal symbols. -data Symbol +data Symbol = \lit(str string) // <8> | \cilit(str string) // <9> | \char-class(list[CharRange] ranges) // <10> ; - + // These are the regular expressions. data Symbol = \empty() // <11> | \opt(Symbol symbol) // <12> | \iter(Symbol symbol) // <13> | \iter-star(Symbol symbol) // <14> - | \iter-seps(Symbol symbol, list[Symbol] separators) // <15> + | \iter-seps(Symbol symbol, list[Symbol] separators) // <15> | \iter-star-seps(Symbol symbol, list[Symbol] separators) // <16> | \alt(set[Symbol] alternatives) // <17> | \seq(list[Symbol] symbols) // <18> ; - + data Symbol // <19> = \conditional(Symbol symbol, set[Condition] conditions); @@ -292,7 +299,7 @@ bool subtype(Symbol::\sort(_), Symbol::\adt("Tree", _)) = true; @description{ A `Condition` can be attached to a symbol; it restricts the applicability of that symbol while parsing input text. For instance, `follow` requires that it -is followed by another symbol and `at-column` requires that it occurs +is followed by another symbol and `at-column` requires that it occurs at a certain position in the current line of the input text. } data Condition @@ -301,9 +308,9 @@ data Condition | \precede(Symbol symbol) | \not-precede(Symbol symbol) | \delete(Symbol symbol) - | \at-column(int column) - | \begin-of-line() - | \end-of-line() + | \at-column(int column) + | \begin-of-line() + | \end-of-line() | \except(str label) ; @@ -311,7 +318,7 @@ data Condition @synopsis{Nested priority is flattened.} Production priority(Symbol s, [*Production a, priority(Symbol _, list[Production] b), *Production c]) = priority(s,a+b+c); - + @synopsis{Normalization of associativity.} @description{ @@ -319,15 +326,15 @@ Production priority(Symbol s, [*Production a, priority(Symbol _, list[Production * Nested (equal) associativity is flattened. * ((ParseTree-priority)) under an associativity group defaults to choice. } -Production associativity(Symbol s, Associativity as, {*Production a, choice(Symbol t, set[Production] b)}) - = associativity(s, as, a+b); - -Production associativity(Symbol rhs, Associativity a, {associativity(rhs, Associativity b, set[Production] alts), *Production rest}) +Production associativity(Symbol s, Associativity as, {*Production a, choice(Symbol t, set[Production] b)}) + = associativity(s, as, a+b); + +Production associativity(Symbol rhs, Associativity a, {associativity(rhs, Associativity b, set[Production] alts), *Production rest}) = associativity(rhs, a, rest + alts); // the nested associativity, even if contradictory, is lost -Production associativity(Symbol s, Associativity as, {*Production a, priority(Symbol t, list[Production] b)}) - = associativity(s, as, {*a, *b}); - +Production associativity(Symbol s, Associativity as, {*Production a, priority(Symbol t, list[Production] b)}) + = associativity(s, as, {*a, *b}); + @synopsis{Annotate a parse tree node with a source location.} anno loc Tree@\loc; @@ -341,11 +348,11 @@ anno loc Tree@\loc; The parse either throws ParseError exceptions or returns parse trees of type `Tree`. See [[ParseTree]]. -The `allowAmbiguity` flag dictates the behavior of the parser in the case of ambiguity. When `allowAmbiguity=true` +The `allowAmbiguity` flag dictates the behavior of the parser in the case of ambiguity. When `allowAmbiguity=true` the parser will construct ambiguity clusters (local sets of parse trees where the input string is ambiguous). If it is `false` the parser will throw an `Ambiguous` exception instead. An `Ambiguous` exception is comparable to a ParseError exception then. -The latter option terminates much faster, i.e. always in cubic time, and always linear in the size of the intermediate parse graph, -while constructing ambiguous parse forests may grow to O(n^p+1), where p is the length of the longest production rule and n +The latter option terminates much faster, i.e. always in cubic time, and always linear in the size of the intermediate parse graph, +while constructing ambiguous parse forests may grow to O(n^p+1), where p is the length of the longest production rule and n is the length of the input. The `filters` set contains functions which may be called optionally after the parse algorithm has finished and just before @@ -355,16 +362,16 @@ composed filters must be added to the set of filters programmatically. Post-pars not later on the Tree representation for efficiency reasons. Namely, the size of the parse graph before Tree construction is still cubic due to "binarized" sharing of intermediate nodes, while after Tree construction the forest may obtain a size in O(n^p+1) where n is the length of the input and p is the length of the longest syntax rule. Filtering using -the `filters` parameter, on the other hand, may very well cut the forest quickly down to even a linear size and result in +the `filters` parameter, on the other hand, may very well cut the forest quickly down to even a linear size and result in an efficient overall parsing algorithm. The `hasSideEffects` flag is normally set to false. When the `filters` functions have side-effects to remove ambiguity, this option must be set to `true` to ensure correct behavior. A side-effect of filter functions is -typically the construction of a symbol table and the removal (see [[Statements/Filter]]) of syntax trees which refer to +typically the construction of a symbol table and the removal (see [[Statements/Filter]]) of syntax trees which refer to undefined symbols. In such a case `hasSideEffects` must be set to `true` for correctness' sake. If its set to `false` then the algorithm assumes tree construction is context-free and it can memoize the results of shared intermediate graph nodes. The tree construction algorithm is effectively always worst case -polynomial in O(n^p+1) --p being the length of the longest syntax rule-- when `hasSideEffects` is true, but may be linear when set +polynomial in O(n^p+1) --p being the length of the longest syntax rule-- when `hasSideEffects` is true, but may be linear when set to false. So this is quite an important flag to consider. } @examples{ @@ -392,14 +399,15 @@ catch ParseError(loc l): { } ``` } -&T<:Tree parse(type[&T<:Tree] begin, str input, bool allowAmbiguity=false, bool hasSideEffects=false, set[Tree(Tree)] filters={}) - = parser(begin, allowAmbiguity=allowAmbiguity, hasSideEffects=hasSideEffects, filters=filters)(input, |unknown:///|); -&T<:Tree parse(type[&T<:Tree] begin, str input, loc origin, bool allowAmbiguity=false, bool hasSideEffects=false, set[Tree(Tree)] filters={}) - = parser(begin, allowAmbiguity=allowAmbiguity, hasSideEffects=hasSideEffects, filters=filters)(input, origin); - -&T<:Tree parse(type[&T<:Tree] begin, loc input, bool allowAmbiguity=false, bool hasSideEffects=false, set[Tree(Tree)] filters={}) - = parser(begin, allowAmbiguity=allowAmbiguity, hasSideEffects=hasSideEffects, filters=filters)(input, input); +&T<:Tree parse(type[&T<:Tree] begin, str input, bool allowAmbiguity=false, bool allowRecovery=false, bool hasSideEffects=false, set[Tree(Tree)] filters={}) + = parser(begin, allowAmbiguity=allowAmbiguity, allowRecovery=allowRecovery, hasSideEffects=hasSideEffects, filters=filters)(input, |unknown:///|); + +&T<:Tree parse(type[&T<:Tree] begin, str input, loc origin, bool allowAmbiguity=false, bool allowRecovery=false, bool hasSideEffects=false, set[Tree(Tree)] filters={}) + = parser(begin, allowAmbiguity=allowAmbiguity, allowRecovery=allowRecovery, hasSideEffects=hasSideEffects, filters=filters)(input, origin); + +&T<:Tree parse(type[&T<:Tree] begin, loc input, bool allowAmbiguity=false, bool allowRecovery=false, bool hasSideEffects=false, set[Tree(Tree)] filters={}) + = parser(begin, allowAmbiguity=allowAmbiguity, allowRecovery=allowRecovery, hasSideEffects=hasSideEffects, filters=filters)(input, input); @synopsis{Generates a parser from an input grammar.} @@ -415,15 +423,16 @@ So the parse function reads either directly from a str or via the contents of a which leads to the prefix of the `src` fields of the resulting tree. The parse function behaves differently depending of the given keyword parameters: - * `allowAmbiguity`: if true then no exception is thrown in case of ambiguity and a parse forest is returned. if false, + * `allowAmbiguity`: if true then no exception is thrown in case of ambiguity and a parse forest is returned. if false, the parser throws an exception during tree building and produces only the first ambiguous subtree in its message. if set to `false`, the parse constructs trees in linear time. if set to `true` the parser constructs trees in polynomial time. - * + * 'allowRecovery`: ***experimental*** if true, the parser tries to recover when it encounters a parse error. if a parse error is encountered that can be recovered from, + special `error` and `skipped` nodes are included in the resulting parse tree. More documentation will be added here when this feature matures. * `hasSideEffects`: if false then the parser is a lot faster when constructing trees, since it does not execute the parse _actions_ in an interpreted environment to make side effects (like a symbol table) and it can share more intermediate results as a result. } @javaClass{org.rascalmpl.library.Prelude} -java &T (value input, loc origin) parser(type[&T] grammar, bool allowAmbiguity=false, bool hasSideEffects=false, set[Tree(Tree)] filters={}); +java &T (value input, loc origin) parser(type[&T] grammar, bool allowAmbiguity=false, bool allowRecovery=false, bool hasSideEffects=false, set[Tree(Tree)] filters={}); @javaClass{org.rascalmpl.library.Prelude} @synopsis{Generates a parser function that can be used to find the left-most deepest ambiguous sub-sentence.} @@ -433,10 +442,10 @@ the tree that exhibits ambiguity. This can be done very quickly, while the whole * Use this function for ambiguity diagnostics and regression testing for ambiguity. } @pitfalls{ -* The returned sub-tree usually has a different type than the parameter of the type[] symbol that was passed in. +* The returned sub-tree usually has a different type than the parameter of the type[] symbol that was passed in. The reason is that sub-trees typically have a different non-terminal than the start non-terminal of a grammar. } -java Tree (value input, loc origin) firstAmbiguityFinder(type[Tree] grammar, bool hasSideEffects=false, set[Tree(Tree)] filters={}); +java Tree (value input, loc origin) firstAmbiguityFinder(type[Tree] grammar, bool allowRecovery=false, bool hasSideEffects=false, set[Tree(Tree)] filters={}); @synopsis{Generates parsers from a grammar (reified type), where all non-terminals in the grammar can be used as start-symbol.} @description{ @@ -444,7 +453,7 @@ This parser generator behaves the same as the `parser` function, but it produces nonterminal parameter. This can be used to select a specific non-terminal from the grammar to use as start-symbol for parsing. } @javaClass{org.rascalmpl.library.Prelude} -java &U (type[&U] nonterminal, value input, loc origin) parsers(type[&T] grammar, bool allowAmbiguity=false, bool hasSideEffects=false, set[Tree(Tree)] filters={}); +java &U (type[&U] nonterminal, value input, loc origin) parsers(type[&T] grammar, bool allowAmbiguity=false, bool allowRecovery=false, bool hasSideEffects=false, set[Tree(Tree)] filters={}); @javaClass{org.rascalmpl.library.Prelude} @synopsis{Generates a parser function that can be used to find the left-most deepest ambiguous sub-sentence.} @@ -454,16 +463,16 @@ the tree that exhibits ambiguity. This can be done very quickly, while the whole * Use this function for ambiguity diagnostics and regression testing for ambiguity. } @pitfalls{ -* The returned sub-tree usually has a different type than the parameter of the type[] symbol that was passed in. +* The returned sub-tree usually has a different type than the parameter of the type[] symbol that was passed in. The reason is that sub-trees typically have a different non-terminal than the start non-terminal of a grammar. } -java Tree (type[Tree] nonterminal, value input, loc origin) firstAmbiguityFinders(type[Tree] grammar, bool hasSideEffects=false, set[Tree(Tree)] filters={}); +java Tree (type[Tree] nonterminal, value input, loc origin) firstAmbiguityFinders(type[Tree] grammar, bool allowRecovery=false, bool hasSideEffects=false, set[Tree(Tree)] filters={}); @synopsis{Parse the input but instead of returning the entire tree, return the trees for the first ambiguous substring.} @description{ This function is similar to the ((parse)) function in its functionality. However, in case of serious ambiguity parse could be very slow. This function is much faster, because it does not try to construct an entire forest and thus avoids -the cost of constructing nested ambiguity clusters. +the cost of constructing nested ambiguity clusters. If the input sentence is not ambiguous after all, simply the entire tree is returned. } @@ -535,15 +544,15 @@ p(type(sort("E"), ()), "e+e", |src:///|); * reifiying types (use of `#`) will trigger the loading of a parser generator anyway. You have to use this notation for types to avoid that: `type(\start(sort("MySort")), ())` to avoid the computation for `#start[A]` } -java &U (type[&U] nonterminal, value input, loc origin) loadParsers(loc savedParsers, bool allowAmbiguity=false, bool hasSideEffects=false, set[Tree(Tree)] filters={}); +java &U (type[&U] nonterminal, value input, loc origin) loadParsers(loc savedParsers, bool allowAmbiguity=false, bool allowRecovery=false, bool hasSideEffects=false, set[Tree(Tree)] filters={}); @synopsis{Load a previously serialized parser, for a specific non-terminal, from disk for usage} @description{ This loader behaves just like ((loadParsers)), except that the resulting parser function is already -bound to a specific non-terminal. +bound to a specific non-terminal. } @javaClass{org.rascalmpl.library.Prelude} -java &U (value input, loc origin) loadParser(type[&U] nonterminal, loc savedParsers, bool allowAmbiguity=false, bool hasSideEffects=false, set[Tree(Tree)] filters={}); +java &U (value input, loc origin) loadParser(type[&U] nonterminal, loc savedParsers, bool allowAmbiguity=false, bool allowRecovery=false, bool hasSideEffects=false, set[Tree(Tree)] filters={}); @synopsis{Yield the string of characters that form the leafs of the given parse tree.} @description{ @@ -594,20 +603,20 @@ an abstract syntax tree (a value of the given type) as follows: * Literals, layout and empty (i.e. ()) nodes are skipped. -* Regular */+ lists are imploded to `list`s or `set`s depending on what is +* Regular */+ lists are imploded to `list`s or `set`s depending on what is expected in the ADT. * Ambiguities are imploded to `set`s. -* If the expected type is `str` the tree is unparsed into a string. This happens for both +* If the expected type is `str` the tree is unparsed into a string. This happens for both lexical and context-free parse trees. * If a tree's production has no label and a single AST (i.e. non-layout, non-literal) argument - (for instance, an injection), the tree node is skipped, and implosion continues + (for instance, an injection), the tree node is skipped, and implosion continues with the lone argument. The same applies to bracket productions, even if they are labeled. -* If a tree's production has no label, but more than one argument, the tree is imploded +* If a tree's production has no label, but more than one argument, the tree is imploded to a tuple (provided this conforms to the ADT). * Optionals are imploded to booleans if this is expected in the ADT. @@ -622,14 +631,14 @@ an abstract syntax tree (a value of the given type) as follows: * For trees with (cons-)labeled productions, the corresponding constructor in the ADT corresponding to the non-terminal of the production is found in order to make the AST. - + * If the provided type is `node`, (cons-)labeled trees will be imploded to untyped `node`s. - This means that any subtrees below it will be untyped nodes (if there is a label), tuples of - nodes (if a label is absent), and strings for lexicals. + This means that any subtrees below it will be untyped nodes (if there is a label), tuples of + nodes (if a label is absent), and strings for lexicals. * Unlabeled lexicals are imploded to str, int, real, bool depending on the expected type in - the ADT. To implode lexical into types other than str, the PDB parse functions for - integers and doubles are used. Boolean lexicals should match "true" or "false". + the ADT. To implode lexical into types other than str, the PDB parse functions for + integers and doubles are used. Boolean lexicals should match "true" or "false". NB: lexicals are imploded this way, even if they are ambiguous. * If a lexical tree has a cons label, the tree imploded to a constructor with that name @@ -638,11 +647,11 @@ an abstract syntax tree (a value of the given type) as follows: An `IllegalArgument` exception is thrown if during implosion a tree is encountered that cannot be imploded to the expected type in the ADT. As explained above, this function assumes that the -ADT type names correspond to syntax non-terminal names, and constructor names correspond +ADT type names correspond to syntax non-terminal names, and constructor names correspond to production labels. Labels of production arguments do not have to match with labels in ADT constructors. -Finally, source location fields are propagated as keyword fields on constructor ASTs. +Finally, source location fields are propagated as keyword fields on constructor ASTs. To access them, the user is required to explicitly declare a keyword field on all ADTs used in implosion. In other words, for every ADT type `T`, add: @@ -662,7 +671,7 @@ syntax Decls = decls: "declare" {IDTYPE ","}* ";"; ```rascal data Decls = decls(list[tuple[str,Type]]); ``` -(assuming Id is a lexical non-terminal). +(assuming Id is a lexical non-terminal). * Example for rule 6. Given the grammar ```rascal syntax Formal = formal: "VAR"? {Id ","}+ ":" Type; @@ -678,7 +687,7 @@ syntax Decl = decl: Tag? Signature Body; ``` In this case, a `Decl` is imploded into the following ADT: ```rascal -data Decl = decl(list[Modifier], Signature, Body); +data Decl = decl(list[Modifier], Signature, Body); ``` * Example for rule 9. Given the grammar ```rascal @@ -737,7 +746,7 @@ TreeSearchResult[&T<:Tree] treeAt(type[&T<:Tree] t, loc l, Tree a:appl(_, _)) { for (arg <- a.args, TreeSearchResult[&T<:Tree] r:treeFound(&T<:Tree _) := treeAt(t, l, arg)) { return r; } - + if (&T<:Tree tree := a) { return treeFound(tree); } @@ -764,3 +773,65 @@ bool isNonTerminalType(Symbol::\parameterized-sort(str _, list[Symbol] _)) = tru bool isNonTerminalType(Symbol::\parameterized-lex(str _, list[Symbol] _)) = true; bool isNonTerminalType(Symbol::\start(Symbol s)) = isNonTerminalType(s); default bool isNonTerminalType(Symbol s) = false; + +@synopsis{Check if a parse tree contains any error nodes, the result of error recovery.} +bool hasErrors(Tree tree) = /appl(error(_, _, _), _) := tree; + +@synopsis{Find all error productions in a parse tree.} +list[Tree] findAllErrors(Tree tree) = [err | /err:appl(error(_, _, _), _) := tree]; + +@synopsis{Find the first production containing an error.} +Tree findFirstError(/err:appl(error(_, _, _), _)) = err; + +@synopsis{Find the best error from a tree containing errors. This function will fail if `tree` does not contain an error.} +Tree findBestError(Tree tree) = findFirstError(defaultErrorDisambiguationFilter(tree)); + +@synopsis{Get the symbol (sort) of the failing production} +Symbol getErrorSymbol(appl(error(Symbol sym, _, _), _)) = sym; + +@synopsis{Get the production that failed} +Production getErrorProduction(appl(error(_, Production prod, _), _)) = prod; + +@synopsis{Get the dot (position in the production) of the failing element in a production} +int getErrorDot(appl(error(_, _, int dot), _)) = dot; + +@synopsis{Get the skipped tree} +Tree getSkipped(appl(error(_, _, _), [*_, skip:appl(skipped(_), _)])) = skip; + +@synopsis{Get the text that failed to parse. This is only the text of the part that has been skipped to be able to continue parsing. +If you want the text of the whole error tree, you can just use string interpolation: "". +} +str getErrorText(appl(error(_, _, _), [*_, appl(skipped(_), chars)])) = stringChars([c | char(c) <- chars]); + +@synopsis{Error recovery often produces ambiguous trees where errors can be recovered in multiple ways. +This filter removes error trees until no ambiguities caused by error recovery are left. +Note that regular ambiguous trees remain in the parse forest. +} +Tree defaultErrorDisambiguationFilter(Tree t) { + return visit(t) { + case a:amb(_) => ambDisambiguation(a) + }; +} + +private Tree ambDisambiguation(amb(set[Tree] alternatives)) { + // Go depth-first + rel[int score, Tree alt] scoredErrorTrees = { | Tree alt <- alternatives }; + set[Tree] nonErrorTrees = scoredErrorTrees[0]; + + if (nonErrorTrees == {}) { + return (getFirstFrom(scoredErrorTrees) | it.score > c.score ? c : it | c <- scoredErrorTrees).alt; + } + + if ({Tree single} := nonErrorTrees) { + // One ambiguity left, no ambiguity concerns here + return single; + } + + // Multiple non-error trees left, return an ambiguity node with just the non-error trees + return amb(nonErrorTrees); +} + +private int scoreErrors(Tree t) = (0 | it + getSkipped(e).src.length | /e:appl(error(_,_,_),_) := t); + +// Handle char and cycle nodes +default Tree defaultErrorDisambiguationFilter(Tree t) = t; diff --git a/src/org/rascalmpl/library/Prelude.java b/src/org/rascalmpl/library/Prelude.java index a28ca8f0d79..d53a449cbfa 100644 --- a/src/org/rascalmpl/library/Prelude.java +++ b/src/org/rascalmpl/library/Prelude.java @@ -2377,20 +2377,24 @@ public INode arbNode() { protected final TypeReifier tr; + public IFunction parser(IValue start, IBool allowAmbiguity, IBool allowRecovery, IBool hasSideEffects, ISet filters) { + return rascalValues.parser(start, allowAmbiguity, allowRecovery, hasSideEffects, values.bool(false), filters); + } + public IFunction parser(IValue start, IBool allowAmbiguity, IBool hasSideEffects, ISet filters) { - return rascalValues.parser(start, allowAmbiguity, hasSideEffects, values.bool(false), filters); + return rascalValues.parser(start, allowAmbiguity, values.bool(false), hasSideEffects, values.bool(false), filters); } - public IFunction firstAmbiguityFinder(IValue start, IBool hasSideEffects, ISet filters) { - return rascalValues.parser(start, values.bool(true), hasSideEffects, values.bool(true), filters); + public IFunction firstAmbiguityFinder(IValue start, IBool allowRecovery, IBool hasSideEffects, ISet filters) { + return rascalValues.parser(start, values.bool(true), allowRecovery, hasSideEffects, values.bool(true), filters); } - public IFunction parsers(IValue start, IBool allowAmbiguity, IBool hasSideEffects, ISet filters) { - return rascalValues.parsers(start, allowAmbiguity, hasSideEffects, values.bool(false), filters); + public IFunction parsers(IValue start, IBool allowAmbiguity, IBool allowRecovery, IBool hasSideEffects, ISet filters) { + return rascalValues.parsers(start, allowAmbiguity, allowRecovery, hasSideEffects, values.bool(false), filters); } - public IFunction firstAmbiguityFinders(IValue start, IBool hasSideEffects, ISet filters) { - return rascalValues.parsers(start, values.bool(true), hasSideEffects, values.bool(true), filters); + public IFunction firstAmbiguityFinders(IValue start, IBool allowRecovery, IBool hasSideEffects, ISet filters) { + return rascalValues.parsers(start, values.bool(true), allowRecovery, hasSideEffects, values.bool(true), filters); } public void storeParsers(IValue start, ISourceLocation saveLocation) { @@ -2405,18 +2409,18 @@ public void storeParsers(IValue start, ISourceLocation saveLocation) { } } - public IFunction loadParsers(ISourceLocation savedLocation, IBool allowAmbiguity, IBool hasSideEffects, ISet filters) { + public IFunction loadParsers(ISourceLocation savedLocation, IBool allowAmbiguity, IBool allowRecovery, IBool hasSideEffects, ISet filters) { try { - return rascalValues.loadParsers(savedLocation, allowAmbiguity, hasSideEffects, values.bool(false), filters); + return rascalValues.loadParsers(savedLocation, allowAmbiguity, allowRecovery, hasSideEffects, values.bool(false), filters); } catch (IOException | ClassNotFoundException e) { throw RuntimeExceptionFactory.io(e.getMessage()); } } - public IFunction loadParser(IValue grammar, ISourceLocation savedLocation, IBool allowAmbiguity, IBool hasSideEffects, ISet filters) { + public IFunction loadParser(IValue grammar, ISourceLocation savedLocation, IBool allowRecovery, IBool allowAmbiguity, IBool hasSideEffects, ISet filters) { try { - return rascalValues.loadParser(grammar, savedLocation, allowAmbiguity, hasSideEffects, values.bool(false), filters); + return rascalValues.loadParser(grammar, savedLocation, allowAmbiguity, allowRecovery, hasSideEffects, values.bool(false), filters); } catch (IOException | ClassNotFoundException e) { throw RuntimeExceptionFactory.io(e.getMessage()); diff --git a/src/org/rascalmpl/library/lang/c90/examples/hello-world.c b/src/org/rascalmpl/library/lang/c90/examples/hello-world.c new file mode 100644 index 00000000000..3e04f5ac330 --- /dev/null +++ b/src/org/rascalmpl/library/lang/c90/examples/hello-world.c @@ -0,0 +1,19 @@ + +int print(const char *text); + +void printHello(char *name) { + print("Hello "); + print(name); + print("!"); +} + +int main(int argc, char *argv[]) { + char *name; + if (argc > 1) { + name = argv[1]; + } else { + name = "World"; + } + + printHello(name); +} diff --git a/src/org/rascalmpl/library/lang/diff/unified/UnifiedDiff.rsc b/src/org/rascalmpl/library/lang/diff/unified/UnifiedDiff.rsc index d4e84750c73..4e2d114f003 100644 --- a/src/org/rascalmpl/library/lang/diff/unified/UnifiedDiff.rsc +++ b/src/org/rascalmpl/library/lang/diff/unified/UnifiedDiff.rsc @@ -3,6 +3,8 @@ @contributor{Tijs van der Storm - storm@cwi.nl (CWI)} module lang::diff::unified::UnifiedDiff +start syntax DiffFile = Diff; + syntax Diff = Header old Header new Chunk* chunks ; diff --git a/src/org/rascalmpl/library/lang/diff/unified/examples/example.diff b/src/org/rascalmpl/library/lang/diff/unified/examples/example.diff new file mode 100644 index 00000000000..ea75dfad7a5 --- /dev/null +++ b/src/org/rascalmpl/library/lang/diff/unified/examples/example.diff @@ -0,0 +1,39 @@ +--- a/src/org/rascalmpl/parser/uptr/UPTRNodeFactory.java ++++ b/src/org/rascalmpl/parser/uptr/UPTRNodeFactory.java +@@ -1,6 +1,7 @@ + package org.rascalmpl.parser.uptr; + + import java.net.URI; ++import java.util.Arrays; + import java.util.IdentityHashMap; + import java.util.Map; + +@@ -21,7 +22,9 @@ import org.rascalmpl.values.parsetrees.ProductionAdapter; + import org.rascalmpl.values.parsetrees.TreeAdapter; + + public class UPTRNodeFactory implements INodeConstructorFactory{ +- private final static RascalValueFactory VF = (RascalValueFactory) ValueFactoryFactory.getValueFactory(); ++ private static final RascalValueFactory VF = (RascalValueFactory) ValueFactoryFactory.getValueFactory(); ++ private static final IConstructor SKIPPED = VF.constructor(RascalValueFactory.Production_Skipped, VF.constructor(RascalValueFactory.Symbol_IterStar, VF.constructor(RascalValueFactory.Symbol_CharClass, VF.list(VF.constructor(RascalValueFactory.CharRange_Range, VF.integer(1), VF.integer(Character.MAX_CODE_POINT)))))); ++ + private boolean allowAmb; + + public UPTRNodeFactory(boolean allowAmbiguity){ +@@ -141,7 +144,14 @@ public class UPTRNodeFactory implements INodeConstructorFactory children, Object production) { ++ IConstructor prod = (IConstructor) production; ++ IConstructor errorProd = VF.constructor(RascalValueFactory.Production_Error, prod.get(0), prod, VF.integer(children.size()-1)); ++ return buildAppl(children, errorProd); ++ } ++ + } diff --git a/src/org/rascalmpl/library/lang/dot/examples/parser-state.dot b/src/org/rascalmpl/library/lang/dot/examples/parser-state.dot new file mode 100644 index 00000000000..fc3047e35a5 --- /dev/null +++ b/src/org/rascalmpl/library/lang/dot/examples/parser-state.dot @@ -0,0 +1,65 @@ +digraph Parser { +"Parser"["label"="Parser\nInput: \"void f(){if(1){}}\"\nLocation: 0 ('v')\nStep 5: Reducing terminals"]; +"todo-1"["label"="<0> 0", "shape"="record"]; +"-2"["label"="Epsilon: \n.0@0 ,matchable,end\n?\nin: 'lex(\"LAYOUT\") -> regular(\iter-star(lex(\"LAYOUT\")))'"]; +"7226"["label"="List: 7226\n.0@0 ,expandable,end\n7226\nin: 'LAYOUTLIST -> \iter-star(lex(\"LAYOUT\"))'"]; +"12860"["label"="NonTerminal: LAYOUTLIST\n.1@0 \nlayouts_LAYOUTLIST\nin: Tags Visibility Signature '=' Expression 'when' 12878 ';'"]; +"-1"["label"="NonTerminal: FunctionDeclaration\n.0@-1 \nFunctionDeclaration"]; +"12860" -> "-1"; +"7226" -> "12860"; +"-2" -> "7226"; +"todo-1":"0":sw -> "-2"["label"="Stack"]; +"46484886"["shape"="octagon", "label"="Epsilon"]; +"todo-1":"0":se -> "46484886"["label"="Node"]; +"todoLists":"1" -> "todo-1"; +"todoLists"["label"="<0> 0 | <1> 1 | <2> 2 | <3> 3 | <4> 4 | <5> 5 | <6> 6 | <7> 7 | <8> 8 | <9> 9 | <10> 10 | <11> 11 | <12> 12 | <13> 13 | <14> 14 | <15> 15", "shape"="record"]; +"Parser" -> "todoLists"["label"="todo lists"]; +"stacksToExpand"["label"="", "shape"="record"]; +"Parser" -> "stacksToExpand"["label"="stacks to expand"]; +"terminalsToReduce"["label"="<0> 0", "shape"="record", "color"="red"]; +"terminalsToReduce":"0":sw -> "-2"["label"="Stack"]; +"terminalsToReduce":"0":se -> "46484886"["label"="Node"]; +"Parser" -> "terminalsToReduce"["label"="terminals to reduce"]; +"nonTerminalsToReduce"["label"="", "shape"="record"]; +"Parser" -> "nonTerminalsToReduce"["label"="non-terminals to reduce"]; +"122"["label"="NonTerminal: Tag\n.0@0 ,end\nTag\nin: 'sort(\"Tag\") -> regular(\iter-star-seps(sort(\"Tag\"),[layouts(\"LAYOUTLIST\")]))'"]; +"124"["label"="SeparatedList: 124\n.0@0 ,expandable,end\n124\nin: 'default -> tags'"]; +"12858"["label"="NonTerminal: Tags\n.0@0 \nTags\nin: Tags Visibility Signature '=' Expression 'when' 12878 ';'"]; +"12858" -> "-1"; +"124" -> "12858"; +"122" -> "124"; +"unexpandableNodes":"0" -> "122"; +"13120"["label"="NonTerminal: Comment\n.0@0 ,end\nComment\nin: 'LAYOUT -> Comment'"]; +"7221"["label"="NonTerminal: LAYOUT\n.0@0 ,end\nLAYOUT\nin: 'lex(\"LAYOUT\") -> regular(\iter-star(lex(\"LAYOUT\")))'"]; +"7221" -> "7226"; +"13120" -> "7221"; +"unexpandableNodes":"1" -> "13120"; +"unexpandableNodes"["label"="<0> 0 | <1> 1", "shape"="record"]; +"12824"["label"="Char: \n.0@-1 ,matchable\n0\nin: 0 'sort(\"FunctionDeclaration\")' ':' 12828 0"]; +"unmatchableLeafNodes":"0" -> "12824"; +"128"["label"="Char: \n.0@-1 ,matchable\n0\nin: 0 'sort(\"Tags\")' ':' 132 0"]; +"unmatchableLeafNodes":"1" -> "128"; +"2043"["label"="Literal: \n.0@-1 ,matchable\n'@'\nin: '@' Name '=' Expression"]; +"unmatchableLeafNodes":"2" -> "2043"; +"2065"["label"="Char: \n.0@-1 ,matchable\n0\nin: 0 '\iter-star(sort(\"Tag\"))' ':' 2069 0"]; +"unmatchableLeafNodes":"3" -> "2065"; +"13122"["label"="Char: \n.0@-1 ,matchable,end\n9-13,32,133,160,5760,6158,8192-8202,8232-8233,8239,8287,12288\nin: 'LAYOUT -> [range(9,13),range(32,32),range(133,133),range(160,160),range(5760,5760),range(6158,6158),range(8192,8202),range(8232,8233),range(8239,8239),range(8287,8287),range(12288,12288)]'"]; +"unmatchableLeafNodes":"4" -> "13122"; +"13125"["label"="Char: \n.0@-1 ,matchable\n0\nin: 0 '\iter-star(sort(\"LAYOUT\"))' ':' 13129 0"]; +"unmatchableLeafNodes":"5" -> "13125"; +"7373"["label"="Literal: \n.0@-1 ,matchable\n'/*'\nin: '/*' 7379 '*/'"]; +"unmatchableLeafNodes":"6" -> "7373"; +"7382"["label"="Literal: \n.0@-1 ,matchable\n'//'\nin: '//' 7386"]; +"unmatchableLeafNodes":"7" -> "7382"; +"7389"["label"="Char: \n.0@-1 ,matchable\n0\nin: 0 'sort(\"Comment\")' ':' 7393 0"]; +"unmatchableLeafNodes":"8" -> "7389"; +"unmatchableLeafNodes"["label"="<0> 0 | <1> 1 | <2> 2 | <3> 3 | <4> 4 | <5> 5 | <6> 6 | <7> 7 | <8> 8", "shape"="record"]; +"unmatchableMidProductionNodes"["shape"="record", "label"=""]; +"filteredNodes"["label"="", "shape"="record"]; +"error"["label"="Errors"]; +"Parser" -> "error"["label"="error tracking"]; +"error" -> "unexpandableNodes"["label"="unexpandable"]; +"error" -> "unmatchableLeafNodes"["label"="unmatchable leafs"]; +"error" -> "unmatchableMidProductionNodes"["label"="unmatchable mid-prod"]; +"error" -> "filteredNodes"["label"="filtered"]; +} diff --git a/src/org/rascalmpl/library/lang/dot/syntax/Dot.rsc b/src/org/rascalmpl/library/lang/dot/syntax/Dot.rsc index 7fcf9b98ca7..d50f8f66a52 100644 --- a/src/org/rascalmpl/library/lang/dot/syntax/Dot.rsc +++ b/src/org/rascalmpl/library/lang/dot/syntax/Dot.rsc @@ -68,7 +68,7 @@ syntax NodeId | Id Port ; -syntax Port = ":" Id Id? +syntax Port = ":" Id (":" Id)? // | ":" Id // | ":" CompassPt ; diff --git a/src/org/rascalmpl/library/lang/pico/examples/fac.pico b/src/org/rascalmpl/library/lang/pico/examples/fac.pico new file mode 100644 index 00000000000..92e8ff7bb34 --- /dev/null +++ b/src/org/rascalmpl/library/lang/pico/examples/fac.pico @@ -0,0 +1,18 @@ +begin declare input : natural, + output : natural, + repnr : natural, + rep : natural, + s1 : string, + s2 : string; + input := 14; + output := 1; + while input - 1 do + rep := output; + repnr := input; + while repnr - 1 do + output := output + rep; + repnr := repnr - 1 + od; + input := input - 1 + od +end diff --git a/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/BasicRecoveryTests.rsc b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/BasicRecoveryTests.rsc new file mode 100644 index 00000000000..23b3af5d2f5 --- /dev/null +++ b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/BasicRecoveryTests.rsc @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2024, NWO-I Centrum Wiskunde & Informatica (CWI) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + +module lang::rascal::tests::concrete::recovery::BasicRecoveryTests + +import ParseTree; + +layout Layout = [\ ]* !>> [\ ]; + +syntax S = T; + +syntax T = ABC End; +syntax ABC = 'a' 'b' 'c'; +syntax End = "$"; + +private Tree parseS(str input, bool visualize=false) + = parser(#S, allowRecovery=true, allowAmbiguity=true)(input, |unknown:///?visualize=<"">|); + +test bool basicOk() { + return !hasErrors(parseS("a b c $")); +} + +test bool abx() { + Tree t = parseS("a b x $"); + return getErrorText(findBestError(t)) == "x "; +} + +test bool axc() { + Tree t = parseS("a x c $"); + return getErrorText(findBestError(t)) == "x c"; +} + +test bool ax() { + Tree t = parseS("a x $"); + return getErrorText(findBestError(t)) == "x "; +} diff --git a/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/ErrorRecoveryBenchmark.rsc b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/ErrorRecoveryBenchmark.rsc new file mode 100644 index 00000000000..6642b08d605 --- /dev/null +++ b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/ErrorRecoveryBenchmark.rsc @@ -0,0 +1,58 @@ +module lang::rascal::tests::concrete::recovery::ErrorRecoveryBenchmark + +import lang::rascal::tests::concrete::recovery::RecoveryTestSupport; + +import IO; +import util::Benchmark; +import String; +import List; + +void runTestC() { testRecoveryC(); } +void runTestDiff() { testRecoveryDiff(); } +void runTestDot() { testRecoveryDot(); } +void runTestJava() { testRecoveryJava(); } +void runTestJson() { testRecoveryJson(); } +void runTestPico() { testRecoveryPico(); } +void runTestRascal() { testRecoveryRascal(); } + +FileStats testRecoveryC() = testErrorRecovery(|std:///lang/c90/syntax/C.rsc|, "TranslationUnit", |std:///lang/c90/examples/hello-world.c|); +FileStats testRecoveryDiff() = testErrorRecovery(|std:///lang/diff/unified/UnifiedDiff.rsc|, "DiffFile", |std:///lang/diff/unified/examples/example.diff|); +FileStats testRecoveryDot() = testErrorRecovery(|std:///lang/dot/syntax/Dot.rsc|, "DOT", |std:///lang/dot/examples/parser-state.dot|); +FileStats testRecoveryJava() = testErrorRecovery(|std:///lang/java/syntax/Java15.rsc|, "CompilationUnit", zippedFile("m3/snakes-and-ladders-project-source.zip", "src/snakes/LastSquare.java")); +FileStats testRecoveryJson() = testErrorRecovery(|std:///lang/json/syntax/JSON.rsc|, "JSONText", |std:///lang/json/examples/ex01.json|); +FileStats testRecoveryPico() = testErrorRecovery(|std:///lang/pico/syntax/Main.rsc|, "Program", |std:///lang/pico/examples/fac.pico|); +FileStats testRecoveryRascal() = testErrorRecovery(|std:///lang/rascal/syntax/Rascal.rsc|, "Module", |std:///lang/rascal/vis/ImportGraph.rsc|); + +void runLanguageTests() { + testRecoveryC(); + testRecoveryDiff(); + testRecoveryDot(); + testRecoveryJava(); + testRecoveryJson(); + testRecoveryPico(); + testRecoveryRascal(); +} + +void runRascalBatchTest(int maxFiles=1000, int maxFileSize=4000) { + int startTime = realTime(); + TestStats stats = batchRecoveryTest(|std:///lang/rascal/syntax/Rascal.rsc|, "Module", |std:///|, ".rsc", maxFiles, maxFileSize); + int duration = realTime() - startTime; + println(); + println("========================im========================================"); + println("Rascal batch test done in seconds, total result:"); + printStats(stats); +} + +int main(list[str] args) { + int maxFiles = 1000; + int maxFileSize = 4000; + if (size(args) == 2) { + maxFiles = toInt(args[0]); + maxFileSize = toInt(args[1]); + } else if (size(args) != 0) { + println("Usage: ErrorRecoveryBenchmark "); + } + + runRascalBatchTest(maxFiles=maxFiles, maxFileSize=maxFileSize); + return 0; +} \ No newline at end of file diff --git a/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/ListRecoveryTests.rsc b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/ListRecoveryTests.rsc new file mode 100644 index 00000000000..9a0e191c27c --- /dev/null +++ b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/ListRecoveryTests.rsc @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2024, NWO-I Centrum Wiskunde & Informatica (CWI) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + +module lang::rascal::tests::concrete::recovery::ListRecoveryTests + +import ParseTree; + +layout Layout = [\ ]* !>> [\ ]; + +syntax S = T End; + +syntax T = { AB "," }*; +syntax AB = "a" "b"; +syntax End = "$"; + +Tree parseList(str s, bool visualize=false) { + return parser(#S, allowRecovery=true, allowAmbiguity=true)(s, |unknown:///?visualize=<"">|); +} + +test bool listOk() { + return !hasErrors(parseList("a b , a b , a b $", visualize=true)); +} + +test bool listTypo() { + Tree t = parseList("a b, a x, ab $", visualize=true); + return hasErrors(t); +} + +test bool listTypoWs() { + Tree t = parseList("a b , a x , a b $", visualize=true); + return hasErrors(t); +} diff --git a/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/NOTES.md b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/NOTES.md new file mode 100644 index 00000000000..f88771a2e4c --- /dev/null +++ b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/NOTES.md @@ -0,0 +1,836 @@ + +# Error recovery benchmark + +Next to some basic integration tests, this directory contains a simple error recovery benchmark (see `ErrorRecoveryBenchmark.rsc`). +This benchmark is meant to provide feedback on the quality and speed of error recovery and to be able to track progress in these areas. + +The benchmark tests error recovery for a number of languages by performing modifications on a valid input file and attempting to parse the result. +Currently only two types of modifications are implemented: + +- Deletion of single characters +- Deletion until the next end-of-line character + +We anticipate more types of modifications later, possibly based on the parse tree instead of the raw source file. + +## Baseline test + +Below the output of a baseline test is included. This provides a basic idea of what information the benchmark provides and also serves as a comparison for assessing the result of future improvements. + +Note that a benchmark test is documented by a string of characters including: + +- `.`: After modification the input still parses without errors +- '+': After modification error recovery is able to recover by skipping a reasonable amount of input +- '-': After modification error recovery is able to recover by skipping an excessive amount of input (>25% of the contents of the file) +- '?': After modification parsing results in an unrecoverable parse error +- A newline is printed after each line of the input file + +The rest of the output should (hopefully) be self-descriptive: + +``` +$ /usr/bin/env C:\\Program\ Files\\Eclipse\ Adoptium\\jdk-11.0.14.101-hotspot\\bin\\java.exe -agentlib:jdwp=transport=dt_socket,server=n,suspend=y,address=localhost:58567 @C:\\Users\\pieter\\AppData\\Local\\Temp\\cp_89hnnok9nfsf1vtykxy3jiv5n.argfile org.rascalmpl.shell.RascalShell +Windows console mode could not be enabled, errno=1 +Version: Rascal version not specified in META-INF/MANIFEST.MF??? +INFO: detected |lib://rascal| at |file:///D:/rascal/rascal/target/classes/| +rascal>import lang::rascal::tests::concrete::recovery::ErrorRecoveryBenchmark; +☑ Loading modules 🕗 0:00:03.047 +ok +rascal>runLanguageTests(); +☑ Generating parser; MoreParameters Specifier Declarator TypeQualifier TranslationUnit AnonymousIdentifier FunctionDefinition MultiLineCommentBodyToken NonCommaExpression Expression Asterisk Declaration StorageClass StringConstant AbstractDeclarator PrototypeDeclarator GlobalDeclaration Comment Parameter Parameter 🕛 0:00:04.556 +Error recovery of |std:///lang/c90/syntax/C.rsc| (TranslationUnit) on |std:///lang/c90/examples/hello-world.c|, reference parse time: 42 ms. + +Single char deletions: +. +.........+................+-.. +. +...............+..........+.+. +.......++......+++. +.......+....++. +.......++.+++. +-. +. +........?....................+++.+......................................................... +............?. +..++.-.....+.++.?. +...............++++. +..+.++++.?. +.........+.+.....?+. +..?. +. +............+....+?. +?. + +Total parses: 314 (100%) +Successful parses: 262 (83%) +Successful recoveries: 41 (78%) +Failed recoveries: 3 (5%) +Parse errors: 8 (15%) +Slow parses: 0 (0%) + mean median 95% min max +Parse time ratios (log2(ratio)): 0 0 0 0 0 + +Deletes until end-of-line: + +..........+++++++++++++++++-. + +++++++++++++++++++++++?----++ +........++++++++++ +...+++++++++++ +...++++++++++ +- + +?++??++++???????????????????????++........................................................ +...+++++????? +????+++++++++++?? +.....++++++++++++++ +...??+++?? +.....+++++++??????+ +??? + +...?????+++++?????? +? + +Total parses: 295 (100%) +Successful parses: 100 (33%) +Successful recoveries: 126 (64%) +Failed recoveries: 6 (3%) +Parse errors: 63 (32%) +Slow parses: 0 (0%) + mean median 95% min max +Parse time ratios (log2(ratio)): 0 0 0 0 0 + +----------------------------------------------------------- +Total test stats for |std:///lang/c90/examples/hello-world.c|: + +Total parses: 609 (100%) +Successful parses: 362 (59%) +Successful recoveries: 167 (67%) +Failed recoveries: 9 (3%) +Parse errors: 71 (28%) +Slow parses: 0 (0%) + mean median 95% min max +Parse time ratios (log2(ratio)): 0 0 0 0 0 +☑ Generating parser; Range Content Month Date DiffFile Diff Hours Decimal TimeZone DateTime Sign ChunkStart Year Day Line Minutes Seconds Chunk Time Header Indicator FileName 🕛 0:00:00.677 +Error recovery of |std:///lang/diff/unified/UnifiedDiff.rsc| (DiffFile) on |std:///lang/diff/unified/examples/example.diff|, reference parse time: 10 ms. + +Single char deletions: +++++....................................................+ +++++....................................................+ ++++++.++++.++++. ++................................... ++. ++..................... ++......................... ++.................................. ++...................... ++. +----...---...----........................................................... +-.................................................... +-. +-......................................................................................... +-.......................................................................................................... +-.......................................................................................................... +-............................................................................................................................................................................................................................................................................................................................... +-. +-........................... +-.. +-................................................. +????....???......???.................................................................................. +?... +?. +............... +......................................................... +.................................................... +....... +........................................................ +?................................................. +?... +?. +?.............................................................................. +?................................................. +?.................................................................................................................................. +?......................................... +?... +?. +?.? + +Total parses: 1776 (100%) +Successful parses: 1702 (95%) +Successful recoveries: 30 (40%) +Failed recoveries: 21 (28%) +Parse errors: 23 (31%) +Slow parses: 0 (0%) + mean median 95% min max +Parse time ratios (log2(ratio)): 0 0 0 0 0 + +Deletes until end-of-line: +++++.................................................... +++++.................................................... ++++++++++++++++ ++.................................. ++ ++.................... ++........................ ++................................. ++..................... ++ +-----------------.......................................................... +-................................................... +- +-........................................................................................ +-......................................................................................................... +-......................................................................................................... ++.............................................................................................................................................................................................................................................................................................................................. +- +-.......................... +-. +-................................................ +????????????????????................................................................................. +?.. +? +?............. +?....................................................... +?.................................................. +?..... +?...................................................... +?................................................ +?.. +? +?............................................................................. +?................................................ +?................................................................................................................................. +?........................................ +?.. +? +?. + +Total parses: 1737 (100%) +Successful parses: 1643 (94%) +Successful recoveries: 31 (32%) +Failed recoveries: 26 (27%) +Parse errors: 37 (39%) +Slow parses: 0 (0%) + mean median 95% min max +Parse time ratios (log2(ratio)): 0 0 0 0 1 + +----------------------------------------------------------- +Total test stats for |std:///lang/diff/unified/examples/example.diff|: + +Total parses: 3513 (100%) +Successful parses: 3345 (95%) +Successful recoveries: 61 (36%) +Failed recoveries: 47 (27%) +Parse errors: 60 (35%) +Slow parses: 0 (0%) + mean median 95% min max +Parse time ratios (log2(ratio)): 0 0 0 0 1 +☑ Generating parser; DotAttr NodeId Id EdgeStatement EdgeRhs Nod LAYOUT AttrStatement EdgeOp Comment AttrTag DOT Edg StatementList NodeStatement AttrList Statement StatementOptional Subgraph Graph AttrList0 Port 🕛 0:00:01.346 +Error recovery of |std:///lang/dot/syntax/Dot.rsc| (DOT) on |std:///lang/dot/examples/parser-state.dot|, reference parse time: 38 ms. + +Single char deletions: +++??????.......?. ++......+-+.....+++...............+..................+................................................++.. ++......--+.....+++.....+..+.....+++......++.. ++..+-+.....+++............................................+.......+.............................+.......+.....++.. +-....--+.....+++..........................................................................+.......+....++.. ++.....+-+.....+++.................................................................................................................++.. ++..+++.....+++.............................................................++.. ++.....+.++.+..+.. ++....+.++.+.....+.. ++..+.++.+....+.. ++......+.+.+....++.+..+++.....+++.....++.. ++........+++.....+++.......+..+.....+++.......++.. ++......+.+.+....++.+........+++.....+++....++.. ++.........+.+.+.++.+......+.. ++.........+++.....+++.........................................................................................................................................+..+.....+++......++.. ++......+.++.+.........+++.....+++..........++.. ++..............+++.....++++..+.....+++......++.. ++......+.++.+..............+++.....+++................++.. ++.................+++.....+++.....+..+.....+++......+..+.....+++...++.. ++.................+.+.+....++.+..+++.....+++.....++.. ++.................+.+.+....++.+........+-+.....+++....++.. ++......+.++.+.................+-+.....+++...................++.. ++....................+-+.....++++..+.....+++......++.. ++......+.-+.+....................+-+.....+++.......................++.. +-...--+.....+++............................................+....+...................................+....+............+...........+......++.. +-...--+.....+++....................................................................++.. ++.....+++.....+++.............................................................................................++.. ++.....+.++.+..-.. +-...-.++.-.....-.. +-...-.++.-...-.. +-.................-.-.-.-+.-...-.. +-.....--+.....+++.................................................................++.. +-....-++.....+++.................................................+.......+.............................+.......+.....++.. ++....+.++.+....+.. ++.....+.++.+....+.. ++.................+.+.+.++.+.....+.. ++.................+-+.....+++.............+..+.....+++......++.. ++.....+++.....+++.........................................+....................+...............++.. ++....................+.+.+.++.+.....+.. ++...+++.....+++.........................................+.....+.............++.. ++....................+.+.+.++.+...+.. ++....+++.....+++.............................................................++.. ++....................+.+.+.++.+....+.. ++....+++.....+++....................................................+....+...............++.. ++....................+.+.+.-+.+....+.. ++.....+++.....+++..............................................................................................................................................................................................................................................................................................++.. ++....................+.+.+.++.+.....+.. ++.....+++.....+++....................................................+.......+................++.. ++....................+.+.+.++.+.....+.. ++....+++.....+++.....................................................++.. ++....................+.+.+.++.+....+.. ++....+++.....+++................................................++.. ++....................+.+.+.++.+....+.. ++....+++.....+++.........................................+........+..............++.. ++....................+.+.+.+?.+....+.. ++....................+++.....+++.....................................................................+..+.....+++......++.. +?.............................?++.....+++......+..+.....+++++.. +?.............?++.....++++..+.....+++......++.. +?.....?++.....+++......++.. +?......?.+?.?.....?++.....+++..............++.. +?.....?.+?.?.................?++.....+++............++.. +?.....?.+?.?....................?++.....+++.................++.. +?.....?.+?.?.............................?++.....+++....................++.. +?.....?.+?.?.............?++.....++?........??.. +?. + +Total parses: 4177 (100%) +Successful parses: 3493 (83%) +Successful recoveries: 599 (87%) +Failed recoveries: 42 (6%) +Parse errors: 43 (6%) +Slow parses: 0 (0%) + mean median 95% min max +Parse time ratios (log2(ratio)): 0 0 0 0 0 + +Deletes until end-of-line: +???????????????? +.-------.++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++. +.+++++++.++++++++++++++++++++++++++++++++++. +.---.+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++. +.+++++.++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++. +.++++++.++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++. +.+++.++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++. +.++++++..+..+++. +.+++++..+..++++++. +.+++..+..+++++. +.+++++++..++.....+..+++.++++++++++++++++. +.+++++++++.+++++++++++++++++++++++++++++++++++++. +.+++++++..++.+...+..+++++++++.+++++++++++++++. +.++++++++++..++..+..+++++++. +.++++++++++.++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++. +.+++++++..+..++++++++++.+++++++++++++++++++++. +.+++++++++++++++.+++++++++++++++++++++++++++++. +.+++++++..+..+++++++++++++++.+++++++++++++++++++++++++++. +.++++++++++++++++++.+++++++++++++++++++++++++++++++++++++++++++++++++. +.++++++++++++++++++.+++.+...+..+++.++++++++++++++++. +.++++++++++++++++++..++.....+..+++++++++.+++++++++++++++. +.+++++++..+..++++++++++++++++++.++++++++++++++++++++++++++++++. +.+++++++++++++++++++++.+++++++++++++++++++++++++++++. +.-------..+..---------------------.++++++++++++++++++++++++++++++++++. +.----.+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++. +.++++.+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++. +.++++++.++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++. +.------..+..---. +.----..+..------. +.----..+..----. +.------------------..--..+..----. +.------.++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++. +.+++++.++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++. +.+++++..+..+++++. +.++++++..+..+++++. +.++++++++++++++++++..++..+..++++++. +.++++++++++++++++++.++++++++++++++++++++++++++++++++++++++++++. +.++++++.+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++. +.+++++++++++++++++++++..++..+..++++++. +.++++.++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++. +.+++++++++++++++++++++..++..+..++++. +.+++++.++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++. +.+++++++++++++++++++++..++..+..+++++. +.+++++.++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++. +.+++++++++++++++++++++..++..+..+++++. +.++++++.+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++. +.+++++++++++++++++++++..++..+..++++++. +.++++++.++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++. +.+++++++++++++++++++++..++..+..++++++. +.+++++.++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++. +.+++++++++++++++++++++..++..+..+++++. +.+++++.+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++. +.+++++++++++++++++++++..++..+..+++++. +.+++++.++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++. +.+++++++++++++++++++++..++..?..+++++. +.?????????????????????.++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++. +.??????????????????????????????.+++++++++++++++++++++++++++++. +.??????????????.+++++++++++++++++++++++++++++. +.??????.+++++++++++++++++. +.???????..?..??????.+++++++++++++++++++++++++. +.??????..?..??????????????????.+++++++++++++++++++++++. +.??????..?..?????????????????????.++++++++++++++++++++++++++++. +.??????..?..??????????????????????????????.+++++++++++++++++++++++++++++++. +.??????..?????????????????.???????????????????. +? + +Total parses: 4112 (100%) +Successful parses: 340 (8%) +Successful recoveries: 3438 (91%) +Failed recoveries: 99 (2%) +Parse errors: 235 (6%) +Slow parses: 0 (0%) + mean median 95% min max +Parse time ratios (log2(ratio)): 0 0 0 0 0 + +----------------------------------------------------------- +Total test stats for |std:///lang/dot/examples/parser-state.dot|: + +Total parses: 8289 (100%) +Successful parses: 3833 (46%) +Successful recoveries: 4037 (90%) +Failed recoveries: 141 (3%) +Parse errors: 278 (6%) +Slow parses: 0 (0%) + mean median 95% min max +Parse time ratios (log2(ratio)): 0 0 0 0 0 +☑ Generating parser; SwitchBlock CharLiteral BinaryExponent ClassOrInterfaceType VarDec Super VarMod Id Type ClassBody Interfaces Asterisk CommentPart DimExpr ImportDec ArrayAccess PackageDec MethodSpec DeciFloatExponentPart BlockStm EnumConstArgs AbstractMethodDec DeciNumeral AbstractMethodMod StringLiteral Field 🕛 0:00:13.312 +Error recovery of |std:///lang/java/syntax/Java15.rsc| (CompilationUnit) on |jar+file:///D:/rascal/rascal/test/org/rascalmpl/test/data/m3/snakes-and-ladders-project-source.zip!/src/snakes/LastSquare.java|, reference parse time: 118 ms. + +Single char deletions: +++++++++......-. +. +++++++.++++++..........+++++++++.......-. +. +.++++++...........-....+....+....+........+.-. +.......+....+.........++. +.+. +. +.+......... +.++++++.....................++.+. +..++++++.....+. +.?. +?. + +Total parses: 198 (100%) +Successful parses: 131 (66%) +Successful recoveries: 61 (91%) +Failed recoveries: 4 (5%) +Parse errors: 2 (2%) +Slow parses: 0 (0%) + mean median 95% min max +Parse time ratios (log2(ratio)): 0 0 0 0 0 + +Deletes until end-of-line: +.++++++-------- + +---------------------------------------- + +++++++++++++++++++++------------------------- +...+++++++++++++++++++++ +++ + +..+....... +++++++++++++++++++++++++++++++++ +...+++++++++++ +?? +? + +Total parses: 185 (100%) +Successful parses: 16 (8%) +Successful recoveries: 93 (55%) +Failed recoveries: 73 (43%) +Parse errors: 3 (1%) +Slow parses: 0 (0%) + mean median 95% min max +Parse time ratios (log2(ratio)): 0 0 0 0 0 + +----------------------------------------------------------- +Total test stats for |jar+file:///D:/rascal/rascal/test/org/rascalmpl/test/data/m3/snakes-and-ladders-project-source.zip!/src/snakes/LastSquare.java|: + +Total parses: 383 (100%) +Successful parses: 147 (38%) +Successful recoveries: 154 (65%) +Failed recoveries: 77 (32%) +Parse errors: 5 (2%) +Slow parses: 0 (0%) + mean median 95% min max +Parse time ratios (log2(ratio)): 0 0 0 0 0 +☑ Generating parser; Member Object Value NumericLiteral StringChar IntegerLiteral RealLiteral Array JSONText UnicodeEscape StringLiteral 🕛 0:00:01.228 +Error recovery of |std:///lang/json/syntax/JSON.rsc| (JSONText) on |std:///lang/json/examples/ex01.json|, reference parse time: 17 ms. + +Single char deletions: +?.. +......?.....??.?.. +..........+.....++..+..?.. +..........+......++.+..?.. +..........+.....++..+....................??.. +..........?.........??.?.. +..............+...++....+......................................++.. +..............+......-+....+.. +..............-.....+-..+...-.. +..........?+.. +..........?...??.?...+....+....+......?.. +......?.. +? +Total parses: 337 (100%) +Successful parses: 285 (84%) +Successful recoveries: 27 (51%) +Failed recoveries: 4 (7%) +Parse errors: 21 (40%) +Slow parses: 0 (0%) + mean median 95% min max +Parse time ratios (log2(ratio)): 0 0 0 0 0 + +Deletes until end-of-line: +?. +????????????????. +...........+++++++??????. +...........++++++++?????. +...........????????????????????????????????. +????????????????????????. +...............++++++++++++++++++++++++++++++++++++++++++++++++++. +...............--------+++++. +-----------------------------. +-----------+. +??????????????????+????????????????????. +???????. + +Total parses: 324 (100%) +Successful parses: 75 (23%) +Successful recoveries: 72 (28%) +Failed recoveries: 48 (19%) +Parse errors: 129 (51%) +Slow parses: 0 (0%) + mean median 95% min max +Parse time ratios (log2(ratio)): 0 0 0 0 0 + +----------------------------------------------------------- +Total test stats for |std:///lang/json/examples/ex01.json|: + +Total parses: 661 (100%) +Successful parses: 360 (54%) +Successful recoveries: 99 (32%) +Failed recoveries: 52 (17%) +Parse errors: 150 (49%) +Slow parses: 0 (0%) + mean median 95% min max +Parse time ratios (log2(ratio)): 0 0 0 0 0 +☑ Generating parser; Type Id WhitespaceAndComment Statement Natural Program Declarations String Expression IdType 🕛 0:00:01.139 +Error recovery of |std:///lang/pico/syntax/Main.rsc| (Program) on |std:///lang/pico/examples/fac.pico|, reference parse time: 16 ms. + +Single char deletions: +?????.-------.......+.+++++++-... +.....................+.++++++++....... +....................+.++++++++. +..................+.++++++++. +..............+..+.+++++++. +..............+..+.------+. +............++...+. +.............++.+-. +......-----.......+.-.--.. +..............++.......+. +................++......-. +..........+++++.......+.+.++. +....................++........+....+. +...................++.......+.+. +..........+++. +................??.......+.+. +......--. +???. + +Total parses: 454 (100%) +Successful parses: 337 (74%) +Successful recoveries: 81 (69%) +Failed recoveries: 26 (22%) +Parse errors: 10 (8%) +Slow parses: 0 (0%) + mean median 95% min max +Parse time ratios (log2(ratio)): 0 0 0 0 0 + +Deletes until end-of-line: +?????--------..++++++++++++++-.. +...............++++++++++++++++...... +...............+++++++++++++++ +...............+++++++++++++ +...............+++++++++++ +-------------------------+ +.......+++++++++++ +.......----------- ++++++++-----------------. +...........+++++++++++++ +...........+++++++------- +++++++++++++++++++++++++++++ +..............++++++++++++++++++++++ ++++++++++++++++++++++++......++ +------------+ +??????????????????++......++ ++++++++- +??? + +Total parses: 436 (100%) +Successful parses: 133 (30%) +Successful recoveries: 195 (64%) +Failed recoveries: 82 (27%) +Parse errors: 26 (8%) +Slow parses: 0 (0%) + mean median 95% min max +Parse time ratios (log2(ratio)): 0 0 0 0 0 + +----------------------------------------------------------- +Total test stats for |std:///lang/pico/examples/fac.pico|: + +Total parses: 890 (100%) +Successful parses: 470 (52%) +Successful recoveries: 276 (65%) +Failed recoveries: 108 (25%) +Parse errors: 36 (8%) +Slow parses: 0 (0%) + mean median 95% min max +Parse time ratios (log2(ratio)): 0 0 0 0 0 +Error recovery of |std:///lang/rascal/syntax/Rascal.rsc| (Module) on |std:///lang/rascal/vis/ImportGraph.rsc|, reference parse time: 17 ms. + +Single char deletions: +?........+...............................................................................................?. +?...........+. +...................................................................................................... +.................................................................................................... +................................................................................................................ +........................................................................................... +?. +?........+. +.................................................................................................... +............................................................................ +............................................................................................... +.................... +?. +?........+. +................................................................................................. +?. +?...............? +???????....++......++...++............ +. +??????+....++..........+. +??????+...++......+. +??????+....++......++.......++..........++.......+. +??????+....++......+++......++......+. +??????+.........+. +??????+....++..........+. +??????+....++...........+. ++++++++..+. ++++++++........++......++.....-. +. ++........+..............................................................................................?. +....?...........?.....................+..................+.?. +...............+!+.......+++............++.............................++!. ++. +. ++........?......................................................................................?. +....?...........?.....................+..................+.?. +....++...................................................................................... +.........+.....................+!................+.........++++. +...............+!.................................++. ++. +. ++........?.................................................................................................?. +....?...........?.....................+..................-!.?. +....+!.+................+.........+?. +..... +....++...........................................................................+ +....+!.+!.+.+....+.+.++.?.+.+.+......?.+....+.......++...............++..?.++++++++.........+. +......+.+.+....+.+.++.?.+.+.+......?.+....+.......++...............++..?.++++++++.........+. +....++........................................................................................ +....++...........................................................................................................................................+ +......+. +. +.......+.........+........+.+. +........++.+....+.++.+......+....?+. +....................+. +........++++.. +............++++++.+.......+...++. +....+. +. +....+++++++....+.........+......++.+.+.......+...++. +. +..........................+.....+...+.........................+++...........+.........+.......++..........................++..........+................+?++................+++++. ++. +. ++........+........................................................................................?. ++++++.............+.............+. +.......+...+............+.+.+++. +.......+...+...............+.+++. +.......+...+....+.......+.+.+++. +.......+...+....+.......+.+.+++. +.......+...+....+.......+.++. ++?. +. +?........?..........................................................................................?. +............?...............?....+...+.....+.?. +.............+!.+.....+...+.+...++..............++. +..... +...........+.+...............+.+...+............++. +. +...............?.............+. +..............+.+.+.+.......+...+..........++. +..............+.+.+.+.......+...+..........++. +..............+.+.+.+.......+...+..........++. +................+.+.+...........+..........+. +....++. +. +........................+..................+++..+..................+.+..+..................++. +. +.....................+. +?. +. +?........?.............................................................?. +............?...............?........?.?. +....+++.?. +.................?............+....++. +. +........+....+.....+.....+.+.................+.++. +. +........++++++.............+. +..................+.+.+....++. +..................+.+.+....+.+.....+. +..................+.+.+....+.+.....+. +....................+.++....+.....++. +........+?. +....?. +....++++++..........+.+.+.. +...........................++?. +? +Total parses: 4314 (100%) +Successful parses: 3815 (88%) +Successful recoveries: 392 (78%) +Failed recoveries: 2 (0%) +Parse errors: 105 (21%) +Slow parses: 9 (1%) + mean median 95% min max +Parse time ratios (log2(ratio)): 0 0 2 0 5 + +Deletes until end-of-line: +.+........???????????????????????????????????????????????????????????????????????????????????????????????? +?++++++++++++ +..................................................................................................... +................................................................................................... +............................................................................................................... +.......................................................................................... +? +?+++++++++ +................................................................................................... +........................................................................... +.............................................................................................. +................... +? +?+++++++++ +................................................................................................ +? +.?.............. +????????....+?......+?...+?.......... + +.?????++++++++++++++++++ +.?????+++++++++++++ +.?????++++++++++++++++++++++++++++++++++++++++++++ +.?????+++++++++++++++++++++++++++++++ +.?????+++++++++++ +.?????++++++++++++++++++ +.?????+++++++++++++++++++ +.+++++++++ +.+++++------------------------- + +.+........??????????????????????????????????????????????????????????????????????????????????????????????? +++++++???????????+++++++++++++++++++++++++++++++++++++++++?? +.....+!+!+!+!+!++!+!+!+!+!++++++++++++++++++++++++?+++++++++++++++++++++++++++++++! ++ + +.+........??????????????????????????????????????????????????????????????????????????????????????? +++++++???????????+++++++++++++++++++++++++++++++++++++++++?? +.....+..................................................................................... +.....+++++..++++++++++++++++++++++++++++++++++++++++++++++++++ +.....+!+!+!+!+!++!+!+!+!+!+++++++++++++!++++++++++++++++++++++ ++ + +.+........?????????????????????????????????????????????????????????????????????????????????????????????????? +??????????????????-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!??-!-!-!-!-!?????????????-!----!?? +.........???????????????++++++++++? +.... +.....+........................................................................... ++++++..++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +.........++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +.....+....................................................................................... +.....+........................................................................................................................................... ++++++++ + +?????----++-+++++++++++++++++ ++++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++ +.........+++. ++++++++++++++?????+++????????+?++ ++++++ + +.....+++++++++++++++++++++++++++++++..+++++++++++++ + +.....+!+!+!+!+!+!+!+!+!+!+!+!+!+!+!+!+!+!+!+!+!+!++++++????+????????????????????????????????+????????????????+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++?+????????????????????++ ++ + +.+........????????????????????????????????????????????????????????????????????????????????????????? ++++++++++++++++++++++++++++++++++ +........++++.........+..+..++++ +.....+++++++++++++++++++++++++++ +.....++++++++++++++++++++++++++ +.....++++++++++++++++++++++++++ +++++++++++++++++++++++++++++ +?? + +.?........??????????????????????????????????????????????????????????????????????????????????????????? +?????????????????????????????+++++++++++?????? +.......+........?++++++++++++++++++++++++++++++++ +.... +.......+......????????????????????++++++++++++++++ + +++++++++++++++++++++++++++++++ +...........+..+..++?+++++++++++++++++++++++++ +.........++++++++++?+++++++++++++++++++++++++ +.........++++++++++?+++++++++++++++++++++++++ +++++++++++++++++++++++++++++++++++++++++++++ +++++++ + +.....++++++++++++++++++++..++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +.....+++++++++++++++++ +? + +.?........?????????????????????????????????????????????????????????????? +???????????????????????????????????????? +????????? +.........++++++++++++++++++++++++++++ + +.........+++++++++++++++++++..+++++++++++++++++++ + +++++++++++++++++++++++++++++ +...............+..+..++?+++++ +.............++++++++++?++++++++++++ +.............++++++++++?++++++++++++ +++++++++++++++++++++++++++++++++++++ +?????????? +????? ++++++++++++++++++++++++++. +??????????????++????????????+? + +Total parses: 4206 (100%) +Successful parses: 1542 (36%) +Successful recoveries: 1610 (60%) +Failed recoveries: 55 (2%) +Parse errors: 999 (37%) +Slow parses: 66 (2%) + mean median 95% min max +Parse time ratios (log2(ratio)): 0 0 2 0 5 + +----------------------------------------------------------- +Total test stats for |std:///lang/rascal/vis/ImportGraph.rsc|: + +Total parses: 8520 (100%) +Successful parses: 5357 (62%) +Successful recoveries: 2002 (63%) +Failed recoveries: 57 (1%) +Parse errors: 1104 (34%) +Slow parses: 75 (2%) + mean median 95% min max +Parse time ratios (log2(ratio)): 0 0 2 0 5 +ok +``` diff --git a/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/NestedRecoveryTests.rsc b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/NestedRecoveryTests.rsc new file mode 100644 index 00000000000..528c20d9eb3 --- /dev/null +++ b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/NestedRecoveryTests.rsc @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2024, NWO-I Centrum Wiskunde & Informatica (CWI) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + +module lang::rascal::tests::concrete::recovery::NestedRecoveryTests + +import ParseTree; + +layout Layout = [\ ]* !>> [\ ]; + +syntax S = T; + +syntax T = A B C; + +syntax A = "a"; +syntax B = "b" "b"; +syntax C = "c"; + +private Tree parseS(str input, bool visualize=false) + = parser(#S, allowRecovery=true, allowAmbiguity=true)(input, |unknown:///?visualize=<"">|); + +test bool nestedOk() { + return !hasErrors(parseS("a b b c")); +} + +test bool nestedTypo() { + Tree t = parseS("a b x c"); + return getErrorText(findFirstError(defaultErrorDisambiguationFilter(t))) == "x "; +} diff --git a/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/PicoRecoveryTests.rsc b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/PicoRecoveryTests.rsc new file mode 100644 index 00000000000..564b8840eec --- /dev/null +++ b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/PicoRecoveryTests.rsc @@ -0,0 +1,116 @@ +/** + * Copyright (c) 2024, NWO-I Centrum Wiskunde & Informatica (CWI) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + +module lang::rascal::tests::concrete::recovery::PicoRecoveryTests + +import lang::pico::\syntax::Main; + +import ParseTree; + +import IO; +import String; + +Tree parsePico(str input, bool visualize=false) + = parser(#Program, allowRecovery=true, allowAmbiguity=true)(input, |unknown:///?visualize=<"">|); + +bool checkError(Tree t, str expectedError) { + str bestError = getErrorText(findBestError(t)); + println("best error: , expected: "); + return size(bestError) == size(expectedError); +} + +test bool picoOk() { + t = parsePico("begin declare input : natural, + output : natural, + repnr : natural, + rep : natural; + input := 14; + output := 0; + while input - 1 do + rep := output; + repnr := input; + while repnr - 1 do + output := output + rep; + repnr := repnr - 1 + od; + input := input - 1 + od +end"); + return !hasErrors(t); +} + +test bool picoTypo() { + t = parsePico("begin declare input : natural, + output : natural, + repnr : natural, + rep : natural; + input := 14; + output := 0; + while input - 1 do + rep := output; + repnr := input; + while repnr - 1 do + output := output x rep; + repnr := repnr - 1 + od; + input := input - 1 + od +end"); + + return checkError(t, "output x rep"); +} + +test bool picoMissingSemi() { + t = parsePico("begin declare input : natural, + output : natural, + repnr : natural, + rep : natural; + input := 14; + output := 0; + while input - 1 do + rep := output; + repnr := input; + while repnr - 1 do + output := output + rep; + repnr := repnr - 1 + od + input := input - 1 + od +end"); + return checkError(t, "input := input - 1\n od"); +} + +test bool picoTypoSmall() { + t = parsePico( +"begin declare; + while input do + input x= 14; + output := 0 + od +end"); + + return checkError(t, "x= 14"); +} + +test bool picoMissingSemiSmall() { + t = parsePico( +"begin declare; + while input do + input := 14 + output := 0 + od +end"); + + return checkError(t, "output := 0\n od"); +} \ No newline at end of file diff --git a/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/RascalRecoveryTests.rsc b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/RascalRecoveryTests.rsc new file mode 100644 index 00000000000..9e0332422c3 --- /dev/null +++ b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/RascalRecoveryTests.rsc @@ -0,0 +1,163 @@ +/** + * Copyright (c) 2024, NWO-I Centrum Wiskunde & Informatica (CWI) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + +module lang::rascal::tests::concrete::recovery::RascalRecoveryTests + +import lang::rascal::\syntax::Rascal; + +import ParseTree; +import IO; + +Tree parseRascal(type[&T] t, str input, bool visualize=false) { + Tree result = parser(t, allowRecovery=true, allowAmbiguity=true)(input, |unknown:///?visualize=<"">|); + list[Tree] errors = findAllErrors(result); + if (errors != []) { + println("Tree has errors"); + for (error <- errors) { + println("- "); + } + + Tree disambiguated = defaultErrorDisambiguationFilter(result); + println("Best error: "); + } + + return result; +} + +Tree parseRascal(str input, bool visualize=false) = parseRascal(#start[Module], input, visualize=visualize); + +Tree parseFunctionDeclaration(str input, bool visualize=false) = parseRascal(#FunctionDeclaration, input, visualize=visualize); + +Tree parseStatement(str input, bool visualize=false) = parseRascal(#Statement, input, visualize=visualize); + +test bool rascalOk() { + Tree t = parseRascal(" + module A + + int inc(int i) { + return i+1; + } + "); + return !hasErrors(t); +} + +test bool rascalFunctionDeclarationOk() { + Tree t = parseFunctionDeclaration("void f(){}"); + return !hasErrors(t); +} + + +test bool rascalModuleFollowedBySemi() { + Tree t = parseRascal(" + module A + ; + "); + + // There are a lot of productions in Rascal that have a ; as terminator. + // The parser assumes the user has only entered the ; on one of them, + // so the error list contains them all. + list[Tree] errors = findAllErrors(t); + assert size(errors) == 10; + + return getErrorText(findFirstError(t)) == ";"; +} + +test bool rascalOperatorTypo() { + Tree t = parseRascal(" + module A + + int f() = 1 x 1; + "); + + return getErrorText(findFirstError(t)) == "x 1;"; +} + +test bool rascalIllegalStatement() { + Tree t = parseRascal("module A void f(){a}"); + return getErrorText(findFirstError(t)) == "a}"; +} + +test bool rascalMissingCloseParen() { + Tree t = parseRascal("module A void f({} void g(){}"); + + assert getErrorText(findFirstError(t)) == "void g("; + assert getErrorText(findFirstError(defaultErrorDisambiguationFilter(t))) == "("; + + return true; +} + +test bool rascalFunctionDeclarationMissingCloseParen() { + Tree t = parseFunctionDeclaration("void f({} void g() {}"); + + assert getErrorText(findFirstError(t)) == "void g("; + + Tree error = findFirstError(defaultErrorDisambiguationFilter(t)); + assert getErrorText(error) == "("; + loc location = getSkipped(error).src; + assert location.begin.column == 16 && location.length == 1; + + return true; +} + +test bool rascalIfMissingExpr() { + Tree t = parseFunctionDeclaration("void f(){if(){1;}}", visualize=false); + return getErrorText(findFirstError(t)) == ")"; +} + +test bool rascalIfBodyEmpty() { + Tree t = parseRascal("module A void f(){1;} void g(){if(1){}} void h(){1;}"); + + println("error: "); + assert getErrorText(findBestError(t)) == "} void h(){1"; + + return true; +} + +// Not working yet: +/* +test bool rascalMissingOpeningParen() { + Tree t = parseRascal("module A void f){} void g() { }"); + + println("error text: "); + return getErrorText(findFirstError(t)) == "a}"; +} + +test bool rascalFunFunMissingCloseParen() { + Tree t = parseRascal("module A void f(){void g({}} void h(){}"); + + println("error text: "); + return getErrorText(findFirstError(t)) == "a}"; +} + +test bool rascalIfMissingOpeningParen() { + Tree t = parseRascal("module A void f(){if 1){}}", visualize=false); + + println("error text: "); + return getErrorText(findFirstError(t)) == ";"; +} + +test bool rascalIfMissingCloseParen() { + Tree t = parseRascal("module A void f(){if(1{}}", visualize=false); + + println("error text: "); + return getErrorText(findFirstError(t)) == ";"; +} + +test bool rascalIfMissingSemi() { + Tree t = parseRascal("module A void f(){if (true) {a}}"); + + println("error text: "); + return getErrorText(findFirstError(t)) == ";"; +} +*/ \ No newline at end of file diff --git a/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/RecoveryTestSupport.rsc b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/RecoveryTestSupport.rsc new file mode 100644 index 00000000000..ac73a8f53f9 --- /dev/null +++ b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/RecoveryTestSupport.rsc @@ -0,0 +1,373 @@ +module lang::rascal::tests::concrete::recovery::RecoveryTestSupport + +import lang::rascal::\syntax::Rascal; +import ParseTree; +import String; +import IO; +import util::Benchmark; +import Grammar; +import analysis::statistics::Descriptive; +import util::Math; +import Set; +import List; + +import lang::rascal::grammar::definition::Modules; + +alias FrequencyTable = map[int val, int count]; + +public data TestMeasurement(loc source=|unknown:///|, int duration=0) = successfulParse() | recovered(int errorSize=0) | parseError(); +public data FileStats = fileStats(int totalParses = 0, int successfulParses=0, int successfulRecoveries=0, int failedRecoveries=0, int parseErrors=0, int slowParses=0, FrequencyTable parseTimeRatios=()); + +public data TestStats = testStats(int filesTested=0, int testCount=0, FrequencyTable successfulParsePct=(), FrequencyTable successfulRecoveryPct=(), FrequencyTable failedRecoveryPct=(), FrequencyTable parseErrorPct=(), FrequencyTable slowParsePct=(), FrequencyTable parseTimeRatios=()); + +private TestMeasurement testRecovery(&T (value input, loc origin) standardParser, &T (value input, loc origin) recoveryParser, str input, loc source) { + int startTime = 0; + int duration = 0; + TestMeasurement measurement = successfulParse(); + try { + startTime = realTime(); + Tree t = standardParser(input, source); + duration = realTime() - startTime; + measurement = successfulParse(source=source, duration=duration); + } catch ParseError(_): { + startTime = realTime(); + try { + Tree t = recoveryParser(input, source); + duration = realTime() - startTime; + Tree best = findBestError(t); + errorSize = size(getErrorText(best)); + measurement = recovered(source=source, duration=duration, errorSize=errorSize); + } catch ParseError(_): { + duration = realTime() - startTime; + measurement = parseError(source=source, duration=duration); + } + } + + return measurement; +} + +FileStats updateStats(FileStats stats, TestMeasurement measurement, int referenceParseTime, int recoverySuccessLimit) { + stats.totalParses += 1; + + int ratio = referenceParseTime == 0 ? measurement.duration : measurement.duration/referenceParseTime; + int parseTimeRatio = ratio == 0 ? 0 : round(log2(ratio)); + + switch (measurement) { + case successfulParse(): { + print("."); + stats.successfulParses += 1; + } + case recovered(errorSize=errorSize): { + stats.parseTimeRatios = increment(stats.parseTimeRatios, parseTimeRatio); + if (errorSize <= recoverySuccessLimit) { + print("+"); + stats.successfulRecoveries += 1; + } else { + print("-"); + stats.failedRecoveries += 1; + } + } + case parseError(): { + stats.parseTimeRatios = increment(stats.parseTimeRatios, parseTimeRatio); + print("?"); + stats.parseErrors += 1; + } + } + + if (measurement.duration > referenceParseTime*10) { + print("!"); + stats.slowParses += 1; + } + + return stats; +} + +FileStats mergeFileStats(FileStats stats1, FileStats stats2) { + return fileStats( + totalParses = stats1.totalParses + stats2.totalParses, + successfulParses = stats1.successfulParses + stats2.successfulParses, + successfulRecoveries = stats1.successfulRecoveries + stats2.successfulRecoveries, + failedRecoveries = stats1.failedRecoveries + stats2.failedRecoveries, + parseErrors = stats1.parseErrors + stats2.parseErrors, + slowParses = stats1.slowParses + stats2.slowParses, + parseTimeRatios = mergeFrequencyTables(stats1.parseTimeRatios, stats2.parseTimeRatios) + ); +} + +FrequencyTable increment(FrequencyTable frequencyTable, int val) { + if (val in frequencyTable) { + frequencyTable[val] += 1; + } else { + frequencyTable[val] = 1; + } + + return frequencyTable; +} + +TestStats consolidateStats(TestStats cumulativeStats, FileStats fileStats) { + int totalFailed = fileStats.totalParses - fileStats.successfulParses; + + cumulativeStats.successfulParsePct = increment(cumulativeStats.successfulParsePct, percentage(fileStats.successfulParses, fileStats.totalParses)); + cumulativeStats.successfulRecoveryPct = increment(cumulativeStats.successfulRecoveryPct, percentage(fileStats.successfulRecoveries, totalFailed)); + cumulativeStats.failedRecoveryPct = increment(cumulativeStats.failedRecoveryPct, percentage(fileStats.failedRecoveries, totalFailed)); + cumulativeStats.parseErrorPct = increment(cumulativeStats.parseErrorPct, percentage(fileStats.parseErrors, totalFailed)); + cumulativeStats.slowParsePct = increment(cumulativeStats.slowParsePct, percentage(fileStats.slowParses, totalFailed)); + cumulativeStats.parseTimeRatios = mergeFrequencyTables(cumulativeStats.parseTimeRatios, fileStats.parseTimeRatios); + + cumulativeStats.filesTested += 1; + cumulativeStats.testCount += fileStats.totalParses; + + return cumulativeStats; +} + +map[int,int] mergeFrequencyTables(map[int,int] hist1, map[int,int] hist2) { + for (int pct <- hist2) { + if (pct in hist1) { + hist1[pct] += hist2[pct]; + } else { + hist1[pct] = hist2[pct]; + } + } + + return hist1; +} + +TestStats mergeStats(TestStats stats, TestStats stats2) { + stats.filesTested += stats2.filesTested; + stats.testCount += stats2.testCount; + stats.successfulParsePct = mergeFrequencyTables(stats.successfulParsePct, stats2.successfulParsePct); + stats.successfulRecoveryPct = mergeFrequencyTables(stats.successfulRecoveryPct, stats2.successfulRecoveryPct); + stats.failedRecoveryPct = mergeFrequencyTables(stats.failedRecoveryPct, stats2.failedRecoveryPct); + stats.parseErrorPct = mergeFrequencyTables(stats.parseErrorPct, stats2.parseErrorPct); + stats.slowParsePct = mergeFrequencyTables(stats.slowParsePct, stats2.slowParsePct); + stats.parseTimeRatios = mergeFrequencyTables(stats.parseTimeRatios, stats2.parseTimeRatios); + + return stats; +} + +FileStats testSingleCharDeletions(&T (value input, loc origin) standardParser, &T (value input, loc origin) recoveryParser, str input, int referenceParseTime, int recoverySuccessLimit) { + FileStats stats = fileStats(); + int len = size(input); + int i = 0; + + while (i < len) { + str modifiedInput = substring(input, 0, i) + substring(input, i+1); + TestMeasurement measurement = testRecovery(standardParser, recoveryParser, modifiedInput, |unknown:///?deleted=<"">|); + stats = updateStats(stats, measurement, referenceParseTime, recoverySuccessLimit); + if (i < len && substring(input, i, i+1) == "\n") { + println(); + } + i = i+1; + } + + return stats; +} + +FileStats testDeleteUntilEol(&T (value input, loc origin) standardParser, &T (value input, loc origin) recoveryParser, str input, int referenceParseTime, int recoverySuccessLimit, int begin=0, int end=-1) { + FileStats stats = fileStats(); + int lineStart = begin; + list[int] lineEndings = findAll(input, "\n"); + + for (int lineEnd <- lineEndings) { + lineLength = lineEnd - lineStart; + for (int pos <- [lineStart..lineEnd]) { + + // Check boundaries (only used for quick bug testing) + if (end != -1 && end < pos) { + return stats; + } + if (pos < begin) { + continue; + } + + modifiedInput = substring(input, 0, pos) + substring(input, lineEnd); + TestMeasurement measurement = testRecovery(standardParser, recoveryParser, modifiedInput, |unknown:///?deletedUntilEol=<",">|); + stats = updateStats(stats, measurement, referenceParseTime, recoverySuccessLimit); + } + lineStart = lineEnd+1; + println(); + } + + return stats; +} + +private int percentage(int number, int total) { + return total == 0 ? 0 : (100*number)/total; +} + +int statLabelWidth = 40; +int statFieldWidth = 7; + + +void printFileStats(FileStats fileStats) { + void printStat(str label, int stat, int total, bool printPct=true) { + int pct = total == 0 ? 0 : stat*100/total; + print(left(label + ":", statLabelWidth)); + str pctStr = printPct ? " (%)" : ""; + println(left("", statFieldWidth)); + } + + println(); + printStat("Total parses", fileStats.totalParses, fileStats.totalParses); + printStat("Successful parses", fileStats.successfulParses, fileStats.totalParses); + int failedParses = fileStats.totalParses - fileStats.successfulParses; + printStat("Successful recoveries", fileStats.successfulRecoveries, failedParses); + printStat("Failed recoveries", fileStats.failedRecoveries, failedParses); + printStat("Parse errors", fileStats.parseErrors, failedParses); + printStat("Slow parses", fileStats.slowParses, failedParses); + printFrequencyTableHeader(); + printFrequencyTableStats("Parse time ratios", fileStats.parseTimeRatios, unit = "log2(ratio)"); +} + +void printFrequencyTableHeader() { + print(left("", statLabelWidth+1)); + print(right("mean", statFieldWidth)); + print(right("median", statFieldWidth)); + print(right("95%", statFieldWidth)); + print(right("min", statFieldWidth)); + println(right("max", statFieldWidth)); +} + +void printFrequencyTableStats(str label, FrequencyTable frequencyTable, str unit = "%") { + print(left(label + " ():", statLabelWidth)); + + int totalCount = (0 | it+frequencyTable[val] | val <- frequencyTable); + + int total = 0; + int median = 0; + int medianCount = 0; + int cumulativeCount = 0; + int ninetyFivePercentileLimit = round(totalCount * 0.95); + int ninetyFivePercentile = -1; + int minVal = 1000000000; + int maxVal = -1000000000; + + for (val <- sort(toList(frequencyTable.val))) { + minVal = min(minVal, val); + maxVal = max(maxVal, val); + + int count = frequencyTable[val]; + cumulativeCount += count; + + if (ninetyFivePercentile == -1 && cumulativeCount >= ninetyFivePercentileLimit) { + ninetyFivePercentile = val; + } + + total += val*count; + + if (count > medianCount) { + medianCount = count; + median = val; + } + } + + if (totalCount == 0) { + print("-"); + } else { + int mean = total/totalCount; + print(right("", statFieldWidth)); + print(right("", statFieldWidth)); + print(right("", statFieldWidth)); + print(right("", statFieldWidth)); + println(right("", statFieldWidth)); + } +} + +void printStats(TestStats stats) { + if (stats.filesTested != 1) { + println("Files tested: "); + } + println("Total parses: "); + printFrequencyTableHeader(); + printFrequencyTableStats("Succesful parses", stats.successfulParsePct); + printFrequencyTableStats("Succesful recoveries", stats.successfulRecoveryPct); + printFrequencyTableStats("Failed recoveries", stats.failedRecoveryPct); + printFrequencyTableStats("Parse errors", stats.parseErrorPct); + printFrequencyTableStats("Slow parses", stats.slowParsePct); + printFrequencyTableStats("Parse time ratios", stats.parseTimeRatios, unit = "log2/%"); + + println(); +} + +private str syntaxLocToModuleName(loc syntaxFile) { + str path = replaceLast(substring(syntaxFile.path, 1), ".rsc", ""); + return replaceAll(path, "/", "::"); +} + +loc zippedFile(str zip, str path) { + loc res = getResource(zip); + loc zipFile = res[scheme="jar+"][path=res.path + "!/"]; + return zipFile + path; +} + +FileStats testErrorRecovery(loc syntaxFile, str topSort, loc testInput) = testErrorRecovery(syntaxFile, topSort, testInput, readFile(testInput)); + +FileStats testErrorRecovery(loc syntaxFile, str topSort, loc testInput, str input) { + Module \module = parse(#start[Module], syntaxFile).top; + str modName = syntaxLocToModuleName(syntaxFile); + Grammar gram = modules2grammar(modName, {\module}); + + if (sym:\start(\sort(topSort)) <- gram.starts) { + type[value] begin = type(sym, gram.rules); + standardParser = parser(begin, allowAmbiguity=true, allowRecovery=false); + recoveryParser = parser(begin, allowAmbiguity=true, allowRecovery=true); + + int startTime = realTime(); + standardParser(input, testInput); + int referenceParseTime = realTime() - startTime; + + recoverySuccessLimit = size(input)/4; + + println("Error recovery of () on , reference parse time: ms."); + + println(); + println("Single char deletions:"); + FileStats singleCharDeletionStats = testSingleCharDeletions(standardParser, recoveryParser, input, referenceParseTime, recoverySuccessLimit); + printFileStats(singleCharDeletionStats); + + println(); + println("Deletes until end-of-line:"); + FileStats deleteUntilEolStats = testDeleteUntilEol(standardParser, recoveryParser, input, referenceParseTime, recoverySuccessLimit); + printFileStats(deleteUntilEolStats); + + FileStats stats = mergeFileStats(singleCharDeletionStats, deleteUntilEolStats); + println(); + println("-----------------------------------------------------------"); + println("Total test stats for :"); + printFileStats(stats); + return stats; + } else { + throw "Cannot find top sort in "; + } +} + +TestStats batchRecoveryTest(loc syntaxFile, str topSort, loc dir, str ext, int maxFiles, int maxFileSize, TestStats cumulativeStats=testStats()) { + println("Batch testing in directory (maxFiles=, maxFileSize=)"); + for (entry <- listEntries(dir)) { + loc file = dir + entry; + if (isFile(file)) { + if (endsWith(file.path, ext)) { + str content = readFile(file); + if (size(content) <= maxFileSize) { + println("========================================================================"); + println("Testing file # ( of left)"); + FileStats fileStats = testErrorRecovery(syntaxFile, topSort, file, content); + cumulativeStats = consolidateStats(cumulativeStats, fileStats); + println(); + println("------------------------------------------------------------------------"); + println("Cumulative stats after testing :"); + printStats(cumulativeStats); + } + } + } else if (isDirectory(file)) { + cumulativeStats = batchRecoveryTest(syntaxFile, topSort, file, ext, maxFiles, maxFileSize, cumulativeStats=cumulativeStats); + } + + if (cumulativeStats.filesTested >= maxFiles) { + break; + } + } + + return cumulativeStats; +} + diff --git a/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/ToyRascal.rsc b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/ToyRascal.rsc new file mode 100644 index 00000000000..4a60dfb88e4 --- /dev/null +++ b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/ToyRascal.rsc @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2024, NWO-I Centrum Wiskunde & Informatica (CWI) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + +module lang::rascal::tests::concrete::recovery::ToyRascal + +start syntax FunctionDeclaration = Signature FunctionBody; + +syntax Signature = Name Parameters; + +syntax Name = "f" | "g"; + +syntax Parameters = "(" ")"; + +syntax FunctionBody = "{" Statement* statements "}" ; + +syntax Statement + = "{" Statement+ statements "}" + | "if" "(" { Expression "," }+ ")" Statement + | "s" ";"; + +syntax Expression = "1"; + +syntax Label + = "l" ":" + | ; + +layout Layout = [ \n\r\t]*; diff --git a/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/ToyRascalRecoveryTests.rsc b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/ToyRascalRecoveryTests.rsc new file mode 100644 index 00000000000..75c7f478996 --- /dev/null +++ b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/ToyRascalRecoveryTests.rsc @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2024, NWO-I Centrum Wiskunde & Informatica (CWI) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + +module lang::rascal::tests::concrete::recovery::ToyRascalRecoveryTests + +import lang::rascal::tests::concrete::recovery::ToyRascal; + +import ParseTree; +import IO; + +Tree parseToyRascal(str input, bool visualize=false) { + Tree result = parser(#start[FunctionDeclaration], allowRecovery=true, allowAmbiguity=true)(input, |unknown:///?visualize=<"">|); + list[Tree] errors = findAllErrors(result); + if (errors != []) { + println("Tree has errors"); + for (error <- errors) { + println("- "); + } + + Tree disambiguated = defaultErrorDisambiguationFilter(result); + println("Best error: "); + } + + return result; +} + +test bool toyRascalOk() { + Tree t = parseToyRascal("f(){s;}"); + return !hasErrors(t); +} + +test bool toyRascalMissingOpenParen() { + Tree t = parseToyRascal("f){}", visualize=true); + return hasErrors(t) && getErrorText(findBestError(t)) == ")"; +} + +test bool toyRascalMissingCloseParen() { + Tree t = parseToyRascal("f({}", visualize=true); + return hasErrors(t) && getErrorText(findBestError(t)) == "("; +} + +test bool toyRascalMissingIfBody() { + Tree t = parseToyRascal("f(){if(1){}}", visualize=true); + return hasErrors(t) && getErrorText(findBestError(t)) == "}"; +} \ No newline at end of file diff --git a/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/bugs/InfiniteLoop2Bug.rsc b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/bugs/InfiniteLoop2Bug.rsc new file mode 100644 index 00000000000..4beae613ec8 --- /dev/null +++ b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/bugs/InfiniteLoop2Bug.rsc @@ -0,0 +1,14 @@ +module lang::rascal::tests::concrete::recovery::bugs::InfiniteLoop2Bug + +import lang::rascal::tests::concrete::recovery::RecoveryTestSupport; +import lang::rascal::\syntax::Rascal; +import ParseTree; +import IO; + +void testBug() { + standardParser = parser(#start[Module], allowRecovery=false, allowAmbiguity=true); + recoveryParser = parser(#start[Module], allowRecovery=true, allowAmbiguity=true); + input = readFile(|std:///lang/rascal/grammar/tests/TestGrammars.rsc|); + testDeleteUntilEol(standardParser, recoveryParser, input, 200, 100, begin=278, end=278); +} + diff --git a/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/bugs/InfiniteLoopBug.rsc b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/bugs/InfiniteLoopBug.rsc new file mode 100644 index 00000000000..8710b829d53 --- /dev/null +++ b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/bugs/InfiniteLoopBug.rsc @@ -0,0 +1,20 @@ +module lang::rascal::tests::concrete::recovery::bugs::InfiniteLoopBug + +import lang::rascal::tests::concrete::recovery::RecoveryTestSupport; +import lang::rascal::\syntax::Rascal; +import ParseTree; +import IO; + +void testInfiniteLoop1() { + str input = readFile(|std:///lang/rascal/tests/concrete/recovery/bugs/InfiniteLoopInput.txt|); + p = parser(#start[Module], allowRecovery=true, allowAmbiguity=true); + println("starting parse"); + p(input, |unknown:///?visualize=false|); +} + +void testInfiniteLoop2() { + standardParser = parser(#start[Module], allowRecovery=false, allowAmbiguity=true); + recoveryParser = parser(#start[Module], allowRecovery=true, allowAmbiguity=true); + input = readFile(|std:///analysis/m3/FlowGraph.rsc|); + testDeleteUntilEol(standardParser, recoveryParser, input, 200, 100); +} diff --git a/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/bugs/InfiniteLoopInput.txt b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/bugs/InfiniteLoopInput.txt new file mode 100644 index 00000000000..26359b3e558 --- /dev/null +++ b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/bugs/InfiniteLoopInput.txt @@ -0,0 +1,3 @@ +module M +alias G = rel[loc, set[E +data P = t() | f(); diff --git a/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/bugs/OvertakeBug.rsc b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/bugs/OvertakeBug.rsc new file mode 100644 index 00000000000..2880cef3a39 --- /dev/null +++ b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/bugs/OvertakeBug.rsc @@ -0,0 +1,11 @@ +module lang::rascal::tests::concrete::recovery::bugs::OvertakeBug + +import lang::rascal::\syntax::Rascal; +import ParseTree; +import IO; + +bool testOvertakeBug() { + str input = readFile(|std:///lang/rascal/tests/concrete/recovery/bugs/OvertakeBugInput.txt|); + parser(#Module, allowRecovery=true, allowAmbiguity=true)(input, |unknown:///?visualize=false|); + return true; +} \ No newline at end of file diff --git a/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/bugs/OvertakeBugInput.txt b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/bugs/OvertakeBugInput.txt new file mode 100644 index 00000000000..a6821039bc7 --- /dev/null +++ b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/bugs/OvertakeBugInput.txt @@ -0,0 +1,39 @@ +module analysis::diff::edits::ExecuteTextEdits + +extend analysis::diff::edits::TextEdits; +import IO; +import String; +import List; + +void executeDocumentEdits(list[DocumentEdit] edits) { + for (e <- edits) { + executeDocumentEdit(e); + } +} + +void executeDocumentEdit(removed(loc f)) { + remove(f.top; +} + +void executeDocumentEdit(created(loc f)) { + writeFile(f, ""); +} + +void executeDocumentEdit(renamed(loc from, loc to)) { + move(from.top, to.top, overwrite=true); +} + +void executeDocumentEdit(changed(loc file, list[TextEdit] edits)) { + assert isSorted(edits, less=bool (TextEdit e1, TextEdit e2) { + return e1.range.offset < e2.range.offset; + }); + + str content = readFile(file); + + for (replace(loc range, str repl) <- reverse(edits)) { + assert range.top == file.top; + content = ""; + } + + writeFile(file.top, content); +} \ No newline at end of file diff --git a/src/org/rascalmpl/library/util/TermREPL.java b/src/org/rascalmpl/library/util/TermREPL.java index f149bb36340..5acb2776393 100644 --- a/src/org/rascalmpl/library/util/TermREPL.java +++ b/src/org/rascalmpl/library/util/TermREPL.java @@ -1,3 +1,16 @@ +/** + * Copyright (c) 2024, NWO-I Centrum Wiskunde & Informatica (CWI) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ package org.rascalmpl.library.util; import java.io.ByteArrayInputStream; @@ -71,7 +84,7 @@ public ITuple newREPL(IConstructor repl, IString title, IString welcome, IString } TypeFactory tf = TypeFactory.getInstance(); - IFunction run = vf.function(tf.functionType(tf.voidType(), tf.tupleEmpty(), tf.tupleEmpty()), + IFunction run = vf.function(tf.functionType(tf.voidType(), tf.tupleEmpty(), tf.tupleEmpty()), (args, kwargs) -> { try { baseRepl.run(); @@ -80,14 +93,14 @@ public ITuple newREPL(IConstructor repl, IString title, IString welcome, IString throw RuntimeExceptionFactory.io(e.getMessage()); } return vf.tuple(); - }); + }); - IFunction send = vf.function(tf.functionType(tf.voidType(), tf.tupleType(tf.stringType()), tf.tupleEmpty()), + IFunction send = vf.function(tf.functionType(tf.voidType(), tf.tupleType(tf.stringType()), tf.tupleEmpty()), (args, kwargs) -> { baseRepl.queueCommand(((IString)args[0]).getValue()); return vf.tuple(); }); - + return vf.tuple(run, send); } @@ -110,7 +123,7 @@ public TheREPL(IValueFactory vf, IString title, IString welcome, IString prompt, this.input = input; this.stderr = stderr; this.stdout = stdout; - + // TODO: these casts mean that TheRepl only works with functions produced by the // interpreter for now. The reason is that the REPL needs access to environment configuration // parameters of these functions such as stdout, stdin, etc. @@ -192,32 +205,32 @@ public void handleInput(String line, Map output, Map output, Map metadata, IConstructor content) throws IOException, UnsupportedEncodingException { String id = ((IString) content.get("id")).getValue(); Function callback = liftProviderFunction(content.get("callback")); REPLContentServer server = contentManager.addServer(id, callback); - + String URL = "http://localhost:" + server.getListeningPort(); - + produceHTMLResponse(id, URL, output, metadata); } - + private void produceHTMLResponse(String id, String URL, Map output, Map metadata) throws UnsupportedEncodingException{ String html; if (metadata.containsKey("origin") && metadata.get("origin").equals("notebook")) html = " \n
\n
"; else html = ""; - + metadata.put("url", URL); output.put("text/html", new ByteArrayInputStream(html.getBytes("UTF8"))); - + String message = "Serving visual content at |" + URL + "|"; output.put("text/plain", new ByteArrayInputStream(message.getBytes("UTF8"))); - + } private Function liftProviderFunction(IValue callback) { @@ -238,7 +251,7 @@ private void handleJSONResponse(Map output, IConstructor re IValue dtf = kws.getParameter("dateTimeFormat"); IValue dai = kws.getParameter("dateTimeAsInt"); - + JsonValueWriter writer = new JsonValueWriter() .setCalendarFormat(dtf != null ? ((IString) dtf).getValue() : "yyyy-MM-dd\'T\'HH:mm:ss\'Z\'") .setDatesAsInt(dai != null ? ((IBool) dai).getValue() : true); @@ -312,7 +325,7 @@ private IValue call(IFunction f, Type[] types, IValue[] args) { @Override public CompletionResult completeFragment(String line, int cursor) { ITuple result = (ITuple)call(completor, new Type[] { tf.stringType(), tf.integerType() }, - new IValue[] { vf.string(line), vf.integer(cursor) }); + new IValue[] { vf.string(line), vf.integer(cursor) }); List suggestions = new ArrayList<>(); diff --git a/src/org/rascalmpl/library/vis/Text.rsc b/src/org/rascalmpl/library/vis/Text.rsc index 566a229c92d..5c8c6846925 100644 --- a/src/org/rascalmpl/library/vis/Text.rsc +++ b/src/org/rascalmpl/library/vis/Text.rsc @@ -41,6 +41,8 @@ str prettyTree(Tree t, bool src=false, bool characters=true, bool \layout=false, str nodeLabel(appl(prod(label(str l, Symbol nt), _, _), _)) = " = : "; str nodeLabel(appl(prod(Symbol nt, as, _), _)) = " = <}>"; + str nodeLabel(appl(error(Symbol nt, Production p, int dot), _)) = "!error dot=: "; + str nodeLabel(appl(skipped(Symbol s), chars)) = "skipped"; str nodeLabel(appl(regular(Symbol nt), _)) = ""; str nodeLabel(char(32)) = "⎵"; str nodeLabel(char(10)) = "\\r"; diff --git a/src/org/rascalmpl/parser/gtd/ExpectsProvider.java b/src/org/rascalmpl/parser/gtd/ExpectsProvider.java new file mode 100644 index 00000000000..fbbd7970c4e --- /dev/null +++ b/src/org/rascalmpl/parser/gtd/ExpectsProvider.java @@ -0,0 +1,21 @@ +/** + * Copyright (c) 2024, NWO-I Centrum Wiskunde & Informatica (CWI) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + +package org.rascalmpl.parser.gtd; + +import org.rascalmpl.parser.gtd.stack.AbstractStackNode; + +public interface ExpectsProvider

{ + AbstractStackNode

[] getExpects(String nonTerminal); +} diff --git a/src/org/rascalmpl/parser/gtd/IGTD.java b/src/org/rascalmpl/parser/gtd/IGTD.java index bb4e9b5c656..0e61d06b213 100644 --- a/src/org/rascalmpl/parser/gtd/IGTD.java +++ b/src/org/rascalmpl/parser/gtd/IGTD.java @@ -22,7 +22,7 @@ /** * Parser interface. */ -public interface IGTD{ +public interface IGTD extends ExpectsProvider

{ /** * Parse the input string, using the given non-terminal as start node. If * the parse process successfully completes a result will be constructed diff --git a/src/org/rascalmpl/parser/gtd/SGTDBF.java b/src/org/rascalmpl/parser/gtd/SGTDBF.java index b0232bacf3e..2abcfd836f1 100755 --- a/src/org/rascalmpl/parser/gtd/SGTDBF.java +++ b/src/org/rascalmpl/parser/gtd/SGTDBF.java @@ -1,13 +1,10 @@ /******************************************************************************* - * Copyright (c) 2009-2013 CWI - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html + * Copyright (c) 2009-2013 CWI All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which accompanies this + * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html * - * Contributors: - * * Arnold Lankamp - Arnold.Lankamp@cwi.nl -*******************************************************************************/ + * Contributors: * Arnold Lankamp - Arnold.Lankamp@cwi.nl + *******************************************************************************/ package org.rascalmpl.parser.gtd; import java.lang.reflect.InvocationTargetException; @@ -45,586 +42,716 @@ import org.rascalmpl.parser.gtd.util.IntegerList; import org.rascalmpl.parser.gtd.util.IntegerObjectList; import org.rascalmpl.parser.gtd.util.Stack; +import org.rascalmpl.parser.uptr.debug.NopDebugListener; +import org.rascalmpl.parser.util.DebugUtil; +import org.rascalmpl.parser.util.ParseStateVisualizer; +import org.rascalmpl.util.visualize.dot.NodeId; +import org.rascalmpl.values.RascalValueFactory; +import org.rascalmpl.values.parsetrees.ITree; +import org.rascalmpl.values.parsetrees.TreeAdapter; + +import io.usethesource.vallang.IConstructor; +import io.usethesource.vallang.IList; +import io.usethesource.vallang.ISet; +import io.usethesource.vallang.IValue; +import io.usethesource.vallang.type.Type; /** * This is the core of the parser; it drives the parse process. */ -public abstract class SGTDBF implements IGTD{ +public abstract class SGTDBF implements IGTD { private final static int DEFAULT_TODOLIST_CAPACITY = 16; - + private URI inputURI; private int[] input; - + private int location; + protected int lookAheadChar; + + // A mapping between character location and line/column. private final PositionStore positionStore; - + + // Terminals that matched. Circular buffer indexed by length of the terminal. Each entry contains + // the node to reduce and the result node + // This is a circular buffer where queueIndex determines the start of the buffer. + // At each position, a stack is maintained of all terminals to reduce of a certain length. + // So at queueIndex+3, all terminals of length 3 that need reducing are stored. private DoubleStack, AbstractNode>[] todoLists; private int queueIndex; - + + // Stack of non-terminal nodes to expand + // - Nodes are removed in expand, which pops and expands all stack nodes on this stack + // - Nodes are added in: + // - parse: the root node is pushed + // - updateNextNode: next node of the production is pushed + // - updateAlternativeNextNode: next node of a prefix-shared production is pushed + // - handleExpects: non-matchable first element of each alternative is pushed + // - expandStack: when an expandable stack is expanded, all non-matchable children are pushed private final Stack> stacksToExpand; + + // The current stack of non-terminals to reduce. Each stack has a container node to accumulate + // results. + // - Nodes are removed in `reduceNonTerminals` where all productions are advanced one dot past the + // non-terminal node + // - Nodes are added in: + // - handleEdgeList: result container node is created and all edges are pushed with the same result + // container node + // - handleEdgeListWithRestrictions: result container node is created and all edges are pushed with + // the same result container node + // - expandStack: non-matchable, non-expandable nodes (and their existing result container node) are + // added if their name can be found in `cachedEdgesForExpect`. + // - expandStack: expandable nodes that are nullable? Might be a cycle thing private final DoubleStack, AbstractContainerNode

> stacksWithNonTerminalsToReduce; + + // The current stack of non-terminals to reduce: it contains the matchable node with the smallest + // length from todoLists. + // - Nodes are removed in `reduceTerminals` where all productions are advanced one dot past the + // matchable node + // - Variable is assigned in: + // - findFirstStacksToReduce: the first non-empty `todoList` is assigned to this variable + // - findStacksToReduce: again the first non-empty `todoList` is assigned to this variable + // - parse: variable is used in main reduce/expand loop to determine when it is time to look for + // more `stacksToReduce`. private DoubleStack, AbstractNode> stacksWithTerminalsToReduce; - + private final HashMap> cachedEdgesForExpect; - + private final IntegerKeyedDoubleValueHashMap, DoubleArrayList, AbstractNode>> sharedNextNodes; - - private int location; - - protected int lookAheadChar; - + + + // Reflection is used to get the expects for each non-terminal. + // This cache is used so the reflection call is only needed once per non-terminal. private final HashMap[]> expectCache; - + private final IntegerObjectList> sharedLastExpects; - + // Guard private boolean invoked; - + // Error reporting private final Stack> unexpandableNodes; - private final Stack> unmatchableLeafNodes; + private final Stack> unmatchableLeafNodes; // Leaf nodes (for instance literals) that failed to + // match private final DoubleStack, AbstractNode>, AbstractStackNode

> unmatchableMidProductionNodes; private final DoubleStack, AbstractNode> filteredNodes; - + // Error reporting guards - private boolean parseErrorOccured; - + private boolean parseErrorEncountered; + private boolean parseErrorRecovered; + // Error recovery private IRecoverer

recoverer; - + // Debugging private IDebugListener

debugListener; - + private ParseStateVisualizer visualizer; + // Temporary instrumentation for accurate profiling private long timestamp; private boolean printTimes = false; - - public SGTDBF(){ + + + public SGTDBF() { super(); - + positionStore = new PositionStore(); - + stacksToExpand = new Stack>(); stacksWithNonTerminalsToReduce = new DoubleStack, AbstractContainerNode

>(); - + cachedEdgesForExpect = new HashMap>(); - - sharedNextNodes = new IntegerKeyedDoubleValueHashMap, DoubleArrayList, AbstractNode>>(); - + + sharedNextNodes = + new IntegerKeyedDoubleValueHashMap, DoubleArrayList, AbstractNode>>(); + location = 0; - + expectCache = new HashMap[]>(); - + sharedLastExpects = new IntegerObjectList>(); - + unexpandableNodes = new Stack>(); unmatchableLeafNodes = new Stack>(); - unmatchableMidProductionNodes = new DoubleStack, AbstractNode>, AbstractStackNode

>(); + unmatchableMidProductionNodes = + new DoubleStack, AbstractNode>, AbstractStackNode

>(); filteredNodes = new DoubleStack, AbstractNode>(); } /** - * Return a stack node id that is guaranteed not to be in use. - * The parser generator generates an override for this method as it knows which ids have been dispensed. - * Tests that need this should override this method, probably using a common base class. + * Return a stack node id that is guaranteed not to be in use. The parser generator generates an + * override for this method as it knows which ids have been dispensed. Tests that need this should + * override this method, probably using a common base class. */ protected int getFreeStackNodeId() { throw new UnsupportedOperationException(); } - - /** - * Triggers the gathering of alternatives for the given non-terminal. - */ + @SuppressWarnings("unchecked") - protected AbstractStackNode

[] invokeExpects(AbstractStackNode

nonTerminal){ - String name = nonTerminal.getName(); - AbstractStackNode

[] expects = expectCache.get(name); - if(expects == null){ - try{ - Method method = getClass().getMethod(name); - try{ + public AbstractStackNode

[] getExpects(String nonTerminal) { + AbstractStackNode

[] expects = expectCache.get(nonTerminal); + if (expects == null) { + try { + Method method = getClass().getMethod(nonTerminal); + try { method.setAccessible(true); // Try to bypass the 'isAccessible' check to save time. - }catch(SecurityException sex){ + } + catch (SecurityException sex) { // Ignore this if it happens. } - + expects = (AbstractStackNode

[]) method.invoke(this); - }catch(NoSuchMethodException nsmex){ - throw new UndeclaredNonTerminalException(name, getClass()); - }catch(IllegalAccessException iaex){ + } + catch (NoSuchMethodException nsmex) { + throw new UndeclaredNonTerminalException(nonTerminal, getClass()); + } + catch (IllegalAccessException iaex) { throw new RuntimeException(iaex); - }catch(InvocationTargetException itex){ + } + catch (InvocationTargetException itex) { throw new RuntimeException(itex.getTargetException()); } - - expectCache.putUnsafe(name, expects); + + expectCache.putUnsafe(nonTerminal, expects); } - - return expects; + + return expects; } - + + /** + * Triggers the gathering of alternatives for the given non-terminal. + */ + protected AbstractStackNode

[] invokeExpects(AbstractStackNode

nonTerminal) { + return getExpects(nonTerminal.getName()); + } + /** * Moves to the next symbol in the production. */ - private AbstractStackNode

updateNextNode(AbstractStackNode

next, AbstractStackNode

node, AbstractNode result){ - IntegerKeyedDoubleValueHashMap.Entry, DoubleArrayList, AbstractNode>> alternativeEntry = sharedNextNodes.get(next.getId()); - if(alternativeEntry != null){ // Sharing check. + private AbstractStackNode

updateNextNode(AbstractStackNode

next, AbstractStackNode

node, + AbstractNode result) { + IntegerKeyedDoubleValueHashMap.Entry, DoubleArrayList, AbstractNode>> alternativeEntry = + sharedNextNodes.get(next.getId()); + if (alternativeEntry != null) { // Sharing check. DoubleArrayList, AbstractNode> predecessors = alternativeEntry.value2; - if(predecessors != null){ + if (predecessors != null) { predecessors.add(node, result); return null; } - + AbstractStackNode

alternative = alternativeEntry.value1; - if(alternative.getStartLocation() == location){ - if(alternative.isMatchable()){ - if(alternative.isEmptyLeafNode()){ + if (alternative.getStartLocation() == location) { + if (alternative.isMatchable()) { + if (alternative.isEmptyLeafNode()) { // Encountered a possible stack 'overtake'. - if(node.getStartLocation() != location){ + if (node.getStartLocation() != location) { propagateEdgesAndPrefixes(node, result, alternative, alternative.getResult()); - }else{ - propagateEdgesAndPrefixesForNullable(node, result, alternative, alternative.getResult(), node.getEdges().size()); + } + else { + propagateEdgesAndPrefixesForNullable(node, result, alternative, alternative.getResult(), + node.getEdges().size()); } return alternative; } - }else{ + } + else { EdgesSet

alternativeEdgesSet = alternative.getIncomingEdges(); int resultStoreId = getResultStoreId(alternative.getId()); - if(alternativeEdgesSet != null && alternativeEdgesSet.getLastVisitedLevel(resultStoreId) == location){ + if (alternativeEdgesSet != null + && alternativeEdgesSet.getLastVisitedLevel(resultStoreId) == location) { // Encountered a possible stack 'overtake'. - if(node.getStartLocation() != location){ - propagateEdgesAndPrefixes(node, result, alternative, alternativeEdgesSet.getLastResult(resultStoreId)); - }else{ - propagateEdgesAndPrefixesForNullable(node, result, alternative, alternativeEdgesSet.getLastResult(resultStoreId), node.getEdges().size()); + if (node.getStartLocation() != location) { + propagateEdgesAndPrefixes(node, result, alternative, + alternativeEdgesSet.getLastResult(resultStoreId)); + } + else { + propagateEdgesAndPrefixesForNullable(node, result, alternative, + alternativeEdgesSet.getLastResult(resultStoreId), node.getEdges().size()); } return alternative; } } } - + alternative.updateNode(node, result); - - if(debugListener != null) debugListener.progressed(node, result, alternative); - + + debugListener.progressed(node, result, alternative); + return alternative; } - - if(next.isMatchable()){ // Eager matching optimization. - if((location + next.getLength()) > input.length) return null; - + + if (next.isMatchable()) { // Eager matching optimization. + if ((location + next.getLength()) > input.length) + return null; + AbstractNode nextResult = next.match(input, location); - if(nextResult == null){ - // Push the node including it's predecessor to the appropriate error tracking collection (and take care of merging when necessary). - DoubleArrayList, AbstractNode> predecessors = new DoubleArrayList, AbstractNode>(); + if (nextResult == null) { + // Push the node including it's predecessor to the appropriate error tracking collection (and take + // care of merging when necessary). + DoubleArrayList, AbstractNode> predecessors = + new DoubleArrayList, AbstractNode>(); predecessors.add(node, result); unmatchableMidProductionNodes.push(predecessors, next); - + sharedNextNodes.putUnsafe(next.getId(), next, predecessors); - - if(debugListener != null) debugListener.failedToMatch(next); - + + debugListener.failedToMatch(next); + return null; } - - if(debugListener != null) debugListener.matched(next, nextResult); - + + debugListener.matched(next, nextResult); + next = next.getCleanCopyWithResult(location, nextResult); - }else{ + } + else { next = next.getCleanCopy(location); } - - if(!node.isMatchable() || result.isEmpty()){ + + if (!node.isMatchable() || result.isEmpty()) { next.updateNode(node, result); - }else{ // Non-nullable terminal specific edge set sharing optimization. + } + else { // Non-nullable terminal specific edge set sharing optimization. next.updateNodeAfterNonEmptyMatchable(node, result); } - - if(debugListener != null) debugListener.progressed(node, result, next); - + + debugListener.progressed(node, result, next); + sharedNextNodes.putUnsafe(next.getId(), next, null); stacksToExpand.push(next); - + return next; } - + /** * Moves to the next symbol in an alternative continuation of a prefix-shared production. */ - private boolean updateAlternativeNextNode(AbstractStackNode

next, AbstractStackNode

node, AbstractNode result, IntegerObjectList> edgesMap, ArrayList[] prefixesMap){ + private boolean updateAlternativeNextNode(AbstractStackNode

next, AbstractStackNode

node, AbstractNode result, + IntegerObjectList> edgesMap, ArrayList[] prefixesMap) { int id = next.getId(); - IntegerKeyedDoubleValueHashMap.Entry, DoubleArrayList, AbstractNode>> alternativeEntry = sharedNextNodes.get(id); - if(alternativeEntry != null){ // Sharing check. + IntegerKeyedDoubleValueHashMap.Entry, DoubleArrayList, AbstractNode>> alternativeEntry = + sharedNextNodes.get(id); + if (alternativeEntry != null) { // Sharing check. DoubleArrayList, AbstractNode> predecessors = alternativeEntry.value2; - if(predecessors != null){ + if (predecessors != null) { predecessors.add(node, result); return false; } - + AbstractStackNode

alternative = alternativeEntry.value1; - if(result.isEmpty()){ - if(alternative.isMatchable()){ - if(alternative.isEmptyLeafNode()){ + if (result.isEmpty()) { + if (alternative.isMatchable()) { + if (alternative.isEmptyLeafNode()) { // Encountered a possible stack 'overtake'. - propagateAlternativeEdgesAndPrefixes(node, result, alternative, alternative.getResult(), node.getEdges().size(), edgesMap, prefixesMap); + propagateAlternativeEdgesAndPrefixes(node, result, alternative, alternative.getResult(), + node.getEdges().size(), edgesMap, prefixesMap); return true; } - }else{ + } + else { EdgesSet

alternativeEdgesSet = alternative.getIncomingEdges(); int resultStoreId = getResultStoreId(alternative.getId()); - if(alternativeEdgesSet != null && alternativeEdgesSet.getLastVisitedLevel(resultStoreId) == location){ + if (alternativeEdgesSet != null + && alternativeEdgesSet.getLastVisitedLevel(resultStoreId) == location) { AbstractContainerNode

nextResult = alternativeEdgesSet.getLastResult(resultStoreId); // Encountered a possible stack 'overtake'. - propagateAlternativeEdgesAndPrefixes(node, result, alternative, nextResult, node.getEdges().size(), edgesMap, prefixesMap); + propagateAlternativeEdgesAndPrefixes(node, result, alternative, nextResult, + node.getEdges().size(), edgesMap, prefixesMap); return true; } } } - - alternative.updatePrefixSharedNode(edgesMap, prefixesMap); // Prevent unnecessary overhead; share whenever possible. - - if(debugListener != null) debugListener.progressed(node, result, alternative); - + + alternative.updatePrefixSharedNode(edgesMap, prefixesMap); // Prevent unnecessary overhead; share whenever + // possible. + + debugListener.progressed(node, result, alternative); + return true; } - - if(next.isMatchable()){ // Eager matching optimization. - if((location + next.getLength()) > input.length) return false; - + + if (next.isMatchable()) { // Eager matching optimization. + if ((location + next.getLength()) > input.length) + return false; + AbstractNode nextResult = next.match(input, location); - if(nextResult == null){ - // Push the node including it's predecessor to the appropriate error tracking collection (and take care of merging when necessary). - DoubleArrayList, AbstractNode> predecessors = new DoubleArrayList, AbstractNode>(); + if (nextResult == null) { + // Push the node including it's predecessor to the appropriate error tracking collection (and take + // care of merging when necessary). + DoubleArrayList, AbstractNode> predecessors = + new DoubleArrayList, AbstractNode>(); predecessors.add(node, result); unmatchableMidProductionNodes.push(predecessors, next); - + sharedNextNodes.putUnsafe(id, next, predecessors); - - if(debugListener != null) debugListener.failedToMatch(next); - + + debugListener.failedToMatch(next); + return false; } - - if(debugListener != null) debugListener.matched(next, nextResult); - + + debugListener.matched(next, nextResult); + next = next.getCleanCopyWithResult(location, nextResult); - }else{ + } + else { next = next.getCleanCopy(location); } - + next.updatePrefixSharedNode(edgesMap, prefixesMap); // Prevent unnecessary overhead; share whenever possible. - if(debugListener != null) debugListener.progressed(node, result, next); - + debugListener.progressed(node, result, next); + sharedNextNodes.putUnsafe(id, next, null); stacksToExpand.push(next); - + return true; } - + /** - * Part of the hidden-right-recursion fix. - * Executes absent reductions. + * Part of the hidden-right-recursion fix. Executes absent reductions. */ - private void propagateReductions(AbstractStackNode

node, AbstractNode nodeResultStore, AbstractStackNode

next, AbstractNode nextResultStore, int potentialNewEdges){ + private void propagateReductions(AbstractStackNode

node, AbstractNode nodeResultStore, AbstractStackNode

next, + AbstractNode nextResultStore, int potentialNewEdges) { IntegerList propagatedReductions = next.getPropagatedReductions(); - + IntegerObjectList> edgesMap = node.getEdges(); ArrayList[] prefixes = node.getPrefixesMap(); - + P production = next.getParentProduction(); String name = edgesMap.getValue(0).get(0).getName(); - + boolean hasNestingRestrictions = hasNestingRestrictions(name); IntegerList filteredParents = null; - if(hasNestingRestrictions){ + if (hasNestingRestrictions) { filteredParents = getFilteredParents(next.getId()); } - + int fromIndex = edgesMap.size() - potentialNewEdges; - for(int i = edgesMap.size() - 1; i >= fromIndex; --i){ + for (int i = edgesMap.size() - 1; i >= fromIndex; --i) { int startLocation = edgesMap.getKey(i); - + // We know we haven't been here before. propagatedReductions.add(startLocation); - + ArrayList edgePrefixes = new ArrayList(); Link prefix = (prefixes != null) ? new Link(prefixes[i], nodeResultStore) : new Link(null, nodeResultStore); edgePrefixes.add(prefix); - + Link resultLink = new Link(edgePrefixes, nextResultStore); - + EdgesSet

edgeSet = edgesMap.getValue(i); - - if(debugListener != null) debugListener.reducing(node, resultLink, edgeSet); - - if(!hasNestingRestrictions){ + + debugListener.reducing(node, resultLink, edgeSet); + + if (!hasNestingRestrictions) { handleEdgeList(edgeSet, name, production, resultLink, startLocation); - }else{ + } + else { handleEdgeListWithRestrictions(edgeSet, name, production, resultLink, startLocation, filteredParents); } } } - + /** - * Part of the hidden-right-recursion fix. - * Propagates absent prefixes. + * Part of the hidden-right-recursion fix. Propagates absent prefixes. */ - private void propagatePrefixes(AbstractStackNode

next, AbstractNode nextResult, int nrOfAddedEdges){ + private void propagatePrefixes(AbstractStackNode

next, AbstractNode nextResult, int nrOfAddedEdges) { // Proceed with the tail of the production. int nextDot = next.getDot() + 1; AbstractStackNode

[] prod = next.getProduction(); AbstractStackNode

nextNext = prod[nextDot]; - IntegerKeyedDoubleValueHashMap.Entry, DoubleArrayList, AbstractNode>> nextNextAlternativeEntry = sharedNextNodes.get(nextNext.getId()); + IntegerKeyedDoubleValueHashMap.Entry, DoubleArrayList, AbstractNode>> nextNextAlternativeEntry = + sharedNextNodes.get(nextNext.getId()); AbstractStackNode

nextNextAlternative = null; - if(nextNextAlternativeEntry != null){ // Valid continuation. + if (nextNextAlternativeEntry != null) { // Valid continuation. DoubleArrayList, AbstractNode> predecessors = nextNextAlternativeEntry.value2; - if(predecessors == null){ + if (predecessors == null) { nextNextAlternative = nextNextAlternativeEntry.value1; - if(nextNextAlternative.isMatchable()){ - if(nextNextAlternative.isEmptyLeafNode()){ - propagateEdgesAndPrefixesForNullable(next, nextResult, nextNextAlternative, nextNextAlternative.getResult(), nrOfAddedEdges); - }else{ + if (nextNextAlternative.isMatchable()) { + if (nextNextAlternative.isEmptyLeafNode()) { + propagateEdgesAndPrefixesForNullable(next, nextResult, nextNextAlternative, + nextNextAlternative.getResult(), nrOfAddedEdges); + } + else { nextNextAlternative.updateNode(next, nextResult); - - if(debugListener != null) debugListener.propagated(next, nextResult, nextNextAlternative); + + debugListener.propagated(next, nextResult, nextNextAlternative); } - }else{ + } + else { EdgesSet

nextNextAlternativeEdgesSet = nextNextAlternative.getIncomingEdges(); int resultStoreId = getResultStoreId(nextNextAlternative.getId()); - if(nextNextAlternativeEdgesSet != null && nextNextAlternativeEdgesSet.getLastVisitedLevel(resultStoreId) == location){ - propagateEdgesAndPrefixesForNullable(next, nextResult, nextNextAlternative, nextNextAlternativeEdgesSet.getLastResult(resultStoreId), nrOfAddedEdges); - }else{ + if (nextNextAlternativeEdgesSet != null + && nextNextAlternativeEdgesSet.getLastVisitedLevel(resultStoreId) == location) { + propagateEdgesAndPrefixesForNullable(next, nextResult, nextNextAlternative, + nextNextAlternativeEdgesSet.getLastResult(resultStoreId), nrOfAddedEdges); + } + else { nextNextAlternative.updateNode(next, nextResult); - - if(debugListener != null) debugListener.propagated(next, nextResult, nextNextAlternative); + + debugListener.propagated(next, nextResult, nextNextAlternative); } } - }else{ + } + else { predecessors.add(next, nextResult); } } - + // Handle alternative continuations (related to prefix sharing). AbstractStackNode

[][] alternateProds = next.getAlternateProductions(); - if(alternateProds != null){ - if(nextNextAlternative == null){ // If the first continuation has not been initialized yet (it may be a matchable that didn't match), create a dummy version to construct the necessary edges and prefixes. - if(!nextNext.isMatchable()) return; // Matchable, abort. + if (alternateProds != null) { + if (nextNextAlternative == null) { // If the first continuation has not been initialized yet (it may be a + // matchable that didn't match), create a dummy version to construct the + // necessary edges and prefixes. + if (!nextNext.isMatchable()) + return; // Matchable, abort. nextNextAlternative = nextNext.getCleanCopy(location); nextNextAlternative.updateNode(next, nextResult); - - if(debugListener != null) debugListener.propagated(next, nextResult, nextNextAlternative); + + debugListener.propagated(next, nextResult, nextNextAlternative); } - + IntegerObjectList> nextNextEdgesMap = nextNextAlternative.getEdges(); ArrayList[] nextNextPrefixesMap = nextNextAlternative.getPrefixesMap(); - - for(int i = alternateProds.length - 1; i >= 0; --i){ + + for (int i = alternateProds.length - 1; i >= 0; --i) { prod = alternateProds[i]; - if(nextDot == prod.length) continue; + if (nextDot == prod.length) + continue; AbstractStackNode

alternativeNextNext = prod[nextDot]; - - IntegerKeyedDoubleValueHashMap.Entry, DoubleArrayList, AbstractNode>> nextNextAltAlternativeEntry = sharedNextNodes.get(alternativeNextNext.getId()); + + IntegerKeyedDoubleValueHashMap.Entry, DoubleArrayList, AbstractNode>> nextNextAltAlternativeEntry = + sharedNextNodes.get(alternativeNextNext.getId()); AbstractStackNode

nextNextAltAlternative = null; - if(nextNextAltAlternativeEntry != null){ - DoubleArrayList, AbstractNode> predecessors = nextNextAltAlternativeEntry.value2; - if(predecessors == null){ + if (nextNextAltAlternativeEntry != null) { + DoubleArrayList, AbstractNode> predecessors = + nextNextAltAlternativeEntry.value2; + if (predecessors == null) { nextNextAltAlternative = nextNextAltAlternativeEntry.value1; - if(nextNextAltAlternative.isMatchable()){ - if(nextNextAltAlternative.isEmptyLeafNode()){ - propagateAlternativeEdgesAndPrefixes(next, nextResult, nextNextAltAlternative, nextNextAltAlternative.getResult(), nrOfAddedEdges, nextNextEdgesMap, nextNextPrefixesMap); - }else{ + if (nextNextAltAlternative.isMatchable()) { + if (nextNextAltAlternative.isEmptyLeafNode()) { + propagateAlternativeEdgesAndPrefixes(next, nextResult, nextNextAltAlternative, + nextNextAltAlternative.getResult(), nrOfAddedEdges, nextNextEdgesMap, + nextNextPrefixesMap); + } + else { nextNextAltAlternative.updatePrefixSharedNode(nextNextEdgesMap, nextNextPrefixesMap); - - if(debugListener != null) debugListener.propagated(next, nextResult, nextNextAltAlternative); + + debugListener.propagated(next, nextResult, nextNextAltAlternative); } - }else{ + } + else { EdgesSet

nextAlternativeEdgesSet = nextNextAltAlternative.getIncomingEdges(); int resultStoreId = getResultStoreId(nextNextAltAlternative.getId()); - if(nextAlternativeEdgesSet != null && nextAlternativeEdgesSet.getLastVisitedLevel(resultStoreId) == location){ - propagateAlternativeEdgesAndPrefixes(next, nextResult, nextNextAltAlternative, nextAlternativeEdgesSet.getLastResult(resultStoreId), nrOfAddedEdges, nextNextEdgesMap, nextNextPrefixesMap); - }else{ + if (nextAlternativeEdgesSet != null + && nextAlternativeEdgesSet.getLastVisitedLevel(resultStoreId) == location) { + propagateAlternativeEdgesAndPrefixes(next, nextResult, nextNextAltAlternative, + nextAlternativeEdgesSet.getLastResult(resultStoreId), nrOfAddedEdges, + nextNextEdgesMap, nextNextPrefixesMap); + } + else { nextNextAltAlternative.updatePrefixSharedNode(nextNextEdgesMap, nextNextPrefixesMap); - - if(debugListener != null) debugListener.propagated(next, nextResult, nextNextAltAlternative); + + debugListener.propagated(next, nextResult, nextNextAltAlternative); } } - }else{ + } + else { predecessors.add(next, nextResult); } } } } } - + /** - * Part of the hidden-right-recursion fix. - * Inserts missing prefixes and triggers reductions where necessary. + * Part of the hidden-right-recursion fix. Inserts missing prefixes and triggers reductions where + * necessary. */ - private void propagateEdgesAndPrefixes(AbstractStackNode

node, AbstractNode nodeResult, AbstractStackNode

next, AbstractNode nextResult){ + private void propagateEdgesAndPrefixes(AbstractStackNode

node, AbstractNode nodeResult, + AbstractStackNode

next, AbstractNode nextResult) { int nrOfAddedEdges = next.updateOvertakenNode(node, nodeResult); - if(debugListener != null) debugListener.propagated(node, nodeResult, next); - - if(nrOfAddedEdges == 0) return; - - if(next.isEndNode()){ + debugListener.propagated(node, nodeResult, next); + + if (nrOfAddedEdges == 0) + return; + + if (next.isEndNode()) { propagateReductions(node, nodeResult, next, nextResult, nrOfAddedEdges); } - - if(next.hasNext()){ + + if (next.hasNext()) { propagatePrefixes(next, nextResult, nrOfAddedEdges); } } - + /** - * Part of the hidden-right-recursion fix. - * Inserts missing prefixes and triggers reductions where necessary (specific for nullable nodes). + * Part of the hidden-right-recursion fix. Inserts missing prefixes and triggers reductions where + * necessary (specific for nullable nodes). */ - private void propagateEdgesAndPrefixesForNullable(AbstractStackNode

node, AbstractNode nodeResult, AbstractStackNode

next, AbstractNode nextResult, int potentialNewEdges){ + private void propagateEdgesAndPrefixesForNullable(AbstractStackNode

node, AbstractNode nodeResult, + AbstractStackNode

next, AbstractNode nextResult, int potentialNewEdges) { int nrOfAddedEdges = next.updateOvertakenNullableNode(node, nodeResult, potentialNewEdges); - if(debugListener != null) debugListener.propagated(node, nodeResult, next); - - if(nrOfAddedEdges == 0) return; - - if(next.isEndNode()){ + debugListener.propagated(node, nodeResult, next); + + if (nrOfAddedEdges == 0) + return; + + if (next.isEndNode()) { propagateReductions(node, nodeResult, next, nextResult, nrOfAddedEdges); } - - if(next.hasNext()){ + + if (next.hasNext()) { propagatePrefixes(next, nextResult, nrOfAddedEdges); } } - + /** - * Part of the hidden-right-recursion fix. - * Inserts missing prefixes and triggers reductions where necessary (specifically for alternative continuations of prefix-shared productions). + * Part of the hidden-right-recursion fix. Inserts missing prefixes and triggers reductions where + * necessary (specifically for alternative continuations of prefix-shared productions). */ - private void propagateAlternativeEdgesAndPrefixes(AbstractStackNode

node, AbstractNode nodeResult, AbstractStackNode

next, AbstractNode nextResult, int potentialNewEdges, IntegerObjectList> edgesMap, ArrayList[] prefixesMap){ + private void propagateAlternativeEdgesAndPrefixes(AbstractStackNode

node, AbstractNode nodeResult, + AbstractStackNode

next, AbstractNode nextResult, int potentialNewEdges, + IntegerObjectList> edgesMap, ArrayList[] prefixesMap) { next.updatePrefixSharedNode(edgesMap, prefixesMap); - - if(debugListener != null) debugListener.propagated(node, nodeResult, next); - - if(potentialNewEdges == 0) return; - - if(next.isEndNode()){ + + debugListener.propagated(node, nodeResult, next); + + if (potentialNewEdges == 0) + return; + + if (next.isEndNode()) { propagateReductions(node, nodeResult, next, nextResult, potentialNewEdges); } - - if(next.hasNext()){ + + if (next.hasNext()) { propagatePrefixes(next, nextResult, potentialNewEdges); } } - + /** * Initiates the handling of reductions. */ - private void updateEdges(AbstractStackNode

node, AbstractNode result){ + private void updateEdges(AbstractStackNode

node, AbstractNode result) { IntegerObjectList> edgesMap = node.getEdges(); ArrayList[] prefixesMap = node.getPrefixesMap(); - + P production = node.getParentProduction(); String name = edgesMap.getValue(0).get(0).getName(); - + // Check for nesting restrictions. boolean hasNestingRestrictions = hasNestingRestrictions(name); IntegerList filteredParents = null; - if(hasNestingRestrictions){ + if (hasNestingRestrictions) { filteredParents = getFilteredParents(node.getId()); } - - for(int i = edgesMap.size() - 1; i >= 0; --i){ + + for (int i = edgesMap.size() - 1; i >= 0; --i) { Link resultLink = new Link((prefixesMap != null) ? prefixesMap[i] : null, result); - + EdgesSet

edgeSet = edgesMap.getValue(i); - - if(debugListener != null) debugListener.reducing(node, resultLink, edgeSet); - - if(!hasNestingRestrictions){ // Select the optimized path for handling edge sets that don't have nesting restrictions associated with them. + + debugListener.reducing(node, resultLink, edgeSet); + + if (!hasNestingRestrictions) { // Select the optimized path for handling edge sets that don't have nesting + // restrictions associated with them. handleEdgeList(edgeSet, name, production, resultLink, edgesMap.getKey(i)); - }else{ - handleEdgeListWithRestrictions(edgeSet, name, production, resultLink, edgesMap.getKey(i), filteredParents); + } + else { + handleEdgeListWithRestrictions(edgeSet, name, production, resultLink, edgesMap.getKey(i), + filteredParents); } } } - + /** * Initiates the handling of reductions for nullable symbols. */ - private void updateNullableEdges(AbstractStackNode

node, AbstractNode result){ + private void updateNullableEdges(AbstractStackNode

node, AbstractNode result) { IntegerList propagatedReductions = node.getPropagatedReductions(); - + int initialSize = propagatedReductions.size(); - + IntegerObjectList> edgesMap = node.getEdges(); ArrayList[] prefixesMap = node.getPrefixesMap(); - + P production = node.getParentProduction(); String name = edgesMap.getValue(0).get(0).getName(); - + // Check for nesting restrictions. boolean hasNestingRestrictions = hasNestingRestrictions(name); IntegerList filteredParents = null; - if(hasNestingRestrictions){ + if (hasNestingRestrictions) { filteredParents = getFilteredParents(node.getId()); } - - for(int i = edgesMap.size() - 1; i >= 0; --i){ + + for (int i = edgesMap.size() - 1; i >= 0; --i) { int startLocation = edgesMap.getKey(i); - - if(propagatedReductions.containsBefore(startLocation, initialSize)) continue; // Prevent duplicate reductions (artifact of the hidden-right-recursion fix). + + if (propagatedReductions.containsBefore(startLocation, initialSize)) + continue; // Prevent duplicate reductions (artifact of the hidden-right-recursion fix). propagatedReductions.add(startLocation); - + Link resultLink = new Link((prefixesMap != null) ? prefixesMap[i] : null, result); - + EdgesSet

edgeSet = edgesMap.getValue(i); - - if(debugListener != null) debugListener.reducing(node, resultLink, edgeSet); - - if(!hasNestingRestrictions){ // Select the optimized path for handling edge sets that don't have nesting restrictions associated with them. + + debugListener.reducing(node, resultLink, edgeSet); + + if (!hasNestingRestrictions) { // Select the optimized path for handling edge sets that don't have nesting + // restrictions associated with them. handleEdgeList(edgeSet, name, production, resultLink, startLocation); - }else{ + } + else { handleEdgeListWithRestrictions(edgeSet, name, production, resultLink, startLocation, filteredParents); } } } - + /** * Handles reductions. */ - private void handleEdgeList(EdgesSet

edgeSet, String name, P production, Link resultLink, int startLocation){ + private void handleEdgeList(EdgesSet

edgeSet, String name, P production, Link resultLink, int startLocation) { AbstractContainerNode

resultStore = null; int resultStoreId = EdgesSet.DEFAULT_RESULT_STORE_ID; - if(edgeSet.getLastVisitedLevel(resultStoreId) != location){ + if (edgeSet.getLastVisitedLevel(resultStoreId) != location) { AbstractStackNode

edge = edgeSet.get(0); - - if(edge.isRecovered()){ + + if (edge.isRecovered()) { resultStore = new RecoveredNode

(inputURI, startLocation, location); - }else if(edge.isExpandable()){ - resultStore = new ExpandableContainerNode

(inputURI, startLocation, location, startLocation == location, edge.isSeparator(), edge.isLayout()); - }else{ - resultStore = new SortContainerNode

(inputURI, startLocation, location, startLocation == location, edge.isSeparator(), edge.isLayout()); } - + else if (edge.isExpandable()) { + resultStore = new ExpandableContainerNode

(inputURI, startLocation, location, + startLocation == location, edge.isSeparator(), edge.isLayout()); + } + else { + resultStore = new SortContainerNode

(inputURI, startLocation, location, startLocation == location, + edge.isSeparator(), edge.isLayout()); + } + stacksWithNonTerminalsToReduce.push(edge, resultStore); - - if(debugListener != null) debugListener.reduced(edge); - - for(int j = edgeSet.size() - 1; j >= 1; --j){ + + debugListener.reduced(edge); + + for (int j = edgeSet.size() - 1; j >= 1; --j) { edge = edgeSet.get(j); stacksWithNonTerminalsToReduce.push(edge, resultStore); - - if(debugListener != null) debugListener.reduced(edge); + + debugListener.reduced(edge); } - + edgeSet.setLastVisitedLevel(location, resultStoreId); edgeSet.setLastResult(resultStore, resultStoreId); - }else{ + } + else { resultStore = edgeSet.getLastResult(resultStoreId); } - + resultStore.addAlternative(production, resultLink); } - + // Reuse these structures. private final IntegerList firstTimeRegistration = new IntegerList(); private final IntegerList firstTimeReductions = new IntegerList(); @@ -632,89 +759,99 @@ private void handleEdgeList(EdgesSet

edgeSet, String name, P production, Link /** * Handles reductions which may be associated with nesting restrictions. */ - private void handleEdgeListWithRestrictions(EdgesSet

edgeSet, String name, P production, Link resultLink, int startLocation, IntegerList filteredParents){ + private void handleEdgeListWithRestrictions(EdgesSet

edgeSet, String name, P production, Link resultLink, + int startLocation, IntegerList filteredParents) { // Only add the result to each resultstore once. - // Make sure each edge only gets added to the non-terminal reduction list once per level, by keeping track of them. + // Make sure each edge only gets added to the non-terminal reduction list once per level, by keeping + // track of them. firstTimeRegistration.clear(); firstTimeReductions.clear(); - for(int j = edgeSet.size() - 1; j >= 0; --j){ + for (int j = edgeSet.size() - 1; j >= 0; --j) { AbstractStackNode

edge = edgeSet.get(j); int resultStoreId = getResultStoreId(edge.getId()); - - if(!firstTimeReductions.contains(resultStoreId)){ - if(firstTimeRegistration.contains(resultStoreId)){ - if(debugListener != null) debugListener.filteredByNestingRestriction(edge); - + + if (!firstTimeReductions.contains(resultStoreId)) { + if (firstTimeRegistration.contains(resultStoreId)) { + debugListener.filteredByNestingRestriction(edge); + continue; } firstTimeRegistration.add(resultStoreId); - + // Check whether or not the nesting is allowed. - if(filteredParents == null || !filteredParents.contains(edge.getId())){ + if (filteredParents == null || !filteredParents.contains(edge.getId())) { AbstractContainerNode

resultStore = null; - if(edgeSet.getLastVisitedLevel(resultStoreId) == location){ + if (edgeSet.getLastVisitedLevel(resultStoreId) == location) { resultStore = edgeSet.getLastResult(resultStoreId); } - if(resultStore == null){ + if (resultStore == null) { if (edge.isRecovered()) { resultStore = new RecoveredNode

(inputURI, startLocation, location); - }else if (edge.isExpandable()) { - resultStore = new ExpandableContainerNode

(inputURI, startLocation, location, startLocation == location, edge.isSeparator(), edge.isLayout()); - }else { - resultStore = new SortContainerNode

(inputURI, startLocation, location, startLocation == location, edge.isSeparator(), edge.isLayout()); } - + else if (edge.isExpandable()) { + resultStore = new ExpandableContainerNode

(inputURI, startLocation, location, + startLocation == location, edge.isSeparator(), edge.isLayout()); + } + else { + resultStore = new SortContainerNode

(inputURI, startLocation, location, + startLocation == location, edge.isSeparator(), edge.isLayout()); + } + edgeSet.setLastVisitedLevel(location, resultStoreId); edgeSet.setLastResult(resultStore, resultStoreId); - + stacksWithNonTerminalsToReduce.push(edge, resultStore); firstTimeReductions.add(resultStoreId); } - + resultStore.addAlternative(production, resultLink); - - if(debugListener != null) debugListener.reduced(edge); - }else{ - if(debugListener != null) debugListener.filteredByNestingRestriction(edge); + + debugListener.reduced(edge); + } + else { + debugListener.filteredByNestingRestriction(edge); } - }else{ + } + else { AbstractContainerNode

resultStore = edgeSet.getLastResult(resultStoreId); stacksWithNonTerminalsToReduce.push(edge, resultStore); } } } - + /** * Move to the next symbol(s) in the production. */ - private void moveToNext(AbstractStackNode

node, AbstractNode result){ + private void moveToNext(AbstractStackNode

node, AbstractNode result) { int nextDot = node.getDot() + 1; AbstractStackNode

[] prod = node.getProduction(); AbstractStackNode

newNext = prod[nextDot]; AbstractStackNode

next = updateNextNode(newNext, node, result); - + // Handle alternative continuations of this production (related to prefix-sharing). AbstractStackNode

[][] alternateProds = node.getAlternateProductions(); - if(alternateProds != null){ + if (alternateProds != null) { IntegerObjectList> edgesMap = null; ArrayList[] prefixesMap = null; - if(next != null){ + if (next != null) { edgesMap = next.getEdges(); prefixesMap = next.getPrefixesMap(); } - - for(int i = alternateProds.length - 1; i >= 0; --i){ + + for (int i = alternateProds.length - 1; i >= 0; --i) { prod = alternateProds[i]; - if(nextDot == prod.length) continue; + if (nextDot == prod.length) + continue; AbstractStackNode

newAlternativeNext = prod[nextDot]; - - if(edgesMap != null){ + + if (edgesMap != null) { updateAlternativeNextNode(newAlternativeNext, node, result, edgesMap, prefixesMap); - }else{ + } + else { AbstractStackNode

alternativeNext = updateNextNode(newAlternativeNext, node, result); - - if(alternativeNext != null){ + + if (alternativeNext != null) { edgesMap = alternativeNext.getEdges(); prefixesMap = alternativeNext.getPrefixesMap(); } @@ -722,139 +859,186 @@ private void moveToNext(AbstractStackNode

node, AbstractNode result){ } } } - + /** - * Progress to the next 'states' associated with the given node. - * I.e. move to the next symbol(s) in the production (if available) and executed reductions if necessary. + * Progress to the next 'states' associated with the given node. I.e. move to the next symbol(s) in + * the production (if available) and executed reductions if necessary. */ - private void move(AbstractStackNode

node, AbstractNode result){ - if(debugListener != null) debugListener.moving(node, result); - + private void move(AbstractStackNode

node, AbstractNode result) { + debugListener.moving(node, result); + // Handle filtering. ICompletionFilter[] completionFilters = node.getCompletionFilters(); - if(completionFilters != null){ + if (completionFilters != null) { int startLocation = node.getStartLocation(); - for(int i = completionFilters.length - 1; i >= 0; --i){ - if(completionFilters[i].isFiltered(input, startLocation, location, positionStore)){ + for (int i = completionFilters.length - 1; i >= 0; --i) { + if (completionFilters[i].isFiltered(input, startLocation, location, positionStore)) { filteredNodes.push(node, result); - - if(debugListener != null) debugListener.filteredByCompletionFilter(node, result); - + + debugListener.filteredByCompletionFilter(node, result); + return; } } } - - if(node.isEndNode()){ - if(!result.isEmpty() || node.getId() == AbstractExpandableStackNode.DEFAULT_LIST_EPSILON_ID){ // Only go into the nullable fix path for nullables (special list epsilons can be ignored as well). + + if (node.isEndNode()) { + if (!result.isEmpty() || node.getId() == AbstractExpandableStackNode.DEFAULT_LIST_EPSILON_ID) { // Only go + // into the + // nullable + // fix path + // for + // nullables + // (special + // list + // epsilons + // can be + // ignored + // as well). updateEdges(node, result); - }else{ + } + else { updateNullableEdges(node, result); } } - - if(node.hasNext()){ + + if (node.hasNext()) { moveToNext(node, result); } } - + /** * Initiate the handling of stacks. */ - private void reduce(){ + private void reduceTerminals() { // Reduce terminals - while(!stacksWithTerminalsToReduce.isEmpty()){ + visualize("Reducing terminals", ParseStateVisualizer.TERMINALS_TO_REDUCE_ID); + while (!stacksWithTerminalsToReduce.isEmpty()) { move(stacksWithTerminalsToReduce.peekFirst(), stacksWithTerminalsToReduce.popSecond()); } - + } + + private void reduceNonTerminals() { // Reduce non-terminals - while(!stacksWithNonTerminalsToReduce.isEmpty()){ + visualize("Reducing non-terminals", ParseStateVisualizer.NON_TERMINALS_TO_REDUCE_ID); + while (!stacksWithNonTerminalsToReduce.isEmpty()) { move(stacksWithNonTerminalsToReduce.peekFirst(), stacksWithNonTerminalsToReduce.popSecond()); } } - + /** - * Locates the initial set of stacks that is queued for handling, for which the least amount of characters needs to be shifted. + * Locates the initial set of stacks that is queued for handling, for which the least amount of + * characters needs to be shifted. */ - private boolean findFirstStacksToReduce(){ - for(int i = 0; i < todoLists.length; ++i){ + private boolean findFirstStacksToReduce() { + for (int i = 0; i < todoLists.length; ++i) { DoubleStack, AbstractNode> terminalsTodo = todoLists[i]; - if(!(terminalsTodo == null || terminalsTodo.isEmpty())){ + if (!(terminalsTodo == null || terminalsTodo.isEmpty())) { stacksWithTerminalsToReduce = terminalsTodo; - + location += i; - + queueIndex = i; - + return true; } } - + if (recoverer != null) { - DoubleArrayList, AbstractNode> recoveredNodes = recoverer.reviveStacks(input, location, unexpandableNodes, unmatchableLeafNodes, unmatchableMidProductionNodes, filteredNodes); - if (recoveredNodes.size() > 0) { // TODO Do something with the revived node. Is this the right location to do this? + debugListener.reviving(input, location, unexpandableNodes, unmatchableLeafNodes, + unmatchableMidProductionNodes, filteredNodes); + visualize("Recovering", ParseStateVisualizer.ERROR_TRACKING_ID); + DoubleArrayList, AbstractNode> recoveredNodes = recoverer.reviveStacks(input, location, + unexpandableNodes, unmatchableLeafNodes, unmatchableMidProductionNodes, filteredNodes); + debugListener.revived(recoveredNodes); + if (recoveredNodes.size() > 0) { // TODO Do something with the revived node. Is this the right location to + // do this? for (int i = 0; i < recoveredNodes.size(); i++) { AbstractStackNode

recovered = recoveredNodes.getFirst(i); - addTodo(recovered, recovered.getLength(), recoveredNodes.getSecond(i)); + queueMatchableNode(recovered, recovered.getLength(), recoveredNodes.getSecond(i)); } + parseErrorRecovered = true; return findStacksToReduce(); } - - parseErrorOccured = true; + + parseErrorEncountered = true; } - + return false; } - + /** - * Locates the set of stacks that is queued for handling, for which the least amount of characters needs to be shifted. + * Locates the set of stacks that is queued for handling, for which the least amount of characters + * needs to be shifted. */ - private boolean findStacksToReduce(){ + private boolean findStacksToReduce() { + visualize("Finding stacks to reduce", ParseStateVisualizer.TODO_LISTS_ID); int queueDepth = todoLists.length; - for(int i = 1; i < queueDepth; ++i){ + for (int i = 1; i < queueDepth; ++i) { queueIndex = (queueIndex + 1) % queueDepth; - + DoubleStack, AbstractNode> terminalsTodo = todoLists[queueIndex]; - if(!(terminalsTodo == null || terminalsTodo.isEmpty())){ + if (!(terminalsTodo == null || terminalsTodo.isEmpty())) { + if (ParseStateVisualizer.VISUALIZATION_ENABLED) { + NodeId reduceNodeId = new NodeId("todo-" + i); + visualize("Found stack to reduce", reduceNodeId); + } + stacksWithTerminalsToReduce = terminalsTodo; - + location += i; - + return true; } } - - if (recoverer != null) { - DoubleArrayList, AbstractNode> recoveredNodes = recoverer.reviveStacks(input, location, unexpandableNodes, unmatchableLeafNodes, unmatchableMidProductionNodes, filteredNodes); + + if (recoverer != null && location < input.length) { + debugListener.reviving(input, location, unexpandableNodes, unmatchableLeafNodes, + unmatchableMidProductionNodes, filteredNodes); + visualize("Recovering", ParseStateVisualizer.ERROR_TRACKING_ID); + DoubleArrayList, AbstractNode> recoveredNodes = recoverer.reviveStacks(input, location, + unexpandableNodes, unmatchableLeafNodes, unmatchableMidProductionNodes, filteredNodes); + debugListener.revived(recoveredNodes); + if (ParseStateVisualizer.VISUALIZATION_ENABLED && visualizer != null) { + // Visualize state and include recovered nodes + visualizer.createGraph(this, "Reviving"); + visualizer.addRecoveredNodes(recoveredNodes); + visualizer.writeGraph(); + DebugUtil.opportunityToBreak(); + } + if (recoveredNodes.size() > 0) { for (int i = 0; i < recoveredNodes.size(); i++) { AbstractStackNode

recovered = recoveredNodes.getFirst(i); - - int levelsFromHere = recovered.getLength() - (location - recovered.getStartLocation()); - - addTodo(recovered, levelsFromHere, recoveredNodes.getSecond(i)); + debugListener.reviving(input, location, unexpandableNodes, unmatchableLeafNodes, + unmatchableMidProductionNodes, filteredNodes); + visualize("Queue recovery node", ParseStateVisualizer.getNodeId(recovered)); + queueRecoveryNode(recovered, recovered.getStartLocation(), recovered.getLength(), + recoveredNodes.getSecond(i)); } + parseErrorRecovered = true; return findStacksToReduce(); } - - parseErrorOccured = true; + + parseErrorEncountered = true; } - + return false; } - - public boolean parseErrorHasOccurred(){ - return parseErrorOccured; + + public boolean parseErrorHasOccurred() { + return parseErrorEncountered; } - + /** - * Inserts a stack bottom into the todo-list. + * Inserts a stack bottom into the todoList. */ @SuppressWarnings("unchecked") - private void addTodo(AbstractStackNode

node, int length, AbstractNode result){ - if(result == null) throw new RuntimeException(); + private void queueMatchableNode(AbstractStackNode

node, int length, AbstractNode result) { + assert result != null; + int queueDepth = todoLists.length; - if(length >= queueDepth){ + if (length >= queueDepth) { DoubleStack, AbstractNode>[] oldTodoLists = todoLists; todoLists = new DoubleStack[length + 1]; System.arraycopy(oldTodoLists, queueIndex, todoLists, 0, queueDepth - queueIndex); @@ -862,467 +1046,755 @@ private void addTodo(AbstractStackNode

node, int length, AbstractNode result) queueDepth = length + 1; queueIndex = 0; } - + int insertLocation = (queueIndex + length) % queueDepth; DoubleStack, AbstractNode> terminalsTodo = todoLists[insertLocation]; - if(terminalsTodo == null){ - terminalsTodo = new DoubleStack, AbstractNode>(); + if (terminalsTodo == null) { + terminalsTodo = new DoubleStack<>(); todoLists[insertLocation] = terminalsTodo; } terminalsTodo.push(node, result); } - + + /** + * Inserts a recovery node into the todoList, and possibly rewinds the parser to an earlier location + * in the input + */ + @SuppressWarnings("unchecked") + private void queueRecoveryNode(AbstractStackNode

node, int startPosition, int length, AbstractNode result) { + assert result != null; + + int queueDepth = todoLists.length; + + if (startPosition < location) { + // Have to reset the parser to an earlier location to at least + // be able to process the new node. Cannot throw away the queue, + // because there are possibly already other recovery tokens in the queue. + // However, we may assume that the queue before the current index is + // done, based on the way we cycle the queue now. The queue is + // looking forward to the future and we never re-use past entries. + + int negativeOffset = location - startPosition; + + DoubleStack, AbstractNode>[] oldTodoLists = todoLists; + todoLists = new DoubleStack[negativeOffset + Math.max(queueDepth, length) + 1]; + System.arraycopy(oldTodoLists, queueIndex, todoLists, negativeOffset, queueDepth - queueIndex); + System.arraycopy(oldTodoLists, 0, todoLists, queueDepth - queueIndex + negativeOffset, queueIndex); + + // reset the parser! + queueIndex = 0; + location = startPosition; + + DoubleStack, AbstractNode> terminalsTodo = todoLists[length]; + if (terminalsTodo == null) { + terminalsTodo = new DoubleStack<>(); + todoLists[length] = terminalsTodo; + } + + terminalsTodo.push(node, result); + } + else if (startPosition == location) { + // this is the normal case where new matchable nodes are discovered + // for the current parsing location, so reuse the code for queuing + queueMatchableNode(node, length, result); + } + else { + // This would mean we have discovered a recovery node for a location + // we have not been yet. That would be odd because then there would + // not have been a parse error and we wouldn't need recovery... + throw new RuntimeException("discovered a future recovery? " + node); + } + } + /** * Handles the retrieved alternatives for the given stack. */ - private boolean handleExpects(AbstractStackNode

[] expects, EdgesSet

cachedEdges, AbstractStackNode

stackBeingWorkedOn){ + private boolean handleExpects(AbstractStackNode

[] expects, EdgesSet

cachedEdges, + AbstractStackNode

stackBeingWorkedOn) { boolean hasValidAlternatives = false; - + sharedLastExpects.dirtyClear(); - - EXPECTS: for(int i = expects.length - 1; i >= 0; --i){ + + EXPECTS: for (int i = expects.length - 1; i >= 0; --i) { AbstractStackNode

first = expects[i]; - - if(first.isMatchable()){ // Eager matching optimization. + + if (first.isMatchable()) { // Eager matching optimization. int length = first.getLength(); int endLocation = location + length; - if(endLocation > input.length) continue; - + if (endLocation > input.length) + continue; + AbstractNode result = first.match(input, location); - if(result == null){ + if (result == null) { unmatchableLeafNodes.push(first); - - if(debugListener != null) debugListener.failedToMatch(first); - + + debugListener.failedToMatch(first); + continue; } - - if(debugListener != null) debugListener.matched(first, result); - + + debugListener.matched(first, result); + // Handle filtering. IEnterFilter[] enterFilters = first.getEnterFilters(); - if(enterFilters != null){ - for(int j = enterFilters.length - 1; j >= 0; --j){ - if(enterFilters[j].isFiltered(input, location, positionStore)){ - if(debugListener != null) debugListener.filteredByEnterFilter(first); - + if (enterFilters != null) { + for (int j = enterFilters.length - 1; j >= 0; --j) { + if (enterFilters[j].isFiltered(input, location, positionStore)) { + debugListener.filteredByEnterFilter(first); + continue EXPECTS; } } } - + first = first.getCleanCopyWithResult(location, result); - - addTodo(first, length, result); - }else{ + + queueMatchableNode(first, length, result); + } + else { first = first.getCleanCopy(location); stacksToExpand.push(first); } - + first.initEdges(); first.addEdges(cachedEdges, location); - + sharedLastExpects.add(first.getId(), first); - + hasValidAlternatives = true; - - if(debugListener != null) debugListener.expanded(stackBeingWorkedOn, first); + + debugListener.expanded(stackBeingWorkedOn, first); } - + return hasValidAlternatives; } - + /** * Check whether or not the given sort name has nesting restrictions associated with it. */ - protected boolean hasNestingRestrictions(String name){ + protected boolean hasNestingRestrictions(String name) { return false; // Priority and associativity filtering is off by default. } - + /** * Retrieves the set of disallowed parents for the given child. */ - protected IntegerList getFilteredParents(int childId){ + protected IntegerList getFilteredParents(int childId) { return null; // Default implementation; intended to be overwritten in sub-classes. } - + /** * Retrieves the resultstore id associated with the given id. */ - protected int getResultStoreId(int id){ + protected int getResultStoreId(int id) { return EdgesSet.DEFAULT_RESULT_STORE_ID; // Default implementation; intended to be overwritten in sub-classes. } - + /** * Expands the given stack node. */ - private void expandStack(AbstractStackNode

stack){ - if(debugListener != null) debugListener.expanding(stack); - + private void expandStack(AbstractStackNode

stack) { + debugListener.expanding(stack); + // Handle filtering. IEnterFilter[] enterFilters = stack.getEnterFilters(); - if(enterFilters != null){ - for(int i = enterFilters.length - 1; i >= 0; --i){ - if(enterFilters[i].isFiltered(input, location, positionStore)){ + if (enterFilters != null) { + for (int i = enterFilters.length - 1; i >= 0; --i) { + if (enterFilters[i].isFiltered(input, location, positionStore)) { unexpandableNodes.push(stack); - - if(debugListener != null) debugListener.filteredByEnterFilter(stack); - + + debugListener.filteredByEnterFilter(stack); + return; } } } - - if(stack.isMatchable()){ // Eager matching optimization related. - addTodo(stack, stack.getLength(), stack.getResult()); - }else if(!stack.isExpandable()){ // A 'normal' non-terminal. + + if (stack.isMatchable()) { // Eager matching optimization related. + queueMatchableNode(stack, stack.getLength(), stack.getResult()); + } + else if (!stack.isExpandable()) { // A 'normal' non-terminal. EdgesSet

cachedEdges = cachedEdgesForExpect.get(stack.getName()); - if(cachedEdges == null){ + if (cachedEdges == null) { cachedEdges = new EdgesSet

(1); cachedEdgesForExpect.put(stack.getName(), cachedEdges); - + AbstractStackNode

[] expects = invokeExpects(stack); - if(expects == null){ + if (expects == null) { unexpandableNodes.push(stack); return; } - - if(!handleExpects(expects, cachedEdges, stack)){ + + if (!handleExpects(expects, cachedEdges, stack)) { unexpandableNodes.push(stack); return; } - }else{ + } + else { int resultStoreId = getResultStoreId(stack.getId()); - if(cachedEdges.getLastVisitedLevel(resultStoreId) == location){ // Is nullable, add the known results. + if (cachedEdges.getLastVisitedLevel(resultStoreId) == location) { // Is nullable, add the known results. stacksWithNonTerminalsToReduce.push(stack, cachedEdges.getLastResult(resultStoreId)); - - if(debugListener != null) debugListener.foundIterationCachedNullableResult(stack); + + debugListener.foundIterationCachedNullableResult(stack); } } - + cachedEdges.add(stack); - + stack.setIncomingEdges(cachedEdges); - }else{ // Expandable + } + else { // Expandable EdgesSet

cachedEdges = cachedEdgesForExpect.get(stack.getName()); - if(cachedEdges == null){ + if (cachedEdges == null) { boolean expanded = false; - + cachedEdges = new EdgesSet

(); cachedEdgesForExpect.put(stack.getName(), cachedEdges); - + AbstractStackNode

[] listChildren = stack.getChildren(); - - CHILDREN : for(int i = listChildren.length - 1; i >= 0; --i){ + + CHILDREN: for (int i = listChildren.length - 1; i >= 0; --i) { AbstractStackNode

child = listChildren[i]; int childId = child.getId(); - - IntegerKeyedDoubleValueHashMap.Entry, DoubleArrayList, AbstractNode>> sharedChildEntry = sharedNextNodes.get(childId); - if(sharedChildEntry != null && sharedChildEntry.value2 == null){ + + IntegerKeyedDoubleValueHashMap.Entry, DoubleArrayList, AbstractNode>> sharedChildEntry = + sharedNextNodes.get(childId); + if (sharedChildEntry != null && sharedChildEntry.value2 == null) { AbstractStackNode

sharedChild = sharedChildEntry.value1; sharedChild.setEdgesSetWithPrefix(cachedEdges, null, location); - }else{ - if(child.isMatchable()){ + } + else { + if (child.isMatchable()) { int length = child.getLength(); int endLocation = location + length; - if(endLocation > input.length) continue; - + if (endLocation > input.length) + continue; + AbstractNode result = child.match(input, location); - if(result == null){ + if (result == null) { unmatchableLeafNodes.push(child); - - if(debugListener != null) debugListener.failedToMatch(child); - + + debugListener.failedToMatch(child); + continue; } - - if(debugListener != null) debugListener.matched(child, result); - + + debugListener.matched(child, result); + // Handle filtering IEnterFilter[] childEnterFilters = child.getEnterFilters(); - if(childEnterFilters != null){ - for(int j = childEnterFilters.length - 1; j >= 0; --j){ - if(childEnterFilters[j].isFiltered(input, location, positionStore)) { - if(debugListener != null) debugListener.filteredByEnterFilter(child); - + if (childEnterFilters != null) { + for (int j = childEnterFilters.length - 1; j >= 0; --j) { + if (childEnterFilters[j].isFiltered(input, location, positionStore)) { + debugListener.filteredByEnterFilter(child); + continue CHILDREN; } } } - + child = child.getCleanCopyWithResult(location, result); - addTodo(child, length, result); - }else{ + queueMatchableNode(child, length, result); + } + else { child = child.getCleanCopy(location); stacksToExpand.push(child); } - + child.initEdges(); child.setEdgesSetWithPrefix(cachedEdges, null, location); - + sharedNextNodes.putUnsafe(childId, child, null); - - if(debugListener != null) debugListener.expanded(stack, child); + + debugListener.expanded(stack, child); } - + expanded = true; } - - if(stack.canBeEmpty()){ // Star list or optional. - AbstractStackNode

empty = stack.getEmptyChild().getCleanCopyWithResult(location, EpsilonStackNode.EPSILON_RESULT); + + if (stack.canBeEmpty()) { // Star list or optional. + AbstractStackNode

empty = + stack.getEmptyChild().getCleanCopyWithResult(location, EpsilonStackNode.EPSILON_RESULT); empty.initEdges(); empty.addEdges(cachedEdges, location); - + stacksToExpand.push(empty); - - if(debugListener != null) debugListener.expanded(stack, empty); - + + debugListener.expanded(stack, empty); + expanded = true; } - - if(!expanded){ + + if (!expanded) { unexpandableNodes.push(stack); } } int resultStoreId = getResultStoreId(stack.getId()); - if(cachedEdges.getLastVisitedLevel(resultStoreId) == location){ // Is nullable, add the known results. + if (cachedEdges.getLastVisitedLevel(resultStoreId) == location) { // Is nullable, add the known results. stacksWithNonTerminalsToReduce.push(stack, cachedEdges.getLastResult(resultStoreId)); - if(debugListener != null) debugListener.foundIterationCachedNullableResult(stack); + debugListener.foundIterationCachedNullableResult(stack); } - + cachedEdges.add(stack); - + stack.setIncomingEdges(cachedEdges); } } - + /** * Initiate stack expansion for all queued stacks. */ - private void expand(){ - while(!stacksToExpand.isEmpty()){ + private void expand() { + visualize("Expanding", ParseStateVisualizer.STACKS_TO_EXPAND_ID); + while (!stacksToExpand.isEmpty()) { expandStack(stacksToExpand.pop()); } } - - protected AbstractNode parse(AbstractStackNode

startNode, URI inputURI, int[] input){ + + protected AbstractNode parse(AbstractStackNode

startNode, URI inputURI, int[] input) { return parse(startNode, inputURI, input, (IRecoverer

) null, (IDebugListener

) null); } - + /** * Initiates parsing. */ @SuppressWarnings("unchecked") - protected AbstractNode parse(AbstractStackNode

startNode, URI inputURI, int[] input, IRecoverer

recoverer, IDebugListener

debugListener){ - initTime(); + protected AbstractNode parse(AbstractStackNode

startNode, URI inputURI, int[] input, IRecoverer

recoverer, + IDebugListener

debugListener) { + if (debugListener == null) { + debugListener = new NopDebugListener<>(); + } + + initTime(); - try { + try { + + if (invoked) { + throw new RuntimeException("Can only invoke 'parse' once."); + } - if(invoked){ - throw new RuntimeException("Can only invoke 'parse' once."); - } + invoked = true; - invoked = true; + // Initialize. + this.inputURI = inputURI; + this.input = input; - // Initialize. - this.inputURI = inputURI; - this.input = input; + this.recoverer = recoverer; + this.debugListener = debugListener; - this.recoverer = recoverer; - this.debugListener = debugListener; + visualizer = ParseStateVisualizer.shouldVisualizeUri(inputURI) ? new ParseStateVisualizer("Parser") : null; - // Initialzed the position store. - positionStore.index(input); + // Initialzed the position store. + positionStore.index(input); - todoLists = new DoubleStack[DEFAULT_TODOLIST_CAPACITY]; + todoLists = new DoubleStack[DEFAULT_TODOLIST_CAPACITY]; - // Handle the initial expansion of the root node. - AbstractStackNode

rootNode = startNode; - rootNode.initEdges(); - stacksToExpand.push(rootNode); - lookAheadChar = (input.length > 0) ? input[0] : 0; + // Handle the initial expansion of the root node. + AbstractStackNode

rootNode = startNode; + rootNode.initEdges(); + stacksToExpand.push(rootNode); + lookAheadChar = (input.length > 0) ? input[0] : 0; - if(debugListener != null) debugListener.shifting(location, input, positionStore); + debugListener.shifting(location, input, positionStore); - expand(); + expand(); - if(findFirstStacksToReduce()){ - boolean shiftedLevel = (location != 0); + if (findFirstStacksToReduce()) { + boolean shiftedLevel = (location != 0); - do{ - lookAheadChar = (location < input.length) ? input[location] : 0; - if(shiftedLevel){ // Nullable fix for the first level. - sharedNextNodes.clear(); - cachedEdgesForExpect.clear(); + do { + lookAheadChar = (location < input.length) ? input[location] : 0; + if (shiftedLevel) { // Nullable fix for the first level. + sharedNextNodes.clear(); + cachedEdgesForExpect.clear(); - unexpandableNodes.dirtyClear(); - unmatchableLeafNodes.dirtyClear(); - unmatchableMidProductionNodes.dirtyClear(); - filteredNodes.dirtyClear(); + unexpandableNodes.dirtyClear(); + unmatchableLeafNodes.dirtyClear(); + unmatchableMidProductionNodes.dirtyClear(); + filteredNodes.dirtyClear(); - if(debugListener != null) debugListener.shifting(location, input, positionStore); - } + debugListener.shifting(location, input, positionStore); + } + // Reduce-expand loop. + do { + debugListener.iterating(); - // Reduce-expand loop. - do{ - if(debugListener != null) debugListener.iterating(); + reduceTerminals(); - reduce(); + reduceNonTerminals(); - expand(); - }while(!stacksWithNonTerminalsToReduce.isEmpty() || !stacksWithTerminalsToReduce.isEmpty()); + expand(); + } + while (!stacksWithNonTerminalsToReduce.isEmpty() || !stacksWithTerminalsToReduce.isEmpty()); - shiftedLevel = true; - }while(findStacksToReduce()); - } + shiftedLevel = true; + } + while (findStacksToReduce()); + } - // Check if we were successful. - if(location == input.length){ - EdgesSet

startNodeEdgesSet = startNode.getIncomingEdges(); - int resultStoreId = getResultStoreId(startNode.getId()); - if(startNodeEdgesSet != null && startNodeEdgesSet.getLastVisitedLevel(resultStoreId) == input.length){ - // Parsing succeeded. - return startNodeEdgesSet.getLastResult(resultStoreId); // Success. - } - } - } - finally { - checkTime("Parsing"); - } + visualize("Done", ParseStateVisualizer.PARSER_ID); - try { - // A parse error occured, and recovery failed as well - parseErrorOccured = true; - - int errorLocation = (location == Integer.MAX_VALUE ? 0 : location); - int line = positionStore.findLine(errorLocation); - int column = positionStore.getColumn(errorLocation, line); - if (location == input.length) { - throw new ParseError("Parse error", inputURI, errorLocation, 0, line + 1, line + 1, column, column, (Stack>) (Stack) unexpandableNodes, (Stack>) (Stack) unmatchableLeafNodes, (DoubleStack>, AbstractStackNode>) (DoubleStack) unmatchableMidProductionNodes, (DoubleStack, AbstractNode>) (DoubleStack) filteredNodes); - } - throw new ParseError("Parse error", inputURI, errorLocation, 1, line + 1, line + 1, column, column + 1, (Stack>) (Stack) unexpandableNodes, (Stack>) (Stack) unmatchableLeafNodes, (DoubleStack>, AbstractStackNode>) (DoubleStack) unmatchableMidProductionNodes, (DoubleStack, AbstractNode>) (DoubleStack) filteredNodes); - } - finally { - checkTime("Error handling"); - } + // Check if we were successful. + if (location == input.length) { + EdgesSet

startNodeEdgesSet = startNode.getIncomingEdges(); + int resultStoreId = getResultStoreId(startNode.getId()); + if (startNodeEdgesSet != null && startNodeEdgesSet.getLastVisitedLevel(resultStoreId) == input.length) { + // Parsing succeeded. + return startNodeEdgesSet.getLastResult(resultStoreId); // Success. + } + } + } + finally { + checkTime("Parsing"); + } + + try { + // A parse error occured, and recovery failed as well + parseErrorEncountered = true; + + int errorLocation = (location == Integer.MAX_VALUE ? 0 : location); + int line = positionStore.findLine(errorLocation); + int column = positionStore.getColumn(errorLocation, line); + if (location == input.length) { + throw new ParseError("Parse error", inputURI, errorLocation, 0, line + 1, line + 1, column, column, + (Stack>) (Stack) unexpandableNodes, + (Stack>) (Stack) unmatchableLeafNodes, + (DoubleStack>, AbstractStackNode>) (DoubleStack) unmatchableMidProductionNodes, + (DoubleStack, AbstractNode>) (DoubleStack) filteredNodes); + } + throw new ParseError("Parse error", inputURI, errorLocation, 1, line + 1, line + 1, column, column + 1, + (Stack>) (Stack) unexpandableNodes, + (Stack>) (Stack) unmatchableLeafNodes, + (DoubleStack>, AbstractStackNode>) (DoubleStack) unmatchableMidProductionNodes, + (DoubleStack, AbstractNode>) (DoubleStack) filteredNodes); + } + finally { + checkTime("Error handling"); + } } private void initTime() { - timestamp = System.nanoTime(); + timestamp = System.nanoTime(); } - - private void checkTime(String msg) { - long newStamp = System.nanoTime(); + + private void checkTime(String msg) { + long newStamp = System.nanoTime(); long duration = newStamp - timestamp; timestamp = newStamp; - + if (printTimes) { - System.err.println(msg + ": " + duration / (1000 * 1000)); + System.err.println(msg + ": " + duration / (1000 * 1000)); } - } + } - private static int[] charsToInts(char[] input){ + private static int[] charsToInts(char[] input) { int[] result = new int[Character.codePointCount(input, 0, input.length)]; int j = 0; - - for(int i = 0; i < input.length; i++){ + + for (int i = 0; i < input.length; i++) { if (!Character.isLowSurrogate(input[i])) { result[j++] = Character.codePointAt(input, i); } } - + return result; } /** * Parses with post parse filtering. */ - private T parse(String nonterminal, URI inputURI, int[] input, IActionExecutor actionExecutor, INodeFlattener converter, INodeConstructorFactory nodeConstructorFactory, IRecoverer

recoverer, IDebugListener

debugListener){ - AbstractNode result = parse(new NonTerminalStackNode

(AbstractStackNode.START_SYMBOL_ID, 0, nonterminal), inputURI, input, recoverer, debugListener); + private T parse(String nonterminal, URI inputURI, int[] input, IActionExecutor actionExecutor, + INodeFlattener converter, INodeConstructorFactory nodeConstructorFactory, IRecoverer

recoverer, + IDebugListener

debugListener) { + AbstractNode result = parse(new NonTerminalStackNode

(AbstractStackNode.START_SYMBOL_ID, 0, nonterminal), + inputURI, input, recoverer, debugListener); return buildResult(result, converter, nodeConstructorFactory, actionExecutor); } - - public T parse(String nonterminal, URI inputURI, char[] input, IActionExecutor actionExecutor, INodeFlattener converter, INodeConstructorFactory nodeConstructorFactory, IRecoverer

recoverer, IDebugListener

debugListener){ - return parse(nonterminal, inputURI, charsToInts(input), actionExecutor, converter, nodeConstructorFactory, recoverer, debugListener); + + public T parse(String nonterminal, URI inputURI, char[] input, IActionExecutor actionExecutor, + INodeFlattener converter, INodeConstructorFactory nodeConstructorFactory, IRecoverer

recoverer, + IDebugListener

debugListener) { + return parse(nonterminal, inputURI, charsToInts(input), actionExecutor, converter, nodeConstructorFactory, + recoverer, debugListener); } - - public T parse(String nonterminal, URI inputURI, char[] input, IActionExecutor actionExecutor, INodeFlattener converter, INodeConstructorFactory nodeConstructorFactory, IRecoverer

recoverer){ + + public T parse(String nonterminal, URI inputURI, char[] input, IActionExecutor actionExecutor, + INodeFlattener converter, INodeConstructorFactory nodeConstructorFactory, IRecoverer

recoverer) { return parse(nonterminal, inputURI, input, actionExecutor, converter, nodeConstructorFactory, recoverer, null); } - - public T parse(String nonterminal, URI inputURI, char[] input, IActionExecutor actionExecutor, INodeFlattener converter, INodeConstructorFactory nodeConstructorFactory, IDebugListener

debugListener){ - return parse(nonterminal, inputURI, input, actionExecutor, converter, nodeConstructorFactory, null, debugListener); + + public T parse(String nonterminal, URI inputURI, char[] input, IActionExecutor actionExecutor, + INodeFlattener converter, INodeConstructorFactory nodeConstructorFactory, + IDebugListener

debugListener) { + return parse(nonterminal, inputURI, input, actionExecutor, converter, nodeConstructorFactory, null, + debugListener); } - public T parse(String nonterminal, URI inputURI, char[] input, IActionExecutor actionExecutor, INodeFlattener converter, INodeConstructorFactory nodeConstructorFactory){ + public T parse(String nonterminal, URI inputURI, char[] input, IActionExecutor actionExecutor, + INodeFlattener converter, INodeConstructorFactory nodeConstructorFactory) { return parse(nonterminal, inputURI, input, actionExecutor, converter, nodeConstructorFactory, null, null); } /** * Parses without post parse filtering. */ - private T parse(String nonterminal, URI inputURI, int[] input, INodeFlattener converter, INodeConstructorFactory nodeConstructorFactory, IRecoverer

recoverer, IDebugListener

debugListener){ - AbstractNode result = parse(new NonTerminalStackNode

(AbstractStackNode.START_SYMBOL_ID, 0, nonterminal), inputURI, input, recoverer, debugListener); + private T parse(String nonterminal, URI inputURI, int[] input, INodeFlattener converter, + INodeConstructorFactory nodeConstructorFactory, IRecoverer

recoverer, + IDebugListener

debugListener) { + AbstractNode result = parse(new NonTerminalStackNode

(AbstractStackNode.START_SYMBOL_ID, 0, nonterminal), + inputURI, input, recoverer, debugListener); return buildResult(result, converter, nodeConstructorFactory, new VoidActionExecutor()); } - - public T parse(String nonterminal, URI inputURI, char[] input, INodeFlattener converter, INodeConstructorFactory nodeConstructorFactory, IRecoverer

recoverer, IDebugListener

debugListener){ - return parse(nonterminal, inputURI, charsToInts(input), converter, nodeConstructorFactory, recoverer, debugListener); + + public T parse(String nonterminal, URI inputURI, char[] input, INodeFlattener converter, + INodeConstructorFactory nodeConstructorFactory, IRecoverer

recoverer, + IDebugListener

debugListener) { + return parse(nonterminal, inputURI, charsToInts(input), converter, nodeConstructorFactory, recoverer, + debugListener); } - - public T parse(String nonterminal, URI inputURI, char[] input, INodeFlattener converter, INodeConstructorFactory nodeConstructorFactory, IRecoverer

recoverer){ + + public T parse(String nonterminal, URI inputURI, char[] input, INodeFlattener converter, + INodeConstructorFactory nodeConstructorFactory, IRecoverer

recoverer) { return parse(nonterminal, inputURI, input, converter, nodeConstructorFactory, recoverer, null); } - public T parse(String nonterminal, URI inputURI, char[] input, INodeFlattener converter, INodeConstructorFactory nodeConstructorFactory, IDebugListener

debugListener){ + public T parse(String nonterminal, URI inputURI, char[] input, INodeFlattener converter, + INodeConstructorFactory nodeConstructorFactory, IDebugListener

debugListener) { return parse(nonterminal, inputURI, input, converter, nodeConstructorFactory, null, debugListener); } - - public T parse(String nonterminal, URI inputURI, char[] input, INodeFlattener converter, INodeConstructorFactory nodeConstructorFactory) { + + public T parse(String nonterminal, URI inputURI, char[] input, INodeFlattener converter, + INodeConstructorFactory nodeConstructorFactory) { return parse(nonterminal, inputURI, charsToInts(input), converter, nodeConstructorFactory, null, null); } - - protected T parse(AbstractStackNode

startNode, URI inputURI, char[] input, INodeFlattener converter, INodeConstructorFactory nodeConstructorFactory) { - + + protected T parse(AbstractStackNode

startNode, URI inputURI, char[] input, INodeFlattener converter, + INodeConstructorFactory nodeConstructorFactory) { + AbstractNode result = parse(startNode, inputURI, charsToInts(input), null, null); - + return buildResult(result, converter, nodeConstructorFactory, new VoidActionExecutor()); } - + /** * Constructed the final parse result using the given converter. */ - protected T buildResult(AbstractNode result, INodeFlattener converter, INodeConstructorFactory nodeConstructorFactory, IActionExecutor actionExecutor){ - initTime(); - try { - FilteringTracker filteringTracker = new FilteringTracker(); - // Invoke the forest flattener, a.k.a. "the bulldozer". - Object rootEnvironment = actionExecutor != null ? actionExecutor.createRootEnvironment() : null; - T parseResult = null; - try { - parseResult = converter.convert(nodeConstructorFactory, result, positionStore, filteringTracker, actionExecutor, rootEnvironment); - } - finally { - actionExecutor.completed(rootEnvironment, (parseResult == null)); - } - if(parseResult != null){ - return parseResult; // Success. - } - - int offset = filteringTracker.getOffset(); - int endOffset = filteringTracker.getEndOffset(); - int length = endOffset - offset; - int beginLine = positionStore.findLine(offset); - int beginColumn = positionStore.getColumn(offset, beginLine); - int endLine = positionStore.findLine(endOffset); - int endColumn = positionStore.getColumn(endOffset, endLine); - throw new ParseError("All results were filtered", inputURI, offset, length, beginLine + 1, endLine + 1, beginColumn, endColumn); - } - finally { - checkTime("Unbinarizing, post-parse filtering, and mapping to UPTR"); - } + protected T buildResult(AbstractNode result, INodeFlattener converter, + INodeConstructorFactory nodeConstructorFactory, IActionExecutor actionExecutor) { + initTime(); + try { + FilteringTracker filteringTracker = new FilteringTracker(); + // Invoke the forest flattener, a.k.a. "the bulldozer". + Object rootEnvironment = actionExecutor != null ? actionExecutor.createRootEnvironment() : null; + T parseResult = null; + try { + parseResult = converter.convert(nodeConstructorFactory, result, positionStore, filteringTracker, + actionExecutor, rootEnvironment); + } + finally { + actionExecutor.completed(rootEnvironment, (parseResult == null)); + } + if (parseResult != null) { + if (recoverer != null && parseErrorRecovered) { + parseResult = introduceErrorNodes(parseResult, nodeConstructorFactory); + } + return parseResult; // Success. + } + + int offset = filteringTracker.getOffset(); + int endOffset = filteringTracker.getEndOffset(); + int length = endOffset - offset; + int beginLine = positionStore.findLine(offset); + int beginColumn = positionStore.getColumn(offset, beginLine); + int endLine = positionStore.findLine(endOffset); + int endColumn = positionStore.getColumn(endOffset, endLine); + throw new ParseError("All results were filtered", inputURI, offset, length, beginLine + 1, endLine + 1, + beginColumn, endColumn); + } + finally { + checkTime("Unbinarizing, post-parse filtering, and mapping to UPTR"); + } + } + + /** + * After parsing, parse trees will only contain `skipped` nodes. This post-processing step + * transforms the original tree into a more useful form. In essence, subtrees containing errors look + * like this after parsing: `appl(prod(S,[]), + * [,,...,appl(skipped([]))])` This method transforms these trees into: + * `appl(error(S,prod(S,[]),), [,,...,appl(skipped([]))])` + * This means productions that failed to parse can be recognized at the top level. Note that this + * can only be done when we know the actual type of T is IConstructor. + */ + @SuppressWarnings("unchecked") + private T introduceErrorNodes(T tree, INodeConstructorFactory nodeConstructorFactory) { + if (!(tree instanceof IConstructor)) { + return tree; + } + + return (T) introduceErrorNodes((IConstructor) tree, + (INodeConstructorFactory) nodeConstructorFactory); + } + + private IConstructor introduceErrorNodes(IConstructor tree, + INodeConstructorFactory nodeConstructorFactory) { + IConstructor result; + Type type = tree.getConstructorType(); + if (type == RascalValueFactory.Tree_Appl) { + result = fixErrorAppl((ITree) tree, nodeConstructorFactory); + } + else if (type == RascalValueFactory.Tree_Char) { + result = tree; + } + else if (type == RascalValueFactory.Tree_Amb) { + result = fixErrorAmb((ITree) tree, nodeConstructorFactory); + } + else if (type == RascalValueFactory.Tree_Cycle) { + result = tree; + } + else { + throw new RuntimeException("Unrecognized tree type: " + type); + } + + if (result != tree && tree.asWithKeywordParameters().hasParameter(RascalValueFactory.Location)) { + IValue loc = tree.asWithKeywordParameters().getParameter(RascalValueFactory.Location); + result = result.asWithKeywordParameters().setParameter(RascalValueFactory.Location, loc); + } + + return result; + } + + private IConstructor fixErrorAppl(ITree tree, + INodeConstructorFactory nodeConstructorFactory) { + IValue prod = TreeAdapter.getProduction(tree); + IList childList = TreeAdapter.getArgs(tree); + + ArrayList newChildren = null; + boolean errorTree = false; + int childCount = childList.length(); + for (int i = 0; i < childCount; i++) { + IConstructor child = (IConstructor) childList.get(i); + IConstructor newChild = null; + + // Last child could be a skipped child + if (i == childCount - 1 + && child.getConstructorType() == RascalValueFactory.Tree_Appl + && TreeAdapter.getProduction((ITree)child).getConstructorType() == RascalValueFactory.Production_Skipped) { + errorTree = true; + newChild = child; + } else { + newChild = introduceErrorNodes(child, nodeConstructorFactory); + } + + if (newChild != child || errorTree) { + if (newChildren == null) { + newChildren = new ArrayList<>(childCount); + for (int j=0; j nodeConstructorFactory) { + ISet alternativeSet = TreeAdapter.getAlternatives(tree); + ArrayList alternatives = new ArrayList<>(alternativeSet.size()); + boolean anyChanges = false; + for (IValue alt : alternativeSet) { + IConstructor newAlt = introduceErrorNodes((IConstructor) alt, nodeConstructorFactory); + if (newAlt != alt) { + anyChanges = true; + } + alternatives.add(newAlt); + } + + if (anyChanges) { + return nodeConstructorFactory.createAmbiguityNode(alternatives); + } + + return tree; + } + + /** + * Datastructure visualization for debugging purposes + */ + + private void visualize(String step, NodeId highlight) { + // Only visualize when debugging + if (ParseStateVisualizer.VISUALIZATION_ENABLED && visualizer != null) { + visualizer.createGraph(this, step); + if (highlight != null) { + visualizer.highlight(highlight); + } + visualizer.writeGraph(); + + DebugUtil.opportunityToBreak(); + } + } + + /** + * Getters used for graph generation for debugging (see DebugVisualizer) + */ + + public int[] getInput() { + return input; + } + + public int getLocation() { + return location; + } + + public int getLookAheadChar() { + return lookAheadChar; + } + + public DoubleStack, AbstractNode>[] getTodoLists() { + return todoLists; + } + + public int getQueueIndex() { + return queueIndex; + } + + public Stack> getStacksToExpand() { + return stacksToExpand; + } + + public DoubleStack, AbstractContainerNode

> getStacksWithNonTerminalsToReduce() { + return stacksWithNonTerminalsToReduce; + } + + public DoubleStack, AbstractNode> getStacksWithTerminalsToReduce() { + return stacksWithTerminalsToReduce; + } + + public Stack> getUnexpandableNodes() { + return unexpandableNodes; + } + + public Stack> getUnmatchableLeafNodes() { + return unmatchableLeafNodes; + } + + public DoubleStack, AbstractNode>, AbstractStackNode

> getUnmatchableMidProductionNodes() { + return unmatchableMidProductionNodes; + } + + public DoubleStack, AbstractNode> getFilteredNodes() { + return filteredNodes; } } diff --git a/src/org/rascalmpl/parser/gtd/debug/IDebugListener.java b/src/org/rascalmpl/parser/gtd/debug/IDebugListener.java index ae412bde870..550987600f8 100644 --- a/src/org/rascalmpl/parser/gtd/debug/IDebugListener.java +++ b/src/org/rascalmpl/parser/gtd/debug/IDebugListener.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2012-2013 CWI + * Copyright (c) 2012-2024 CWI * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -16,14 +16,17 @@ import org.rascalmpl.parser.gtd.result.struct.Link; import org.rascalmpl.parser.gtd.stack.AbstractStackNode; import org.rascalmpl.parser.gtd.stack.edge.EdgesSet; +import org.rascalmpl.parser.gtd.util.DoubleArrayList; +import org.rascalmpl.parser.gtd.util.DoubleStack; +import org.rascalmpl.parser.gtd.util.Stack; public interface IDebugListener

{ void shifting(int offset, int[] input, PositionStore positionStore); - + void iterating(); - + void matched(AbstractStackNode

node, AbstractNode result); - + void failedToMatch(AbstractStackNode

node); void expanding(AbstractStackNode

node); @@ -47,4 +50,14 @@ public interface IDebugListener

{ void filteredByEnterFilter(AbstractStackNode

node); void filteredByCompletionFilter(AbstractStackNode

node, AbstractNode result); + + void reviving(int[] input, + int location, + Stack> unexpandableNodes, + Stack> unmatchableLeafNodes, + DoubleStack, AbstractNode>, + AbstractStackNode

> unmatchableMidProductionNodes, + DoubleStack, AbstractNode> filteredNodes); + + void revived(DoubleArrayList, AbstractNode> recoveredNodes); } diff --git a/src/org/rascalmpl/parser/gtd/recovery/IRecoverer.java b/src/org/rascalmpl/parser/gtd/recovery/IRecoverer.java index f7db2b352ab..f4e7522b1d6 100644 --- a/src/org/rascalmpl/parser/gtd/recovery/IRecoverer.java +++ b/src/org/rascalmpl/parser/gtd/recovery/IRecoverer.java @@ -17,7 +17,22 @@ import org.rascalmpl.parser.gtd.util.DoubleStack; import org.rascalmpl.parser.gtd.util.Stack; -public interface IRecoverer

{ +public interface IRecoverer

{ + /** + * reviveStacks is called when the parser is unable to make more progress and the end-of-input + * has not been reached. The parameters provide insight into the current state of the parser + * and some of its history. With this information new stack nodes may be generated for the parser + * to continue with. It is up to the reviveStacks method to make sure the parser still generates + * derivation trees that cover the entire input. + * + * @param input the 24-bit unicode input character array + * @param location the current character offset in the input that the parser got stuck on + * @param unexpandableNodes these are non-terminals that were predicted at this location but did not fly + * @param unmatchableLeafNodes these are the terminals that were predicted but did not fly + * @param unmatchableMidProductionNodes these are quasi-non-terminals due to prefix sharing that did not fly + * @param filteredNodes these are non-terminals nodes that did not fly due to a disambiguation filter + * @return a list of new predictions for the parser to continue with + */ DoubleArrayList, AbstractNode> reviveStacks(int[] input, int location, Stack> unexpandableNodes, diff --git a/src/org/rascalmpl/parser/gtd/result/AbstractContainerNode.java b/src/org/rascalmpl/parser/gtd/result/AbstractContainerNode.java index 1a9d8373f59..f57f543ec94 100644 --- a/src/org/rascalmpl/parser/gtd/result/AbstractContainerNode.java +++ b/src/org/rascalmpl/parser/gtd/result/AbstractContainerNode.java @@ -15,6 +15,9 @@ import org.rascalmpl.parser.gtd.result.struct.Link; import org.rascalmpl.parser.gtd.util.ArrayList; +import org.rascalmpl.parser.util.DebugUtil; + +import io.usethesource.vallang.IConstructor; /** * All nodes in the resulting tree that can contain other nodes are a subtype @@ -147,4 +150,37 @@ public ArrayList

getAdditionalProductions(){ public ArrayList getAdditionalAlternatives(){ return alternatives; } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("offset=" + offset); + builder.append(",endOffset=" + endOffset); + if (isNullable) { + builder.append(",nullable"); + } + if (isSeparator) { + builder.append(",separator"); + } + if (isLayout) { + builder.append(",layout"); + } + if (firstAlternative != null) { + builder.append(",alternatives=["); + builder.append(firstAlternative); + builder.append(":"); + builder.append(DebugUtil.prodToString((IConstructor) firstProduction)); + + if (alternatives != null) { + for (int i=0; i implements INodeFlattener{ private final CharNodeFlattener charNodeConverter; private final LiteralNodeFlattener literalNodeConverter; private final SortContainerNodeFlattener sortContainerNodeConverter; private final ListContainerNodeFlattener listContainerNodeConverter; - private final RecoveryNodeFlattener recoveryNodeConverter; + private final SkippedNodeFlattener skippedNodeConverter; public DefaultNodeFlattener(){ super(); @@ -40,7 +39,7 @@ public DefaultNodeFlattener(){ literalNodeConverter = new LiteralNodeFlattener(); sortContainerNodeConverter = new SortContainerNodeFlattener(); listContainerNodeConverter = new ListContainerNodeFlattener(); - recoveryNodeConverter = new RecoveryNodeFlattener(); + skippedNodeConverter = new SkippedNodeFlattener(); } /** @@ -53,6 +52,7 @@ protected static class IsInError{ /** * Convert the given node. */ + @SuppressWarnings("unchecked") public T convert(INodeConstructorFactory nodeConstructorFactory, AbstractNode node, IndexedStack stack, int depth, CycleMark cycleMark, PositionStore positionStore, FilteringTracker filteringTracker, IActionExecutor actionExecutor, Object environment){ switch(node.getTypeIdentifier()){ case CharNode.ID: @@ -66,7 +66,7 @@ public T convert(INodeConstructorFactory nodeConstructorFactory, AbstractN case RecoveredNode.ID: return convert(nodeConstructorFactory, ((SortContainerNode) node).getFirstAlternative().getNode(), stack, depth, cycleMark, positionStore, filteringTracker, actionExecutor, environment); case SkippedNode.ID: - return recoveryNodeConverter.convertToUPTR(nodeConstructorFactory, (SkippedNode) node); + return skippedNodeConverter.convertToUPTR(nodeConstructorFactory, (SkippedNode) node, positionStore); default: throw new RuntimeException("Incorrect result node id: "+node.getTypeIdentifier()); } @@ -76,6 +76,6 @@ public T convert(INodeConstructorFactory nodeConstructorFactory, AbstractN * Converts the given parse tree to a tree in UPTR format. */ public T convert(INodeConstructorFactory nodeConstructorFactory, AbstractNode parseTree, PositionStore positionStore, FilteringTracker filteringTracker, IActionExecutor actionExecutor, Object rootEnvironment){ - return convert(nodeConstructorFactory, parseTree, new IndexedStack(), 0, new CycleMark(), positionStore, filteringTracker, actionExecutor, rootEnvironment); + return convert(nodeConstructorFactory, parseTree, new IndexedStack<>(), 0, new CycleMark(), positionStore, filteringTracker, actionExecutor, rootEnvironment); } } diff --git a/src/org/rascalmpl/parser/gtd/result/out/INodeConstructorFactory.java b/src/org/rascalmpl/parser/gtd/result/out/INodeConstructorFactory.java index e79ae43757b..1de4b441a58 100644 --- a/src/org/rascalmpl/parser/gtd/result/out/INodeConstructorFactory.java +++ b/src/org/rascalmpl/parser/gtd/result/out/INodeConstructorFactory.java @@ -38,8 +38,10 @@ public interface INodeConstructorFactory { T createListAmbiguityNode(ArrayList alternatives); - T createRecoveryNode(int[] characters); - + T createSkippedNode(int[] unrecognizedCharacters); + + T createErrorNode(ArrayList children, Object production); + ArrayList getChildren(T node); P createPositionInformation(URI input, int offset, int endOffset, PositionStore positionStore); diff --git a/src/org/rascalmpl/parser/gtd/result/out/RecoveryNodeFlattener.java b/src/org/rascalmpl/parser/gtd/result/out/SkippedNodeFlattener.java similarity index 52% rename from src/org/rascalmpl/parser/gtd/result/out/RecoveryNodeFlattener.java rename to src/org/rascalmpl/parser/gtd/result/out/SkippedNodeFlattener.java index 833da06dcd4..07b3629963d 100644 --- a/src/org/rascalmpl/parser/gtd/result/out/RecoveryNodeFlattener.java +++ b/src/org/rascalmpl/parser/gtd/result/out/SkippedNodeFlattener.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009-2013 CWI + * Copyright (c) 2009-2024 CWI * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -11,18 +11,28 @@ *******************************************************************************/ package org.rascalmpl.parser.gtd.result.out; +import org.rascalmpl.parser.gtd.location.PositionStore; import org.rascalmpl.parser.gtd.result.SkippedNode; /** * A converter for result nodes that contain skipped characters for error recovery */ -public class RecoveryNodeFlattener{ - - public RecoveryNodeFlattener(){ +public class SkippedNodeFlattener{ + public SkippedNodeFlattener(){ super(); } - - public T convertToUPTR(INodeConstructorFactory nodeConstructorFactory, SkippedNode node){ - return nodeConstructorFactory.createRecoveryNode(node.getSkippedChars()); + + public T convertToUPTR(INodeConstructorFactory nodeConstructorFactory, SkippedNode node, PositionStore positionStore){ + T result = nodeConstructorFactory.createSkippedNode(node.getSkippedChars()); + + // Add source location + if (node.getInputUri() != null) { + int startOffset = node.getOffset(); + int endOffset = startOffset + node.getLength(); + P sourceLocation = nodeConstructorFactory.createPositionInformation(node.getInputUri(), startOffset, endOffset, positionStore); + result = nodeConstructorFactory.addPositionInformation(result, sourceLocation); + } + + return result; } } diff --git a/src/org/rascalmpl/parser/gtd/result/struct/Link.java b/src/org/rascalmpl/parser/gtd/result/struct/Link.java index 105da47b9e0..22e805f91da 100644 --- a/src/org/rascalmpl/parser/gtd/result/struct/Link.java +++ b/src/org/rascalmpl/parser/gtd/result/struct/Link.java @@ -35,4 +35,8 @@ public ArrayList getPrefixes(){ public AbstractNode getNode(){ return node; } + + public String toString() { + return "Link[node=" + node + ", prefixes=" + (prefixes == null ? 0 : prefixes.size()) + "]"; + } } diff --git a/src/org/rascalmpl/parser/gtd/stack/AbstractMatchableStackNode.java b/src/org/rascalmpl/parser/gtd/stack/AbstractMatchableStackNode.java index 41951569714..2c165cbe3d0 100644 --- a/src/org/rascalmpl/parser/gtd/stack/AbstractMatchableStackNode.java +++ b/src/org/rascalmpl/parser/gtd/stack/AbstractMatchableStackNode.java @@ -21,49 +21,54 @@ * Literals and characters are examples of matchable nodes. */ public abstract class AbstractMatchableStackNode

extends AbstractStackNode

{ - + protected AbstractMatchableStackNode(int id, int dot){ super(id, dot); } - + + protected AbstractMatchableStackNode(int id, int dot, int startLocation){ + super(id, dot, startLocation); + } + protected AbstractMatchableStackNode(int id, int dot, IEnterFilter[] enterFilters, ICompletionFilter[] completionFilters){ super(id, dot, enterFilters, completionFilters); } - + protected AbstractMatchableStackNode(AbstractMatchableStackNode

original, int startLocation){ super(original, startLocation); } - + /** * Matches the node to the input string and the indicated location and * constructs the result in case the match was successful. Null will - * be returned otherwise. + * be returned otherwise. */ public abstract AbstractNode match(int[] input, int location); - + /** * Returns the length (in number of characters) of the matchable. */ public abstract int getLength(); - + public String getName(){ throw new UnsupportedOperationException(); } - + public AbstractStackNode

[] getChildren(){ throw new UnsupportedOperationException(); } - + public boolean canBeEmpty(){ throw new UnsupportedOperationException(); } - + public AbstractStackNode

getEmptyChild(){ throw new UnsupportedOperationException(); } - + + @Override public final boolean isMatchable(){ return true; } - + } diff --git a/src/org/rascalmpl/parser/gtd/stack/AbstractStackNode.java b/src/org/rascalmpl/parser/gtd/stack/AbstractStackNode.java index 562a2c1303c..fee21a394a7 100644 --- a/src/org/rascalmpl/parser/gtd/stack/AbstractStackNode.java +++ b/src/org/rascalmpl/parser/gtd/stack/AbstractStackNode.java @@ -11,6 +11,8 @@ *******************************************************************************/ package org.rascalmpl.parser.gtd.stack; +import java.util.Arrays; + import org.rascalmpl.parser.gtd.result.AbstractNode; import org.rascalmpl.parser.gtd.result.struct.Link; import org.rascalmpl.parser.gtd.stack.edge.EdgesSet; @@ -20,89 +22,98 @@ import org.rascalmpl.parser.gtd.util.BitSet; import org.rascalmpl.parser.gtd.util.IntegerList; import org.rascalmpl.parser.gtd.util.IntegerObjectList; +import org.rascalmpl.parser.util.DebugUtil; + +import io.usethesource.vallang.IConstructor; @SuppressWarnings({"unchecked", "cast"}) public abstract class AbstractStackNode

{ public static final int START_SYMBOL_ID = -1; public static final int DEFAULT_START_LOCATION = -1; - + protected AbstractStackNode

[] production; protected AbstractStackNode

[][] alternateProductions; - - protected IntegerObjectList> edgesMap; + + // Our edges + protected IntegerObjectList> edgesMap; // : key=startLocation, value=EdgesSet at that location + // Edges of our children protected ArrayList[] prefixesMap; - + protected EdgesSet

incomingEdges; - + protected final int id; protected final int dot; - + protected final int startLocation; - + // Flags private boolean isEndNode; private boolean isSeparator; private boolean isLayout; - + // Filters private final IEnterFilter[] enterFilters; private final ICompletionFilter[] completionFilters; - + // The production (specific to end nodes only) private P alternativeProduction; - + // Hidden-right-recursion related private BitSet propagatedPrefixes; private IntegerList propagatedReductions; - + protected AbstractStackNode(int id, int dot){ + this(id, dot, DEFAULT_START_LOCATION); + } + + protected AbstractStackNode(int id, int dot, int startLocation) { super(); - + this.id = id; this.dot = dot; - - this.startLocation = DEFAULT_START_LOCATION; - + + this.startLocation = startLocation; + this.enterFilters = null; this.completionFilters = null; } - + protected AbstractStackNode(int id, int dot, IEnterFilter[] enterFilters, ICompletionFilter[] completionFilters){ super(); - + this.id = id; this.dot = dot; - + this.startLocation = DEFAULT_START_LOCATION; - + this.enterFilters = enterFilters; this.completionFilters = completionFilters; } - + protected AbstractStackNode(AbstractStackNode

original, int startLocation){ this(original.id, original, startLocation); } - + protected AbstractStackNode(int id, AbstractStackNode

original, int startLocation){ super(); - + this.id = id; dot = original.dot; - + production = original.production; alternateProductions = original.alternateProductions; - + this.startLocation = startLocation; - + isEndNode = original.isEndNode; isSeparator = original.isSeparator; isLayout = original.isLayout; - + alternativeProduction = original.alternativeProduction; enterFilters = original.enterFilters; completionFilters = original.completionFilters; } - + // General. /** * Returns the id of the node. @@ -110,14 +121,14 @@ protected AbstractStackNode(int id, AbstractStackNode

original, int startLoca public int getId(){ return id; } - + /** * Returns the start location of this node. */ public int getStartLocation(){ return startLocation; } - + /** * Checks whether or not this node is the last node in the production. * Note that while this may be an end node alternative continuations of this production may be possible. @@ -125,46 +136,46 @@ public int getStartLocation(){ public boolean isEndNode(){ return isEndNode; } - + public boolean isRecovered() { return false; } - + /** * Mark this node as being a separator. */ public void markAsSeparator(){ isSeparator = true; } - + /** * Checks whether or not this nodes is a separator. */ public boolean isSeparator(){ return isSeparator; } - + /** * Marks this node as representing layout. */ public void markAsLayout(){ isLayout = true; } - + /** * Checks whether or not this node represents layout. */ public boolean isLayout(){ return isLayout; } - + /** * Checks whether this node represents a 'matchable' leaf node. */ public boolean isMatchable(){ return false; } - + /** * Check wheterh this node represents a node which needs to be expanded in-place. */ @@ -175,17 +186,17 @@ public boolean isExpandable(){ * Checks if this node represents a nullable leaf node. */ public abstract boolean isEmptyLeafNode(); - + /** * Returns the name associated with the symbol in this node (optional operation). */ public abstract String getName(); - + /** * Check whether of this this node is equal to the given node. */ public abstract boolean isEqual(AbstractStackNode

stackNode); - + // Last node specific stuff. /** * Associates a production with this node, indicating that this is the last node in the production. @@ -195,7 +206,7 @@ public void setAlternativeProduction(P parentProduction){ this.alternativeProduction = parentProduction; isEndNode = true; } - + /** * Retrieves the production associated with the alternative this node is a part of. * Only the last node in the alternative will have this production on it. @@ -203,7 +214,7 @@ public void setAlternativeProduction(P parentProduction){ public P getParentProduction(){ return alternativeProduction; } - + /** * Retrieves the enter filters associated with the symbol in this node. * The returned value may be null if no enter filters are present. @@ -211,7 +222,7 @@ public P getParentProduction(){ public IEnterFilter[] getEnterFilters(){ return enterFilters; } - + /** * Retrieves the completion filters associated with the symbol in this node. * The returned value may be null if no completion filters are present. @@ -219,7 +230,7 @@ public IEnterFilter[] getEnterFilters(){ public ICompletionFilter[] getCompletionFilters(){ return completionFilters; } - + /** * Checks whether or not the filters that are associated with this nodes symbol are equal to those of the given node. */ @@ -240,13 +251,13 @@ public boolean hasEqualFilters(AbstractStackNode

otherNode){ }else if(enterFilters != null){ return false; } - + ICompletionFilter[] otherCompletionFilters = otherNode.completionFilters; if(otherCompletionFilters != null){ if(completionFilters == null || completionFilters.length != otherCompletionFilters.length) { return false; } - + OUTER: for(int i = completionFilters.length - 1; i >= 0; --i){ ICompletionFilter completionFilter = completionFilters[i]; for(int j = otherCompletionFilters.length - 1; j >= 0; --j){ @@ -257,15 +268,15 @@ public boolean hasEqualFilters(AbstractStackNode

otherNode){ }else if(completionFilters != null){ return false; } - + return true; } - + /** * Children must override the hash code method (it can't be enforce, but I'm putting it in here anyway). */ public abstract int hashCode(); - + /** * Checks equality. */ @@ -275,20 +286,20 @@ public boolean equals(Object o){ } return false; } - + // Creation and sharing. /** * Creates a new copy of this node for the indicated position. */ public abstract AbstractStackNode

getCleanCopy(int startLocation); - + /** * Creates a new copy of this node for the indicated position and associated the given result with it. * This method has the same effect as the one above, but is exclusively used for matchable nodes; * as their results are constructed before the node is created. */ public abstract AbstractStackNode

getCleanCopyWithResult(int startLocation, AbstractNode result); - + /** * Checks whether or not this node has the same id as the given one. * This method is used when determining whether or not stacks should to be merged. @@ -296,7 +307,7 @@ public boolean equals(Object o){ public boolean isSimilar(AbstractStackNode

node){ return (node.id == id); } - + // Node continuations. /** * Indicates the current location in the alternative this node is a part of. @@ -304,14 +315,14 @@ public boolean isSimilar(AbstractStackNode

node){ public int getDot(){ return dot; } - + /** * Sets the main alternative this node is a part of. */ public void setProduction(AbstractStackNode

[] production){ this.production = production; } - + /** * Adds an additional alternative this node is a part of. * This can be the case if the symbol this node is associated with is located in a shared prefix of more then one alternative. @@ -331,28 +342,28 @@ public void addProduction(AbstractStackNode

[] production){ } } } - + /** * Checks if this node has possible continuations. */ public boolean hasNext(){ return !((production == null) || ((dot + 1) == production.length)); } - + /** * Retrieves the main alternative the symbol this node is associated with is a part of. */ public AbstractStackNode

[] getProduction(){ return production; } - + /** * Retrieves the alternative continuations the symbols this node is associated with is a part of. */ public AbstractStackNode

[][] getAlternateProductions(){ return alternateProductions; } - + // Edges & prefixes. /** * Initializes the set of edges of this node. @@ -360,14 +371,14 @@ public AbstractStackNode

[][] getAlternateProductions(){ public void initEdges(){ edgesMap = new IntegerObjectList<>(); } - + /** * Assigns the set of incoming edges to this stack node. Because of sharing there will always be only one of these sets. */ public void setIncomingEdges(EdgesSet

incomingEdges){ this.incomingEdges = incomingEdges; } - + /** * Adds the given edge to the set of edges for the indicated location. */ @@ -377,22 +388,22 @@ public EdgesSet

addEdge(AbstractStackNode

edge, int startLocation){ edges = new EdgesSet<>(1); edgesMap.add(startLocation, edges); } - + edges.add(edge); - + return edges; } - + /** * Adds the given set of edges to the edge map for the indicated location. */ public void addEdges(EdgesSet

edges, int startLocation){ edgesMap.add(startLocation, edges); } - + /** * Sets the given edges set for the indicated location and associated the given prefix with this node. - * + * * This method is used by children of expandable nodes; * expandable nodes contain dynamically unfolding alternatives, which can be cyclic. * A 'null' prefix, for example, indicates that the current node starts the alternative. @@ -411,9 +422,9 @@ public void setEdgesSetWithPrefix(EdgesSet

edges, Link prefix, int startLocat System.arraycopy(oldPrefixesMap, 0, prefixesMap, 0, edgesMapSize); } } - + edgesMap.add(startLocation, edges); - + ArrayList prefixes = prefixesMap[edgesMapSize]; if(prefixes == null){ prefixes = new ArrayList<>(1); @@ -421,7 +432,7 @@ public void setEdgesSetWithPrefix(EdgesSet

edges, Link prefix, int startLocat } prefixes.add(prefix); } - + /** * Add the given prefix at the indicated index. * This index is directly linked to a certain set of edges in the edge map. @@ -432,29 +443,29 @@ private void addPrefix(Link prefix, int index){ prefixes = new ArrayList<>(1); prefixesMap[index] = prefixes; } - + prefixes.add(prefix); } - + /** * Updates this node with information from its predecessor in the alternative. * The sets of edges on this predecessor will be transferred to this node and * the required prefixes for this node will be constructed, using the predecessor's * prefixes in combination with its given result. - * + * * This method also takes care of stack merges in the process of doing this. */ public void updateNode(AbstractStackNode

predecessor, AbstractNode predecessorResult){ IntegerObjectList> edgesMapToAdd = predecessor.edgesMap; ArrayList[] prefixesMapToAdd = predecessor.prefixesMap; - + if(edgesMap == null){ // Clean node, no stack merge occurred. // Initialize the edges map by cloning the one of the predecessor, as we need to transfer all these edges to this node. edgesMap = new IntegerObjectList<>(edgesMapToAdd); - + // Initialize the prefixes map. prefixesMap = new ArrayList[edgesMap.size()]; - + if(prefixesMapToAdd == null){ // The predecessor was the first node in the alternative, so the prefix of this node is just the predecessor's result. int index = edgesMap.findKey(predecessor.getStartLocation()); addPrefix(new Link(null, predecessorResult), index); @@ -466,7 +477,7 @@ public void updateNode(AbstractStackNode

predecessor, AbstractNode predecesso prefixes = new ArrayList<>(1); prefixesMap[i] = prefixes; } - + prefixes.add(new Link(prefixesMapToAdd[i], predecessorResult)); } } @@ -506,7 +517,7 @@ private void handleStackMergeForNonCyclicProduction(AbstractNode predecessorResu System.arraycopy(oldPrefixesMap, 0, prefixesMap, 0, edgesMapSize); } } - + if(prefixesMapToAdd == null){ // The predecessor was the first node in the alternative, so the prefix of this node is just the predecessor's result. addPrefix(new Link(null, predecessorResult), edgesMapSize); edgesMap.add(edgesMapToAdd.getKey(0), edgesMapToAdd.getValue(0)); @@ -518,38 +529,38 @@ private void handleStackMergeForNonCyclicProduction(AbstractNode predecessorResu if(index == -1){ // No prefix set for the given start location is present yet. index = edgesMap.size(); edgesMap.add(locationStart, edgesMapToAdd.getValue(i)); - + prefixes = new ArrayList<>(1); prefixesMap[index] = prefixes; }else{ // A prefix set for the given start location is present. prefixes = prefixesMap[index]; } - + // Add the prefix to the appropriate prefix set. prefixes.add(new Link(prefixesMapToAdd[i], predecessorResult)); } } } - + /** * This method is a specialized version of 'updateNode'. - * + * * In essence its function is the same, with the difference that stack merges are not handled * and the edges map is simply reused. - * + * * The is part of the 'selective edge sharing' optimization. * Since it is guaranteed that stack merges can never occur after non-nullable terminals * in a production it is save to use this assumption to improve efficiency. */ public void updateNodeAfterNonEmptyMatchable(AbstractStackNode

predecessor, AbstractNode result){ ArrayList[] prefixesMapToAdd = predecessor.prefixesMap; - + // Set the edges map. edgesMap = predecessor.edgesMap; - + // Initialize the prefixes map. prefixesMap = new ArrayList[edgesMap.size()]; - + if(prefixesMapToAdd == null){ // The predecessor was the first node in the alternative, so the prefix of this node is just the predecessor's result. addPrefix(new Link(null, result), edgesMap.findKey(predecessor.getStartLocation())); }else{ // The predecessor has prefixes. @@ -562,13 +573,13 @@ public void updateNodeAfterNonEmptyMatchable(AbstractStackNode

predecessor, A } } } - + /** * This method is a specialized version of 'updateNode'. - * + * * Specifically, this is a part of the hidden-right-recursion fix * (for non-nullable predecessor nodes). - * + * * It merges stacks and keeps track of which ones get merged * (the returned number indicated how many edge sets were added and * may need to be propagated forward). @@ -576,10 +587,10 @@ public void updateNodeAfterNonEmptyMatchable(AbstractStackNode

predecessor, A public int updateOvertakenNode(AbstractStackNode

predecessor, AbstractNode result){ IntegerObjectList> edgesMapToAdd = predecessor.edgesMap; ArrayList[] prefixesMapToAdd = predecessor.prefixesMap; - + // Initialize the prefixes map. int edgesMapSize = edgesMap.size(); - int possibleMaxSize = edgesMapSize + edgesMapSize; + int possibleMaxSize = edgesMapSize + edgesMapToAdd.size(); if(prefixesMap == null){ prefixesMap = new ArrayList[possibleMaxSize]; }else{ @@ -589,7 +600,7 @@ public int updateOvertakenNode(AbstractStackNode

predecessor, AbstractNode re System.arraycopy(oldPrefixesMap, 0, prefixesMap, 0, edgesMapSize); } } - + int nrOfAddedEdges = 0; if(prefixesMapToAdd == null){ // The predecessor was the first node in the alternative, so the prefix of this node is just the predecessor's result. addPrefix(new Link(null, result), edgesMapSize); @@ -598,36 +609,36 @@ public int updateOvertakenNode(AbstractStackNode

predecessor, AbstractNode re }else{ // The predecessor has prefixes. for(int i = edgesMapToAdd.size() - 1; i >= 0; --i){ int locationStart = edgesMapToAdd.getKey(i); - + // Prefix not present, add it. int index = edgesMap.findKey(locationStart); ArrayList prefixes; if(index == -1){ // No prefix set for the given start location is present yet. index = edgesMap.size(); edgesMap.add(locationStart, edgesMapToAdd.getValue(i)); - + prefixes = new ArrayList<>(1); prefixesMap[index] = prefixes; - + ++nrOfAddedEdges; }else{ // A prefix set for the given start location is present. prefixes = prefixesMap[index]; } - + // Add the prefix to the prefix set. prefixes.add(new Link(prefixesMapToAdd[i], result)); } } - + return nrOfAddedEdges; } - + /** * This method is a specialized version of 'updateNode'. - * + * * Specifically, this is a part of the hidden-right-recursion fix * (for nullable predecessor nodes). - * + * * It merges stacks and keeps track of which ones get merged * (the returned number indicated how many edge sets were added and * may need to be propagated forward). @@ -637,13 +648,13 @@ public int updateOvertakenNode(AbstractStackNode

predecessor, AbstractNode re public int updateOvertakenNullableNode(AbstractStackNode

predecessor, AbstractNode result, int potentialNewEdges){ IntegerObjectList> edgesMapToAdd = predecessor.edgesMap; ArrayList[] prefixesMapToAdd = predecessor.prefixesMap; - + // Initialize the prefixes map. int edgesMapSize = edgesMap.size(); int possibleMaxSize = edgesMapSize + potentialNewEdges; if(prefixesMap == null){ prefixesMap = new ArrayList[possibleMaxSize]; - + propagatedPrefixes = new BitSet(edgesMapSize); }else{ if(prefixesMap.length < possibleMaxSize){ @@ -651,14 +662,14 @@ public int updateOvertakenNullableNode(AbstractStackNode

predecessor, Abstrac prefixesMap = new ArrayList[possibleMaxSize]; System.arraycopy(oldPrefixesMap, 0, prefixesMap, 0, edgesMapSize); } - + if(propagatedPrefixes == null){ propagatedPrefixes = new BitSet(edgesMapSize); }else{ propagatedPrefixes.enlargeTo(possibleMaxSize); } } - + int nrOfAddedEdges = 0; if(prefixesMapToAdd == null){ // The predecessor was the first node in the alternative, so the prefix of this node is just the predecessor's result. // A prefix is not yet present, so add it. As it's the first and only possible nullable prefix, it's guaranteed that there aren't any prefixes for the current start location present yet. @@ -668,103 +679,157 @@ public int updateOvertakenNullableNode(AbstractStackNode

predecessor, Abstrac }else{ // The predecessor has prefixes. for(int i = edgesMapToAdd.size() - 1; i >= 0; --i){ int locationStart = edgesMapToAdd.getKey(i); - + // Prefix not present, add it. int index = edgesMap.findKey(locationStart); - + ArrayList prefixes; if(index == -1){ // No prefix set for the given start location is present yet. index = edgesMap.size(); edgesMap.add(locationStart, edgesMapToAdd.getValue(i)); propagatedPrefixes.set(index); - + prefixes = new ArrayList<>(1); prefixesMap[index] = prefixes; - + ++nrOfAddedEdges; }else{ // A prefix set for the given start location is present. if(propagatedPrefixes.isSet(index)) continue; // Prefix present, abort. - + prefixes = prefixesMap[index]; } - + // Add the prefix to the prefix set. prefixes.add(new Link(prefixesMapToAdd[i], result)); } } - + return nrOfAddedEdges; } - + /** * This method is a specialized version of 'updateNode'. - * + * * When alternatives have a shared prefix, their successors can shared the same edges and prefixes maps. */ public void updatePrefixSharedNode(IntegerObjectList> edgesMap, ArrayList[] prefixesMap){ this.edgesMap = edgesMap; this.prefixesMap = prefixesMap; } - + /** * Returns the edges map. */ public IntegerObjectList> getEdges(){ return edgesMap; } - + /** * Returns the incoming edges set. */ public EdgesSet

getIncomingEdges(){ return incomingEdges; } - + /** * Returns the prefixes map. */ public ArrayList[] getPrefixesMap(){ return prefixesMap; } - + /** * Returns the list of start location for which the results have already * been propagated (hidden-right-recursion specific). */ public IntegerList getPropagatedReductions(){ if(propagatedReductions == null) propagatedReductions = new IntegerList(); - + return propagatedReductions; } - + + public abstract String toShortString(); + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(id + "." + dot + "@" + startLocation); + if (production != null) { + builder.append(",prod=["); + boolean first = true; + for (AbstractStackNode

prodElem : production) { + if (first) { + first = false; + } else { + builder.append(","); + } + builder.append(prodElem.toShortString()); + } + builder.append("]"); + } + if (isEndNode) { + builder.append(",endNode"); + } + if (isSeparator) { + builder.append(",separator"); + } + if (isLayout) { + builder.append(",layout"); + } + + if (alternateProductions != null && alternateProductions.length != 0) { + builder.append(",alternateProductions=" + Arrays.toString(alternateProductions)); + } + if (prefixesMap != null && prefixesMap.length != 0) { + builder.append(",prefixes=" + Arrays.toString(prefixesMap)); + } + if (incomingEdges != null && incomingEdges.size() != 0) { + builder.append(",incomingEdges=" + incomingEdges); + } + if (alternativeProduction != null) { + builder.append(",alternativeProduction=" + DebugUtil.prodToString((IConstructor)alternativeProduction)); + } + if (propagatedPrefixes != null) { + builder.append(",propagatedPrefixes=" + propagatedPrefixes); + } + if (propagatedReductions != null) { + builder.append(",propagatedReductions=" + propagatedReductions); + } + + // Do not print filters for now. + + return builder.toString(); + } + + public abstract R accept(StackNodeVisitor visitor); + // Matchables. /** * Matches the symbol associated with this node to the input at the specified location. * This operation is only available on matchable nodes. */ public abstract AbstractNode match(int[] input, int location); - + /** * Returns the length of this node (only applies to matchables). */ public abstract int getLength(); - + /** * Returns the result associated with this node (only applies to matchables, after matching). */ public abstract AbstractNode getResult(); - + // Expandables. /** * Returns the set of children of this node (only applies to expandables). */ public abstract AbstractStackNode

[] getChildren(); - + /** * Returns whether or not this node can be nullable (only applies to expandables). */ public abstract boolean canBeEmpty(); - + /** * Returns the empty child of this node (only applies to expandables that are nullable). */ diff --git a/src/org/rascalmpl/parser/gtd/stack/AlternativeStackNode.java b/src/org/rascalmpl/parser/gtd/stack/AlternativeStackNode.java index 041e9273ce9..b3177c9c3bc 100644 --- a/src/org/rascalmpl/parser/gtd/stack/AlternativeStackNode.java +++ b/src/org/rascalmpl/parser/gtd/stack/AlternativeStackNode.java @@ -18,76 +18,82 @@ public class AlternativeStackNode

extends AbstractExpandableStackNode

{ private final P production; private final String name; - + private final AbstractStackNode

[] children; - + public AlternativeStackNode(int id, int dot, P production, AbstractStackNode

[] alternatives){ super(id, dot); - + this.production = production; this.name = String.valueOf(id); - + this.children = generateAlternatives(alternatives); } - + public AlternativeStackNode(int id, int dot, P production, AbstractStackNode

[] alternatives, IEnterFilter[] enterFilters, ICompletionFilter[] completionFilters){ super(id, dot, enterFilters, completionFilters); - + this.production = production; this.name = String.valueOf(id); - + this.children = generateAlternatives(alternatives); } - + private AlternativeStackNode(AlternativeStackNode

original, int startLocation){ super(original, startLocation); - + production = original.production; name = original.name; children = original.children; } - + /** * Generates and initializes the alternatives for this alternative. */ @SuppressWarnings("unchecked") private AbstractStackNode

[] generateAlternatives(AbstractStackNode

[] alternatives){ AbstractStackNode

[] children = (AbstractStackNode

[]) new AbstractStackNode[alternatives.length]; - + for(int i = alternatives.length - 1; i >= 0; --i){ AbstractStackNode

child = alternatives[i].getCleanCopy(DEFAULT_START_LOCATION); AbstractStackNode

[] prod = (AbstractStackNode

[]) new AbstractStackNode[]{child}; child.setProduction(prod); child.setAlternativeProduction(production); - + children[i] = child; } - + return children; } - + public String getName(){ return name; } - + public AbstractStackNode

getCleanCopy(int startLocation){ return new AlternativeStackNode

(this, startLocation); } - + public AbstractStackNode

[] getChildren(){ return children; } - + public boolean canBeEmpty(){ return false; } - + public AbstractStackNode

getEmptyChild(){ throw new UnsupportedOperationException(); } + @Override + public String toShortString() { + return name; + } + + @Override public String toString(){ StringBuilder sb = new StringBuilder(); sb.append("alt"); @@ -95,21 +101,33 @@ public String toString(){ sb.append('('); sb.append(startLocation); sb.append(')'); - + return sb.toString(); } - + + @Override public int hashCode(){ return production.hashCode(); } - + + @Override + public boolean equals(Object peer) { + return super.equals(peer); + } + public boolean isEqual(AbstractStackNode

stackNode){ if(!(stackNode instanceof AlternativeStackNode)) return false; - + AlternativeStackNode

otherNode = (AlternativeStackNode

) stackNode; if(!production.equals(otherNode.production)) return false; - + return hasEqualFilters(stackNode); } + + @Override + public R accept(StackNodeVisitor visitor) { + return visitor.visit(this); + } + } diff --git a/src/org/rascalmpl/parser/gtd/stack/CaseInsensitiveLiteralStackNode.java b/src/org/rascalmpl/parser/gtd/stack/CaseInsensitiveLiteralStackNode.java index 9bf625a33fb..e4b30bb166e 100644 --- a/src/org/rascalmpl/parser/gtd/stack/CaseInsensitiveLiteralStackNode.java +++ b/src/org/rascalmpl/parser/gtd/stack/CaseInsensitiveLiteralStackNode.java @@ -18,72 +18,71 @@ public final class CaseInsensitiveLiteralStackNode

extends AbstractMatchableStackNode

{ private final Object production; - + private final int[][] ciLiteral; - + private final AbstractNode result; - + public CaseInsensitiveLiteralStackNode(int id, int dot, Object production, int[] ciLiteral){ super(id, dot); - + this.production = production; - + this.ciLiteral = fill(ciLiteral); - + result = null; } - + public CaseInsensitiveLiteralStackNode(int id, int dot, P production, int[] ciLiteral, IEnterFilter[] enterFilters, ICompletionFilter[] completionFilters){ super(id, dot, enterFilters, completionFilters); - + this.production = production; - + this.ciLiteral = fill(ciLiteral); - + result = null; } - + private CaseInsensitiveLiteralStackNode(CaseInsensitiveLiteralStackNode

original, int startLocation){ super(original, startLocation); - + production = original.production; - + ciLiteral = original.ciLiteral; - + result = null; } - + private CaseInsensitiveLiteralStackNode(CaseInsensitiveLiteralStackNode

original, int startLocation, AbstractNode result){ super(original, startLocation); - + this.production = original.production; - + this.ciLiteral = original.ciLiteral; - + this.result = result; } - + private static int[][] fill(int[] ciLiteral){ int nrOfCharacters = ciLiteral.length; int[][] ciLiteralResult = new int[nrOfCharacters][]; for(int i = nrOfCharacters - 1; i >= 0; --i){ int character = ciLiteral[i]; - int type = Character.getType(character); - if(type == Character.LOWERCASE_LETTER){ + if (Character.isLowerCase(character)) { ciLiteralResult[i] = new int[]{character, Character.toUpperCase(character)}; - }else if(type == Character.UPPERCASE_LETTER){ + } else if(Character.isUpperCase(character)) { ciLiteralResult[i] = new int[]{character, Character.toLowerCase(character)}; - }else{ + } else { ciLiteralResult[i] = new int[]{character}; } } return ciLiteralResult; } - + public boolean isEmptyLeafNode(){ return false; } - + public AbstractNode match(int[] input, int location){ int literalLength = ciLiteral.length; int[] resultLiteral = new int[literalLength]; @@ -98,26 +97,40 @@ public AbstractNode match(int[] input, int location){ } return null; // Did not match. } - + return new LiteralNode(production, resultLiteral); } - + public AbstractStackNode

getCleanCopy(int startLocation){ - return new CaseInsensitiveLiteralStackNode

(this, startLocation); + return new CaseInsensitiveLiteralStackNode<>(this, startLocation); } - + public AbstractStackNode

getCleanCopyWithResult(int startLocation, AbstractNode result){ - return new CaseInsensitiveLiteralStackNode

(this, startLocation, result); + return new CaseInsensitiveLiteralStackNode<>(this, startLocation, result); } - + public int getLength(){ return ciLiteral.length; } - + public AbstractNode getResult(){ return result; } - + + public int[][] getLiteral() { + return ciLiteral; + } + + @Override + public String toShortString() { + int[] codePoints = new int[ciLiteral.length]; + for (int i=0; i stackNode){ if(!(stackNode instanceof CaseInsensitiveLiteralStackNode)) return false; - + CaseInsensitiveLiteralStackNode

otherNode = (CaseInsensitiveLiteralStackNode

) stackNode; - + if(!production.equals(otherNode.production)) return false; - + return hasEqualFilters(stackNode); } + + @Override + public R accept(StackNodeVisitor visitor) { + return visitor.visit(this); + } + } diff --git a/src/org/rascalmpl/parser/gtd/stack/CharStackNode.java b/src/org/rascalmpl/parser/gtd/stack/CharStackNode.java index ff2d8b2e326..73aeca2258c 100644 --- a/src/org/rascalmpl/parser/gtd/stack/CharStackNode.java +++ b/src/org/rascalmpl/parser/gtd/stack/CharStackNode.java @@ -18,119 +18,149 @@ public final class CharStackNode

extends AbstractMatchableStackNode

{ private final int[][] ranges; - + private final AbstractNode result; - + public CharStackNode(int id, int dot, int[][] ranges){ super(id, dot); this.ranges = ranges; - + result = null; } - + public CharStackNode(int id, int dot, int[][] ranges, IEnterFilter[] enterFilters, ICompletionFilter[] completionFilters){ super(id, dot, enterFilters, completionFilters); this.ranges = ranges; - + result = null; } - + private CharStackNode(CharStackNode

original, int startLocation){ super(original, startLocation); - + ranges = original.ranges; - + result = null; } - + private CharStackNode(CharStackNode

original, int startLocation, AbstractNode result){ super(original, startLocation); - + this.ranges = original.ranges; - + this.result = result; } - + public boolean isEmptyLeafNode(){ return false; } - + public AbstractNode match(int[] input, int location){ int next = input[location]; - + for(int i = ranges.length - 1; i >= 0; --i){ int[] range = ranges[i]; if(next >= range[0] && next <= range[1]){ return CharNode.createCharNode(next); } } - + return null; } - + public AbstractStackNode

getCleanCopy(int startLocation){ - return new CharStackNode

(this, startLocation); + return new CharStackNode<>(this, startLocation); } - + public AbstractStackNode

getCleanCopyWithResult(int startLocation, AbstractNode result){ - return new CharStackNode

(this, startLocation, result); + return new CharStackNode<>(this, startLocation, result); } - + public int getLength(){ return 1; } - + public AbstractNode getResult(){ return result; } - + + @Override + public String toShortString() { + StringBuilder sb = new StringBuilder(); + for (int i=0; i 0) { + sb.append(','); + } + + int[] range = ranges[i]; + sb.append(codePointToString(range[0])); + if (range[0] != range[1]) { + sb.append('-'); + sb.append(codePointToString(range[1])); + } + } + + return sb.toString(); + } + + private String codePointToString(int codePoint) { + if (Character.isLetterOrDigit(codePoint)) { + return new String(Character.toChars(codePoint)); + } + + return String.valueOf(codePoint); + } + + @Override public String toString(){ StringBuilder sb = new StringBuilder(); - - sb.append('['); + + sb.append("CharStackNode[class="); int[] range = ranges[0]; sb.append(range[0]); sb.append('-'); sb.append(range[1]); - for(int i = ranges.length - 2; i >= 0; --i){ + for(int i = 1; i= 0; --i){ int[] range = ranges[i]; hash = hash << 3 + hash >> 5; hash ^= range[0] + (range[1] << 2); } - + return hash; } - + + @Override + public boolean equals(Object peer) { + return super.equals(peer); + } + public boolean isEqual(AbstractStackNode

stackNode){ if(!(stackNode instanceof CharStackNode)) return false; - + CharStackNode

otherNode = (CharStackNode

) stackNode; - + int[][] otherRanges = otherNode.ranges; if(ranges.length != otherRanges.length) return false; - + OUTER: for(int i = ranges.length - 1; i >= 0; --i){ int[] range = ranges[i]; for(int j = otherRanges.length - 1; j >= 0; --j){ @@ -140,7 +170,13 @@ public boolean isEqual(AbstractStackNode

stackNode){ return false; // Could not find a certain range. } // Found all ranges. - + return hasEqualFilters(stackNode); } + + @Override + public R accept(StackNodeVisitor visitor) { + return visitor.visit(this); + } + } diff --git a/src/org/rascalmpl/parser/gtd/stack/EmptyStackNode.java b/src/org/rascalmpl/parser/gtd/stack/EmptyStackNode.java index 79297837d49..1b6faefadd7 100644 --- a/src/org/rascalmpl/parser/gtd/stack/EmptyStackNode.java +++ b/src/org/rascalmpl/parser/gtd/stack/EmptyStackNode.java @@ -25,78 +25,97 @@ public final class EmptyStackNode

extends AbstractExpandableStackNode

{ private final AbstractStackNode

emptyChild; private static final AbstractStackNode[] children = new AbstractStackNode[0]; - + public EmptyStackNode(int id, int dot, P production){ super(id, dot); - + this.production = production; this.name = "empty"+id; // Add the id to make it unique. - + this.emptyChild = generateEmptyChild(); } - + public EmptyStackNode(int id, int dot, P production, IEnterFilter[] enterFilters, ICompletionFilter[] completionFilters) { super(id, dot, enterFilters, completionFilters); - + this.production = production; this.name = "empty"+id; - this.emptyChild = generateEmptyChild(); + this.emptyChild = generateEmptyChild(); } - + private EmptyStackNode(EmptyStackNode

original, int startLocation){ super(original, startLocation); - + production = original.production; name = original.name; emptyChild = original.emptyChild; } - + @SuppressWarnings("unchecked") private AbstractStackNode

generateEmptyChild(){ AbstractStackNode

empty = (AbstractStackNode

) EMPTY.getCleanCopy(DEFAULT_START_LOCATION); empty.setAlternativeProduction(production); return empty; } - + public String getName(){ return name; } - + public AbstractStackNode

getCleanCopy(int startLocation){ return new EmptyStackNode

(this, startLocation); } - + @SuppressWarnings("unchecked") public AbstractStackNode

[] getChildren(){ return (AbstractStackNode

[]) children; } - + public boolean canBeEmpty(){ return true; } - + public AbstractStackNode

getEmptyChild(){ return emptyChild; } + @Override + public String toShortString() { + return name; + } + + @Override public String toString(){ StringBuilder sb = new StringBuilder(); sb.append(name); sb.append('('); sb.append(startLocation); sb.append(')'); - + return sb.toString(); } - + + @Override public int hashCode(){ return 1; } - + + @Override + public boolean equals(Object peer) { + return super.equals(peer); + } + + @Override public boolean isEqual(AbstractStackNode

stackNode){ if(!(stackNode instanceof EmptyStackNode)) return false; - + return hasEqualFilters(stackNode); } + + @Override + public R accept(StackNodeVisitor visitor) { + return visitor.visit(this); + } + } diff --git a/src/org/rascalmpl/parser/gtd/stack/EpsilonStackNode.java b/src/org/rascalmpl/parser/gtd/stack/EpsilonStackNode.java index dfa16c05fed..b89ffda0933 100644 --- a/src/org/rascalmpl/parser/gtd/stack/EpsilonStackNode.java +++ b/src/org/rascalmpl/parser/gtd/stack/EpsilonStackNode.java @@ -18,73 +18,91 @@ public final class EpsilonStackNode

extends AbstractMatchableStackNode

{ public final static EpsilonNode EPSILON_RESULT = new EpsilonNode(); - + private final AbstractNode result; - + public EpsilonStackNode(int id, int dot){ super(id, dot); - + result = null; } - + public EpsilonStackNode(int id, int dot, IEnterFilter[] enterFilters, ICompletionFilter[] completionFilters){ super(id, dot, enterFilters, completionFilters); - + result = null; } - + private EpsilonStackNode(EpsilonStackNode

original, int startLocation){ super(original, startLocation); - + result = null; } - + private EpsilonStackNode(EpsilonStackNode

original, int startLocation, AbstractNode result){ super(original, startLocation); - + this.result = result; } - + public boolean isEmptyLeafNode(){ return true; } - + public AbstractNode match(int[] input, int location){ return EPSILON_RESULT; } - + public AbstractStackNode

getCleanCopy(int startLocation){ - return new EpsilonStackNode

(this, startLocation); + return new EpsilonStackNode<>(this, startLocation); } - + public AbstractStackNode

getCleanCopyWithResult(int startLocation, AbstractNode result){ - return new EpsilonStackNode

(this, startLocation, result); + return new EpsilonStackNode<>(this, startLocation, result); } - + public int getLength(){ return 0; } - + public AbstractNode getResult(){ return result; } - + + @Override + public String toShortString() { + return "\u03B5"; + } + + @Override public String toString(){ StringBuilder sb = new StringBuilder(); sb.append('('); sb.append(startLocation); sb.append(')'); - + return sb.toString(); } - + + @Override public int hashCode(){ return 0; } - + + @Override + public boolean equals(Object peer) { + return super.equals(peer); + } + public boolean isEqual(AbstractStackNode

stackNode){ if(!(stackNode instanceof EpsilonStackNode)) return false; - + return hasEqualFilters(stackNode); } + + @Override + public R accept(StackNodeVisitor visitor) { + return visitor.visit(this); + } + } diff --git a/src/org/rascalmpl/parser/gtd/stack/ListStackNode.java b/src/org/rascalmpl/parser/gtd/stack/ListStackNode.java index 7f5734b2a10..2c2479230bb 100644 --- a/src/org/rascalmpl/parser/gtd/stack/ListStackNode.java +++ b/src/org/rascalmpl/parser/gtd/stack/ListStackNode.java @@ -21,37 +21,37 @@ public final class ListStackNode

extends AbstractExpandableStackNode

{ private final AbstractStackNode

[] children; private final AbstractStackNode

emptyChild; - + public ListStackNode(int id, int dot, P production, AbstractStackNode

child, boolean isPlusList){ super(id, dot); - + this.production = production; this.name = String.valueOf(id); // Add the id to make it unique. - + this.children = generateChildren(child); this.emptyChild = isPlusList ? null : generateEmptyChild(); } - + public ListStackNode(int id, int dot, P production, AbstractStackNode

child, boolean isPlusList, IEnterFilter[] enterFilters, ICompletionFilter[] completionFilters){ super(id, dot, enterFilters, completionFilters); - + this.production = production; this.name = String.valueOf(id); // Add the id to make it unique. - + this.children = generateChildren(child); this.emptyChild = isPlusList ? null : generateEmptyChild(); } - + private ListStackNode(ListStackNode

original, int startLocation){ super(original, startLocation); - + production = original.production; name = original.name; children = original.children; emptyChild = original.emptyChild; } - + /** * Generates and initializes the alternative for this list. */ @@ -62,7 +62,7 @@ private AbstractStackNode

[] generateChildren(AbstractStackNode

child){ listNode.setProduction((AbstractStackNode

[]) new AbstractStackNode[]{listNode, listNode}); return (AbstractStackNode

[]) new AbstractStackNode[]{listNode}; } - + /** * Generates and initializes the empty child for this list (in case this is a star list). */ @@ -72,48 +72,66 @@ private AbstractStackNode

generateEmptyChild(){ empty.setAlternativeProduction(production); return empty; } - + public String getName(){ return name; } - + public AbstractStackNode

getCleanCopy(int startLocation){ - return new ListStackNode

(this, startLocation); + return new ListStackNode<>(this, startLocation); } - + public AbstractStackNode

[] getChildren(){ return children; } - + public boolean canBeEmpty(){ return emptyChild != null; } - + public AbstractStackNode

getEmptyChild(){ return emptyChild; } + @Override + public String toShortString() { + return name; + } + + @Override public String toString(){ StringBuilder sb = new StringBuilder(); sb.append(name); sb.append('('); sb.append(startLocation); sb.append(')'); - + return sb.toString(); } - + + @Override public int hashCode(){ return production.hashCode(); } - + + @Override + public boolean equals(Object peer) { + return super.equals(peer); + } + public boolean isEqual(AbstractStackNode

stackNode){ if(!(stackNode instanceof ListStackNode)) return false; - + ListStackNode

otherNode = (ListStackNode

) stackNode; - + if(!production.equals(otherNode.production)) return false; - + return hasEqualFilters(stackNode); } + + @Override + public R accept(StackNodeVisitor visitor) { + return visitor.visit(this); + } + } diff --git a/src/org/rascalmpl/parser/gtd/stack/LiteralStackNode.java b/src/org/rascalmpl/parser/gtd/stack/LiteralStackNode.java index 0dafc11f539..20ce6afeb52 100644 --- a/src/org/rascalmpl/parser/gtd/stack/LiteralStackNode.java +++ b/src/org/rascalmpl/parser/gtd/stack/LiteralStackNode.java @@ -19,88 +19,108 @@ public final class LiteralStackNode

extends AbstractMatchableStackNode

{ private final int[] literal; private final P production; - + private final LiteralNode result; - + public LiteralStackNode(int id, int dot, P production, int[] literal){ super(id, dot); - + this.literal = literal; this.production = production; - + result = new LiteralNode(production, literal); } - + public LiteralStackNode(int id, int dot, P production, int[] literal, IEnterFilter[] enterFilters, ICompletionFilter[] completionFilters){ super(id, dot, enterFilters, completionFilters); - + this.literal = literal; this.production = production; - + result = new LiteralNode(production, literal); } - + + public int[] getLiteral() { + return literal; + } + private LiteralStackNode(LiteralStackNode

original, int startLocation){ super(original, startLocation); - + literal = original.literal; production = original.production; - + result = original.result; } - + public boolean isEmptyLeafNode(){ return false; } - + public AbstractNode match(int[] input, int location){ for(int i = literal.length - 1; i >= 0; --i){ if(literal[i] != input[location + i]) return null; // Did not match. } - + return result; } - + public AbstractStackNode

getCleanCopy(int startLocation){ return new LiteralStackNode

(this, startLocation); } - + public AbstractStackNode

getCleanCopyWithResult(int startLocation, AbstractNode result){ return new LiteralStackNode

(this, startLocation); } - + public int getLength(){ return literal.length; } - + public AbstractNode getResult(){ return result; } - + + @Override + public String toShortString() { + return "'" + new String(literal, 0, literal.length) + "'"; + } + + @Override public String toString(){ - StringBuilder sb = new StringBuilder(); - for (int i : literal) { - sb.appendCodePoint(i); - } - sb.append(getId()); - sb.append('('); - sb.append(startLocation); - sb.append(')'); - + StringBuilder sb = new StringBuilder("lit['"); + sb.append(new String(literal, 0, literal.length)); + sb.append("',"); + sb.append(super.toString()); + + sb.append(']'); + return sb.toString(); } - + + @Override public int hashCode(){ return production.hashCode(); } - + + @Override + public boolean equals(Object peer) { + return super.equals(peer); + } + public boolean isEqual(AbstractStackNode

stackNode){ if(!(stackNode instanceof LiteralStackNode)) return false; - + LiteralStackNode

otherNode = (LiteralStackNode

) stackNode; - + if(!production.equals(otherNode.production)) return false; - + return hasEqualFilters(stackNode); } + + @Override + public R accept(StackNodeVisitor visitor) { + return visitor.visit(this); + } + } diff --git a/src/org/rascalmpl/parser/gtd/stack/MultiCharacterStackNode.java b/src/org/rascalmpl/parser/gtd/stack/MultiCharacterStackNode.java index 6543b14f9e1..9f185d7116d 100644 --- a/src/org/rascalmpl/parser/gtd/stack/MultiCharacterStackNode.java +++ b/src/org/rascalmpl/parser/gtd/stack/MultiCharacterStackNode.java @@ -18,62 +18,62 @@ public class MultiCharacterStackNode

extends AbstractMatchableStackNode

{ private final P production; - + private final int[][] characters; - + private final AbstractNode result; - + public MultiCharacterStackNode(int id, int dot, P production, int[][] characters){ super(id, dot); - + this.production = production; - + this.characters = characters; - + result = null; } - + public MultiCharacterStackNode(int id, int dot, P production, int[][] characters, IEnterFilter[] enterFilters, ICompletionFilter[] completionFilters){ super(id, dot, enterFilters, completionFilters); - + this.production = production; - + this.characters = characters; - + result = null; } - + private MultiCharacterStackNode(MultiCharacterStackNode

original, int startLocation){ super(original, startLocation); - + production = original.production; - + characters = original.characters; - + result = null; } - + private MultiCharacterStackNode(MultiCharacterStackNode

original, int startLocation, AbstractNode result){ super(original, startLocation); - + this.production = original.production; - + this.characters = original.characters; - + this.result = result; } - + public boolean isEmptyLeafNode(){ return false; } - + public AbstractNode match(int[] input, int location){ int nrOfCharacters = characters.length; int[] resultArray = new int[nrOfCharacters]; - + OUTER : for(int i = nrOfCharacters - 1; i >= 0; --i){ int next = input[location + i]; - + int[] alternatives = characters[i]; for(int j = alternatives.length - 1; j >= 0; --j){ int alternative = alternatives[j]; @@ -84,29 +84,29 @@ public AbstractNode match(int[] input, int location){ } return null; } - + return new LiteralNode(production, resultArray); } - + public AbstractStackNode

getCleanCopy(int startLocation){ return new MultiCharacterStackNode

(this, startLocation); } - + public AbstractStackNode

getCleanCopyWithResult(int startLocation, AbstractNode result){ return new MultiCharacterStackNode

(this, startLocation, result); } - + public int getLength(){ return 1; } - + public AbstractNode getResult(){ return result; } - - public String toString(){ + + @Override + public String toShortString() { StringBuilder sb = new StringBuilder(); - sb.append('['); for(int i = characters.length - 1; i >= 0; --i){ int[] range = characters[i]; @@ -115,42 +115,47 @@ public String toString(){ sb.append(range[1]); } sb.append(']'); - - sb.append(getId()); - sb.append('('); - sb.append(startLocation); - sb.append(')'); - - return sb.toString(); - } - + return toString(); + } + + @Override + public String toString(){ + return toShortString(); + } + + @Override public int hashCode(){ int hash = 0; - + for(int i = characters.length - 1; i >= 0; --i){ int[] chars = characters[i]; - for(int j = chars.length - 1; j <= 0; --j){ + for(int j = chars.length - 1; j >= 0; --j){ hash = hash << 3 + hash >> 5; hash ^= chars[0] + (chars[1] << 2); } } - + return hash; } - + + @Override + public boolean equals(Object peer) { + return super.equals(peer); + } + public boolean isEqual(AbstractStackNode

stackNode){ if(!(stackNode instanceof MultiCharacterStackNode)) return false; - + MultiCharacterStackNode

otherNode = (MultiCharacterStackNode

) stackNode; - + int[][] otherCharacters = otherNode.characters; if(characters.length != otherCharacters.length) return false; - + for(int i = characters.length - 1; i >= 0; --i){ int[] chars = characters[i]; int[] otherChars = otherCharacters[i]; if(chars.length != otherChars.length) return false; - + POS: for(int j = chars.length - 1; j <= 0; --j){ int c = chars[j]; for(int k = otherChars.length - 1; k <= 0; --k){ @@ -160,7 +165,13 @@ public boolean isEqual(AbstractStackNode

stackNode){ } } // Found all characters. - + return hasEqualFilters(stackNode); } + + @Override + public R accept(StackNodeVisitor visitor) { + return visitor.visit(this); + } + } diff --git a/src/org/rascalmpl/parser/gtd/stack/NonTerminalStackNode.java b/src/org/rascalmpl/parser/gtd/stack/NonTerminalStackNode.java index 7f00fe8397b..caeccd8012b 100644 --- a/src/org/rascalmpl/parser/gtd/stack/NonTerminalStackNode.java +++ b/src/org/rascalmpl/parser/gtd/stack/NonTerminalStackNode.java @@ -17,87 +17,104 @@ public final class NonTerminalStackNode

extends AbstractStackNode

{ private final String expectIdentifier; - + public NonTerminalStackNode(int id, int dot, String expectIdentifier){ super(id, dot); - + this.expectIdentifier = expectIdentifier; } - + public NonTerminalStackNode(int id, int dot, String expectIdentifier, IEnterFilter[] enterFilters, ICompletionFilter[] completionFilters){ super(id, dot, enterFilters, completionFilters); - + this.expectIdentifier = expectIdentifier; } - + private NonTerminalStackNode(NonTerminalStackNode

original, int startLocation){ super(original, startLocation); - + expectIdentifier = original.expectIdentifier; } - + public boolean isEmptyLeafNode(){ return false; } - + public String getName(){ return expectIdentifier; } - + public AbstractNode match(int[] input, int location){ throw new UnsupportedOperationException(); } - + public AbstractStackNode

getCleanCopy(int startLocation){ - return new NonTerminalStackNode

(this, startLocation); + return new NonTerminalStackNode<>(this, startLocation); } - + public AbstractStackNode

getCleanCopyWithResult(int startLocation, AbstractNode result){ throw new UnsupportedOperationException(); } - + public int getLength(){ throw new UnsupportedOperationException(); } - + public AbstractStackNode

[] getChildren(){ throw new UnsupportedOperationException(); } - + public boolean canBeEmpty(){ throw new UnsupportedOperationException(); } - + public AbstractStackNode

getEmptyChild(){ throw new UnsupportedOperationException(); } - + public AbstractNode getResult(){ throw new UnsupportedOperationException(); } - public String toString(){ - StringBuilder sb = new StringBuilder(); + @Override + public String toShortString() { + return expectIdentifier; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("NonTerminal["); sb.append(expectIdentifier); - sb.append(getId()); - sb.append('('); - sb.append(startLocation); - sb.append(')'); - + sb.append(","); + sb.append(super.toString()); + sb.append("]"); + return sb.toString(); } - + + @Override public int hashCode(){ return expectIdentifier.hashCode(); } - + + @Override + public boolean equals(Object peer) { + return super.equals(peer); + } + public boolean isEqual(AbstractStackNode

stackNode){ if(!(stackNode instanceof NonTerminalStackNode)) return false; - + NonTerminalStackNode

otherNode = (NonTerminalStackNode

) stackNode; if(!expectIdentifier.equals(otherNode.expectIdentifier)) return false; - + return hasEqualFilters(stackNode); } + + @Override + public R accept(StackNodeVisitor visitor) { + return visitor.visit(this); + } + } diff --git a/src/org/rascalmpl/parser/gtd/stack/OptionalStackNode.java b/src/org/rascalmpl/parser/gtd/stack/OptionalStackNode.java index 4b916f2a3be..51787846b6a 100644 --- a/src/org/rascalmpl/parser/gtd/stack/OptionalStackNode.java +++ b/src/org/rascalmpl/parser/gtd/stack/OptionalStackNode.java @@ -18,40 +18,40 @@ public final class OptionalStackNode

extends AbstractExpandableStackNode

{ private final P production; private final String name; - + private final AbstractStackNode

[] children; private final AbstractStackNode

emptyChild; - + public OptionalStackNode(int id, int dot, P production, AbstractStackNode

optional){ super(id, dot); - + this.production = production; this.name = String.valueOf(id); // Add the id to make it unique. - + this.children = generateChildren(optional); this.emptyChild = generateEmptyChild(); } - + public OptionalStackNode(int id, int dot, P production, AbstractStackNode

optional, IEnterFilter[] enterFilters, ICompletionFilter[] completionFilters){ super(id, dot, enterFilters, completionFilters); - + this.production = production; this.name = String.valueOf(id); // Add the id to make it unique. - + this.children = generateChildren(optional); this.emptyChild = generateEmptyChild(); } - + private OptionalStackNode(OptionalStackNode

original, int startLocation){ super(original, startLocation); - + production = original.production; name = original.name; - + children = original.children; emptyChild = original.emptyChild; } - + /** * Generates and initializes the alternative for this optional. */ @@ -61,7 +61,7 @@ private AbstractStackNode

[] generateChildren(AbstractStackNode

optional){ child.setAlternativeProduction(production); return (AbstractStackNode

[]) new AbstractStackNode[]{child}; } - + /** * Generates and initializes the empty child for this optional. */ @@ -71,48 +71,66 @@ private AbstractStackNode

generateEmptyChild(){ empty.setAlternativeProduction(production); return empty; } - + public String getName(){ return name; } - + public AbstractStackNode

getCleanCopy(int startLocation){ return new OptionalStackNode

(this, startLocation); } - + public AbstractStackNode

[] getChildren(){ return children; } - + public boolean canBeEmpty(){ return true; } - + public AbstractStackNode

getEmptyChild(){ return emptyChild; } + @Override + public String toShortString() { + return name; + } + + @Override public String toString(){ StringBuilder sb = new StringBuilder(); sb.append(name); sb.append('('); sb.append(startLocation); sb.append(')'); - + return sb.toString(); } - + + @Override public int hashCode(){ return production.hashCode(); } - + + @Override + public boolean equals(Object peer) { + return super.equals(peer); + } + public boolean isEqual(AbstractStackNode

stackNode){ if(!(stackNode instanceof OptionalStackNode)) return false; - + OptionalStackNode

otherNode = (OptionalStackNode

) stackNode; - + if(!production.equals(otherNode.production)) return false; - + return hasEqualFilters(stackNode); } + + @Override + public R accept(StackNodeVisitor visitor) { + return visitor.visit(this); + } + } diff --git a/src/org/rascalmpl/parser/gtd/stack/RecoveryPointStackNode.java b/src/org/rascalmpl/parser/gtd/stack/RecoveryPointStackNode.java index 60daa28bc93..4e19828d913 100644 --- a/src/org/rascalmpl/parser/gtd/stack/RecoveryPointStackNode.java +++ b/src/org/rascalmpl/parser/gtd/stack/RecoveryPointStackNode.java @@ -21,7 +21,7 @@ public class RecoveryPointStackNode

extends AbstractStackNode

{ public RecoveryPointStackNode(int id, P parent, AbstractStackNode

robustNode){ super(id, robustNode, robustNode.startLocation); this.prefixesMap = robustNode.prefixesMap; - + // Modify the production, so it ends 'here'. int productionLength = robustNode.dot + 1; if(productionLength == robustNode.production.length){ @@ -30,27 +30,27 @@ public RecoveryPointStackNode(int id, P parent, AbstractStackNode

robustNode) this.production = (AbstractStackNode

[]) new AbstractStackNode[productionLength]; System.arraycopy(robustNode.production, 0, this.production, 0, productionLength); } - + this.parent = parent; this.name = "recovery " + id; this.edgesMap = robustNode.edgesMap; } - + @Override public P getParentProduction() { return parent; } - + @Override public boolean isRecovered() { return true; } - + @Override public boolean isEmptyLeafNode(){ return false; } - + @Override public boolean isEndNode() { return true; @@ -58,41 +58,47 @@ public boolean isEndNode() { @Override public String getName(){ - return "***robust:" + name + "***"; + return name; } - + public AbstractNode match(int[] input, int location){ throw new UnsupportedOperationException(); } - + public AbstractStackNode

getCleanCopy(int startLocation){ throw new UnsupportedOperationException(); } - + public AbstractStackNode

getCleanCopyWithResult(int startLocation, AbstractNode result){ throw new UnsupportedOperationException(); } - + public int getLength(){ throw new UnsupportedOperationException(); } - + public AbstractStackNode

[] getChildren(){ throw new UnsupportedOperationException(); } - + public boolean canBeEmpty(){ throw new UnsupportedOperationException(); } - + public AbstractStackNode

getEmptyChild(){ throw new UnsupportedOperationException(); } - + public AbstractNode getResult(){ throw new UnsupportedOperationException(); } + @Override + public String toShortString() { + return name; + } + + @Override public String toString(){ StringBuilder sb = new StringBuilder(); sb.append(getName()); @@ -101,19 +107,31 @@ public String toString(){ sb.append('('); sb.append(startLocation); sb.append(')'); - + return sb.toString(); } - + + @Override public int hashCode(){ return getName().hashCode(); } - + + @Override + public boolean equals(Object peer) { + return super.equals(peer); + } + public boolean isEqual(AbstractStackNode

stackNode){ if(!(stackNode instanceof RecoveryPointStackNode)) return false; - + RecoveryPointStackNode

otherNode = (RecoveryPointStackNode

) stackNode; return otherNode.name.equals(name) && otherNode.startLocation == startLocation; } + + @Override + public R accept(StackNodeVisitor visitor) { + return visitor.visit(this); + } + } diff --git a/src/org/rascalmpl/parser/gtd/stack/SeparatedListStackNode.java b/src/org/rascalmpl/parser/gtd/stack/SeparatedListStackNode.java index 38adc9cf619..fc4b3f6ca21 100644 --- a/src/org/rascalmpl/parser/gtd/stack/SeparatedListStackNode.java +++ b/src/org/rascalmpl/parser/gtd/stack/SeparatedListStackNode.java @@ -21,38 +21,41 @@ public final class SeparatedListStackNode

extends AbstractExpandableStackNode private final AbstractStackNode

[] children; private final AbstractStackNode

emptyChild; - + public SeparatedListStackNode(int id, int dot, P production, AbstractStackNode

child, AbstractStackNode

[] separators, boolean isPlusList){ super(id, dot); - + this.production = production; this.name = String.valueOf(id); // Add the id to make it unique. - + this.children = generateChildren(child, separators); this.emptyChild = isPlusList ? null : generateEmptyChild(); } - + + public P getListProduction() { + return production; + } public SeparatedListStackNode(int id, int dot, P production, AbstractStackNode

child, AbstractStackNode

[] separators, boolean isPlusList, IEnterFilter[] enterFilters, ICompletionFilter[] completionFilters){ super(id, dot, enterFilters, completionFilters); - + this.production = production; this.name = String.valueOf(id); // Add the id to make it unique. - + this.children = generateChildren(child, separators); this.emptyChild = isPlusList ? null : generateEmptyChild(); } - + private SeparatedListStackNode(SeparatedListStackNode

original, int startLocation){ super(original, startLocation); - + production = original.production; name = original.name; - + children = original.children; emptyChild = original.emptyChild; } - + /** * Generates and initializes the alternative for this separated list. */ @@ -60,10 +63,10 @@ private SeparatedListStackNode(SeparatedListStackNode

original, int startLoca private AbstractStackNode

[] generateChildren(AbstractStackNode

child, AbstractStackNode

[] separators){ AbstractStackNode

listNode = child.getCleanCopy(DEFAULT_START_LOCATION); listNode.setAlternativeProduction(production); - + int numberOfSeparators = separators.length; AbstractStackNode

[] prod = (AbstractStackNode

[]) new AbstractStackNode[numberOfSeparators + 2]; - + listNode.setProduction(prod); prod[0] = listNode; // Start for(int i = numberOfSeparators - 1; i >= 0; --i){ @@ -73,10 +76,10 @@ private AbstractStackNode

[] generateChildren(AbstractStackNode

child, Abst prod[i + 1] = separator; } prod[numberOfSeparators + 1] = listNode; // End - + return (AbstractStackNode

[]) new AbstractStackNode[]{listNode}; } - + /** * Generates and initializes the empty child for this separated list (if it's a star list). */ @@ -86,48 +89,66 @@ private AbstractStackNode

generateEmptyChild(){ empty.setAlternativeProduction(production); return empty; } - + public String getName(){ return name; } - + public AbstractStackNode

getCleanCopy(int startLocation){ return new SeparatedListStackNode

(this, startLocation); } - + public AbstractStackNode

[] getChildren(){ return children; } - + public boolean canBeEmpty(){ return emptyChild != null; } - + public AbstractStackNode

getEmptyChild(){ return emptyChild; } + @Override + public String toShortString() { + return name; + } + + @Override public String toString(){ StringBuilder sb = new StringBuilder(); sb.append(name); sb.append('('); sb.append(startLocation); sb.append(')'); - + return sb.toString(); } - + + @Override public int hashCode(){ return production.hashCode(); } - + + @Override + public boolean equals(Object peer) { + return super.equals(peer); + } + public boolean isEqual(AbstractStackNode

stackNode){ if(!(stackNode instanceof SeparatedListStackNode)) return false; - + SeparatedListStackNode

otherNode = (SeparatedListStackNode

) stackNode; - + if(!production.equals(otherNode.production)) return false; - + return hasEqualFilters(stackNode); } + + @Override + public R accept(StackNodeVisitor visitor) { + return visitor.visit(this); + } + } diff --git a/src/org/rascalmpl/parser/gtd/stack/SequenceStackNode.java b/src/org/rascalmpl/parser/gtd/stack/SequenceStackNode.java index 638d978d00b..05aec33f4d6 100644 --- a/src/org/rascalmpl/parser/gtd/stack/SequenceStackNode.java +++ b/src/org/rascalmpl/parser/gtd/stack/SequenceStackNode.java @@ -18,74 +18,80 @@ public class SequenceStackNode

extends AbstractExpandableStackNode

{ private final P production; private final String name; - + private final AbstractStackNode

[] children; - + public SequenceStackNode(int id, int dot, P production, AbstractStackNode

[] children){ super(id, dot); - + this.production = production; this.name = String.valueOf(id); - + this.children = generateChildren(children); } - + public SequenceStackNode(int id, int dot, P production, AbstractStackNode

[] children, IEnterFilter[] enterFilters, ICompletionFilter[] completionFilters){ super(id, dot, enterFilters, completionFilters); - + this.production = production; this.name = String.valueOf(id); - + this.children = generateChildren(children); } - + private SequenceStackNode(SequenceStackNode

original, int startLocation){ super(original, startLocation); - + production = original.production; name = original.name; children = original.children; } - + /** * Generates and initializes the alternatives for this sequence. */ @SuppressWarnings("unchecked") private AbstractStackNode

[] generateChildren(AbstractStackNode

[] children){ AbstractStackNode

[] prod = (AbstractStackNode

[]) new AbstractStackNode[children.length]; - + for(int i = children.length - 1; i >= 0; --i){ AbstractStackNode

child = children[i].getCleanCopy(DEFAULT_START_LOCATION); child.setProduction(prod); prod[i] = child; } - + prod[prod.length - 1].setAlternativeProduction(production); - + return (AbstractStackNode

[]) new AbstractStackNode[]{prod[0]}; } - + public String getName(){ return name; } - + public AbstractStackNode

getCleanCopy(int startLocation){ - return new SequenceStackNode

(this, startLocation); + return new SequenceStackNode<>(this, startLocation); } - + public AbstractStackNode

[] getChildren(){ return children; } - + public boolean canBeEmpty(){ return false; } - + public AbstractStackNode

getEmptyChild(){ throw new UnsupportedOperationException(); } + @Override + public String toShortString() { + return name; + } + + @Override public String toString(){ StringBuilder sb = new StringBuilder(); sb.append("seq"); @@ -93,21 +99,33 @@ public String toString(){ sb.append('('); sb.append(startLocation); sb.append(')'); - + return sb.toString(); } - + + @Override public int hashCode(){ return production.hashCode(); } - + + @Override + public boolean equals(Object peer) { + return super.equals(peer); + } + public boolean isEqual(AbstractStackNode

stackNode){ if(!(stackNode instanceof SequenceStackNode)) return false; - + SequenceStackNode

otherNode = (SequenceStackNode

) stackNode; - + if(!production.equals(otherNode.production)) return false; - + return hasEqualFilters(stackNode); } + + @Override + public R accept(StackNodeVisitor visitor) { + return visitor.visit(this); + } + } diff --git a/src/org/rascalmpl/parser/gtd/stack/SkippingStackNode.java b/src/org/rascalmpl/parser/gtd/stack/SkippingStackNode.java index eb243b3b6c0..ea82ed1aba8 100644 --- a/src/org/rascalmpl/parser/gtd/stack/SkippingStackNode.java +++ b/src/org/rascalmpl/parser/gtd/stack/SkippingStackNode.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009-2013 CWI + * Copyright (c) 2009-2024 CWI * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -8,93 +8,128 @@ * Contributors: * * Arnold Lankamp - Arnold.Lankamp@cwi.nl + * * Pieter Olivier - Pieter.Olivier@swat.engineering *******************************************************************************/ package org.rascalmpl.parser.gtd.stack; +import java.net.URI; + import org.rascalmpl.parser.gtd.result.AbstractNode; import org.rascalmpl.parser.gtd.result.SkippedNode; public final class SkippingStackNode

extends AbstractMatchableStackNode

{ private final SkippedNode result; - - public SkippingStackNode(int id, int[] until, int[] input, int startLocation, P parentProduction){ + + public static SkippedNode createResultUntilCharClass(URI uri, int[] until, int[] input, int startLocation) { + for (int to = startLocation ; to < input.length; ++to) { + for (int i = 0; i < until.length; ++i) { + if (input[to] == until[i]) { + int length = to - startLocation; + return new SkippedNode(uri, createSkippedToken(input, startLocation, length), startLocation); + } + } + } + + return new SkippedNode(uri, new int[0], startLocation); + } + + public static SkippedNode createResultUntilEndOfInput(URI uri, int[] input, int startLocation) { + int length = input.length - startLocation; + return new SkippedNode(uri, createSkippedToken(input, startLocation, length), startLocation); + } + + public static SkippedNode createResultUntilChar(URI uri, int[] input, int startLocation, int endLocation) { + return new SkippedNode(uri, createSkippedToken(input, startLocation, endLocation - startLocation), startLocation); + } + + private static int[] createSkippedToken(int[] input, int startLocation, int length) { + int[] token = new int[length]; + System.arraycopy(input, startLocation, token, 0, length); + return token; + } + + public SkippingStackNode(int id, P parentProduction, SkippedNode result) { super(id, 0); - - this.result = buildResult(input, until, startLocation); + + this.result = result; + setAlternativeProduction(parentProduction); + } + + public SkippingStackNode(int id, P parentProduction, SkippedNode result, int startLocation) { + super(id, 0, startLocation); + + this.result = result; setAlternativeProduction(parentProduction); } - + private SkippingStackNode(SkippingStackNode

original, int startLocation){ super(original, startLocation); - + this.result = original.result; } - + private SkippingStackNode(SkippingStackNode

original, SkippedNode result, int startLocation){ super(original, startLocation); - + this.result = result; } - - private static SkippedNode buildResult(int[] input, int[] until, int startLocation){ - for (int to = startLocation ; to < input.length; ++to) { - for (int i = 0; i < until.length; ++i) { - if (input[to] == until[i]) { - int length = to - startLocation; - int[] chars = new int[length]; - System.arraycopy(input, startLocation, chars, 0, length); - - return new SkippedNode(chars, startLocation); - } - } - } - - return new SkippedNode(new int[0], startLocation); - } - + public boolean isEmptyLeafNode(){ return result.isEmpty(); } - + public AbstractNode match(int[] input, int location){ return result; } public AbstractStackNode

getCleanCopy(int startLocation){ - return new SkippingStackNode

(this, startLocation); + return new SkippingStackNode<>(this, startLocation); } - + public AbstractStackNode

getCleanCopyWithResult(int startLocation, AbstractNode result){ - return new SkippingStackNode

(this, (SkippedNode) result, startLocation); + return new SkippingStackNode<>(this, (SkippedNode) result, startLocation); } - + public int getLength(){ return result.getLength(); } - + public AbstractNode getResult(){ return result; } - - public String toString(){ - StringBuilder sb = new StringBuilder(); - sb.append(getId()); - sb.append('('); - sb.append(startLocation); - sb.append(')'); - - return sb.toString(); - } - + + @Override + public String toShortString() { + return "skip(" + result.toString() + ")"; + } + + @Override + public String toString() { + return "SkippingStackNode[result=" + result + "," + super.toString() + "]"; + } + + @Override public int hashCode(){ return getParentProduction().hashCode(); } - + + @Override + public boolean equals(Object rhs) { + return super.equals(rhs); + } + public boolean isEqual(AbstractStackNode

stackNode){ - if(!(stackNode instanceof SkippingStackNode)) return false; - + if ( !(stackNode instanceof SkippingStackNode)) { + return false; + } + SkippingStackNode

otherNode = (SkippingStackNode

) stackNode; - + return otherNode.id == id; } + + @Override + public R accept(StackNodeVisitor visitor) { + return visitor.visit(this); + } } diff --git a/src/org/rascalmpl/parser/gtd/stack/StackNodeVisitor.java b/src/org/rascalmpl/parser/gtd/stack/StackNodeVisitor.java new file mode 100644 index 00000000000..fbe9b7939d8 --- /dev/null +++ b/src/org/rascalmpl/parser/gtd/stack/StackNodeVisitor.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2024, NWO-I Centrum Wiskunde & Informatica (CWI) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + +package org.rascalmpl.parser.gtd.stack; + +public interface StackNodeVisitor { + R visit(AlternativeStackNode

node); + R visit(CaseInsensitiveLiteralStackNode

node); + R visit(CharStackNode

node); + R visit(EmptyStackNode

node); + R visit(EpsilonStackNode

node); + R visit(ListStackNode

node); + R visit(LiteralStackNode

node); + R visit(MultiCharacterStackNode

node); + R visit(NonTerminalStackNode

node); + R visit(OptionalStackNode

node); + R visit(RecoveryPointStackNode

node); + R visit(SeparatedListStackNode

node); + R visit(SequenceStackNode

node); + R visit(SkippingStackNode

node); +} diff --git a/src/org/rascalmpl/parser/gtd/stack/StackNodeVisitorAdapter.java b/src/org/rascalmpl/parser/gtd/stack/StackNodeVisitorAdapter.java new file mode 100644 index 00000000000..68094cc199a --- /dev/null +++ b/src/org/rascalmpl/parser/gtd/stack/StackNodeVisitorAdapter.java @@ -0,0 +1,102 @@ +/** + * Copyright (c) 2024, NWO-I Centrum Wiskunde & Informatica (CWI) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + +package org.rascalmpl.parser.gtd.stack; + +public class StackNodeVisitorAdapter implements StackNodeVisitor { + + @Override + public R visit(AlternativeStackNode

node) { + // Do nothing fallback + return null; + } + + @Override + public R visit(CaseInsensitiveLiteralStackNode

node) { + // Do nothing fallback + return null; + } + + @Override + public R visit(CharStackNode

node) { + // Do nothing fallback + return null; + } + + @Override + public R visit(EmptyStackNode

node) { + // Do nothing fallback + return null; + } + + @Override + public R visit(EpsilonStackNode

node) { + // Do nothing fallback + return null; + } + + @Override + public R visit(ListStackNode

node) { + // Do nothing fallback + return null; + } + + @Override + public R visit(LiteralStackNode

node) { + // Do nothing fallback + return null; + } + + @Override + public R visit(MultiCharacterStackNode

node) { + // Do nothing fallback + return null; + } + + @Override + public R visit(NonTerminalStackNode

node) { + // Do nothing fallback + return null; + } + + @Override + public R visit(OptionalStackNode

node) { + // Do nothing fallback + return null; + } + + @Override + public R visit(RecoveryPointStackNode

node) { + // Do nothing fallback + return null; + } + + @Override + public R visit(SeparatedListStackNode

node) { + // Do nothing fallback + return null; + } + + @Override + public R visit(SequenceStackNode

node) { + // Do nothing fallback + return null; + } + + @Override + public R visit(SkippingStackNode

node) { + // Do nothing fallback + return null; + } +} diff --git a/src/org/rascalmpl/parser/gtd/stack/edge/EdgesSet.java b/src/org/rascalmpl/parser/gtd/stack/edge/EdgesSet.java index b778f6677a3..56f1b3d3419 100644 --- a/src/org/rascalmpl/parser/gtd/stack/edge/EdgesSet.java +++ b/src/org/rascalmpl/parser/gtd/stack/edge/EdgesSet.java @@ -22,60 +22,62 @@ @SuppressWarnings({"unchecked", "cast"}) public class EdgesSet

{ - public final static int DEFAULT_RESULT_STORE_ID = -1; - - private final static int DEFAULT_SIZE = 8; - + public static final int DEFAULT_RESULT_STORE_ID = -1; + + private static final int DEFAULT_SIZE = 8; + private AbstractStackNode

[] edges; private int size; - + private int lastVisitedLevel = -1; private IntegerMap lastVisitedFilteredLevel; - + private AbstractContainerNode

lastResults; + + // Indexed by `resultStoreId`. private IntegerObjectList> lastFilteredResults; - + public EdgesSet(){ super(); - + edges = (AbstractStackNode

[]) new AbstractStackNode[DEFAULT_SIZE]; size = 0; } - + public EdgesSet(int initialSize){ super(); - + edges = (AbstractStackNode

[]) new AbstractStackNode[initialSize]; size = 0; } - + private void enlarge(){ edges = Arrays.copyOf(edges, size << 1, edges.getClass()); } - + public void add(AbstractStackNode

edge){ - while(size >= edges.length){ // While instead of if to enable the JIT to eliminate the bounds check on the edges array + while(size >= edges.length){ // While instead of if to enable the JIT to eliminate the bounds check on the edges array enlarge(); } - + edges[size++] = edge; } - + public boolean contains(AbstractStackNode

node){ for(int i = size - 1; i >= 0; --i){ if(edges[i] == node) return true; } return false; } - + public boolean containsBefore(AbstractStackNode

node, int limit){ for(int i = limit - 1; i >= 0; --i){ if(edges[i] == node) return true; } return false; } - + public boolean containsAfter(AbstractStackNode

node, int limit){ if(limit >= 0){ // Bounds check elimination helper. for(int i = size - 1; i >= limit; --i){ @@ -84,11 +86,11 @@ public boolean containsAfter(AbstractStackNode

node, int limit){ } return false; } - + public AbstractStackNode

get(int index){ return edges[index]; } - + public void setLastVisitedLevel(int level, int resultStoreId){ if(resultStoreId == DEFAULT_RESULT_STORE_ID){ lastVisitedLevel = level; @@ -96,21 +98,21 @@ public void setLastVisitedLevel(int level, int resultStoreId){ if(lastVisitedFilteredLevel == null){ lastVisitedFilteredLevel = new IntegerMap(); } - + lastVisitedFilteredLevel.put(resultStoreId, level); } } - + public int getLastVisitedLevel(int resultStoreId){ if(resultStoreId == DEFAULT_RESULT_STORE_ID) return lastVisitedLevel; - + if(lastVisitedFilteredLevel == null){ lastVisitedFilteredLevel = new IntegerMap(); return -1; } return lastVisitedFilteredLevel.get(resultStoreId); } - + public void setLastResult(AbstractContainerNode

lastResult, int resultStoreId){ if(resultStoreId == DEFAULT_RESULT_STORE_ID){ lastResults = lastResult; @@ -118,26 +120,55 @@ public void setLastResult(AbstractContainerNode

lastResult, int resultStoreId if(lastFilteredResults == null){ lastFilteredResults = new IntegerObjectList>(DEFAULT_SIZE); } - + lastFilteredResults.add(resultStoreId, lastResult); } } - + public AbstractContainerNode

getLastResult(int resultStoreId){ if(resultStoreId == DEFAULT_RESULT_STORE_ID) return lastResults; - + if(lastFilteredResults == null){ lastFilteredResults = new IntegerObjectList>(DEFAULT_SIZE); return null; } return lastFilteredResults.findValue(resultStoreId); } - + public int size(){ return size; } - + public void clear(){ size = 0; } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("EdgesSet[{"); + + for (int i=0; i 0) { + builder.append(","); + } + builder.append(edges[i].getName() + "=" + edges[i].getId() + "." + edges[i].getDot() + "@" + edges[i].getStartLocation()); + } + + builder.append("}"); + + if (lastVisitedLevel >= 0) { + builder.append(",lastVisitedLevel=" + lastVisitedLevel); + } + if (lastResults != null) { + builder.append(",lastResults=" + lastResults); + } + + // Skip 'filtered' fields for now + + builder.append("]"); + + return builder.toString(); + } + + } diff --git a/src/org/rascalmpl/parser/gtd/util/ArrayList.java b/src/org/rascalmpl/parser/gtd/util/ArrayList.java index d93c1740981..899fcdbb0d4 100644 --- a/src/org/rascalmpl/parser/gtd/util/ArrayList.java +++ b/src/org/rascalmpl/parser/gtd/util/ArrayList.java @@ -104,4 +104,17 @@ public int size(){ public Object[] getBackingArray(){ return data; } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("["); + for (int i=0; i 0) { + builder.append(","); + } + builder.append(data[i]); + } + builder.append("]"); + return builder.toString(); + } } diff --git a/src/org/rascalmpl/parser/gtd/util/DoubleArrayList.java b/src/org/rascalmpl/parser/gtd/util/DoubleArrayList.java index e1e6d4ea9ea..4eb156c4a98 100644 --- a/src/org/rascalmpl/parser/gtd/util/DoubleArrayList.java +++ b/src/org/rascalmpl/parser/gtd/util/DoubleArrayList.java @@ -11,6 +11,10 @@ *******************************************************************************/ package org.rascalmpl.parser.gtd.util; +import java.util.Comparator; + +import org.apache.commons.lang3.tuple.Pair; + @SuppressWarnings("unchecked") public class DoubleArrayList{ private final static int DEFAULT_SIZE = 8; @@ -102,4 +106,40 @@ public void ditryClear(){ public int size(){ return size; } + + private java.util.List> zip() { + java.util.List> entries = new java.util.ArrayList<>(size); + for (int i=0; i> comparator) { + java.util.List> entries = zip(); + + entries.sort(comparator); + + for (int i=0; i elem = entries.get(i); + first[i] = elem.getLeft(); + second[i] = elem.getRight(); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("list["); + for (int i=0; i"); + } + builder.append("]\n"); + return builder.toString(); + } + } diff --git a/src/org/rascalmpl/parser/gtd/util/DoubleStack.java b/src/org/rascalmpl/parser/gtd/util/DoubleStack.java index 9251e89a519..3c2c6f3a6d7 100644 --- a/src/org/rascalmpl/parser/gtd/util/DoubleStack.java +++ b/src/org/rascalmpl/parser/gtd/util/DoubleStack.java @@ -171,4 +171,19 @@ public void clear(){ public void dirtyClear(){ size = 0; } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("stack["); + for (int i=0; i"); + } + builder.append("]\n"); + return builder.toString(); + } } diff --git a/src/org/rascalmpl/parser/gtd/util/IdDispenser.java b/src/org/rascalmpl/parser/gtd/util/IdDispenser.java new file mode 100644 index 00000000000..f83fe0c4bf6 --- /dev/null +++ b/src/org/rascalmpl/parser/gtd/util/IdDispenser.java @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2024, NWO-I Centrum Wiskunde & Informatica (CWI) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + +package org.rascalmpl.parser.gtd.util; + +public interface IdDispenser { + int dispenseId(); +} diff --git a/src/org/rascalmpl/parser/gtd/util/IntegerObjectList.java b/src/org/rascalmpl/parser/gtd/util/IntegerObjectList.java index 92d1009b02a..30b63fd46db 100644 --- a/src/org/rascalmpl/parser/gtd/util/IntegerObjectList.java +++ b/src/org/rascalmpl/parser/gtd/util/IntegerObjectList.java @@ -125,4 +125,20 @@ public void clear(){ public void dirtyClear(){ size = 0; } + + public String toString() { + StringBuilder builder = new StringBuilder("["); + + for (int i=0; i 0) { + builder.append(","); + } + builder.append(keys[i]); + builder.append("="); + builder.append(values[i]); + } + + builder.append("]"); + return builder.toString(); + } } diff --git a/src/org/rascalmpl/parser/gtd/util/StackNodeIdDispenser.java b/src/org/rascalmpl/parser/gtd/util/StackNodeIdDispenser.java new file mode 100644 index 00000000000..529f5db087d --- /dev/null +++ b/src/org/rascalmpl/parser/gtd/util/StackNodeIdDispenser.java @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2024, NWO-I Centrum Wiskunde & Informatica (CWI) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + package org.rascalmpl.parser.gtd.util; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.rascalmpl.parser.gtd.IGTD; +import org.rascalmpl.values.parsetrees.ITree; + +import io.usethesource.vallang.IConstructor; +import io.usethesource.vallang.ISourceLocation; + +/** + * To offer backwards compatibility for generated parsers that do not yet have the "getFreeStackNodeId" method yet, + * this class uses reflection to find that method and otherwise improvises by just using a ridiculously high starting number. + */ +public class StackNodeIdDispenser implements IdDispenser { + private IGTD parser; + private Method dispenseMethod; + private int nextNodeIdBackup = (Integer.MAX_VALUE/4)*3; + + public StackNodeIdDispenser(IGTD parser) { + try { + dispenseMethod = parser.getClass().getMethod("getFreeStackNodeId"); + } catch (NoSuchMethodException e) { + // Custom IGTB implementation without "getFreeStackNodeId" method. No biggy, we just use nextNodeIdBackup. + } + } + + @Override + public int dispenseId() { + if (dispenseMethod != null) { + try { + return (Integer)dispenseMethod.invoke(parser); + } + catch (InvocationTargetException e) { + if (e.getTargetException() instanceof UnsupportedOperationException) { + // We are dealing with a parser class that has no generated "getFreeStackNodeId" method (yet), + // for backwards compatibility we fall back on "nextNodeIdBackup". + dispenseMethod = null; // No reason to try again. + } else { + throw new RuntimeException(e); + } + } + catch (IllegalAccessException | IllegalArgumentException e) { + throw new RuntimeException(e); + } + } + + return nextNodeIdBackup++; + } + +} + diff --git a/src/org/rascalmpl/parser/uptr/UPTRNodeFactory.java b/src/org/rascalmpl/parser/uptr/UPTRNodeFactory.java index 8a6199fa2a0..006187c7f9a 100644 --- a/src/org/rascalmpl/parser/uptr/UPTRNodeFactory.java +++ b/src/org/rascalmpl/parser/uptr/UPTRNodeFactory.java @@ -1,6 +1,7 @@ package org.rascalmpl.parser.uptr; import java.net.URI; +import java.util.Arrays; import java.util.IdentityHashMap; import java.util.Map; @@ -21,7 +22,9 @@ import org.rascalmpl.values.parsetrees.TreeAdapter; public class UPTRNodeFactory implements INodeConstructorFactory{ - private final static RascalValueFactory VF = (RascalValueFactory) ValueFactoryFactory.getValueFactory(); + private static final RascalValueFactory VF = (RascalValueFactory) ValueFactoryFactory.getValueFactory(); + private static final IConstructor SKIPPED = VF.constructor(RascalValueFactory.Production_Skipped, VF.constructor(RascalValueFactory.Symbol_IterStar, VF.constructor(RascalValueFactory.Symbol_CharClass, VF.list(VF.constructor(RascalValueFactory.CharRange_Range, VF.integer(1), VF.integer(Character.MAX_CODE_POINT)))))); + private boolean allowAmb; public UPTRNodeFactory(boolean allowAmbiguity){ @@ -141,7 +144,14 @@ public Object getProductionFromNode(ITree node){ } @Override - public ITree createRecoveryNode(int[] characters) { - throw new UnsupportedOperationException(); - } + public ITree createSkippedNode(int[] characters) { + return createLiteralNode(characters, SKIPPED); + } + + public ITree createErrorNode(ArrayList children, Object production) { + IConstructor prod = (IConstructor) production; + IConstructor errorProd = VF.constructor(RascalValueFactory.Production_Error, prod.get(0), prod, VF.integer(children.size()-1)); + return buildAppl(children, errorProd); + } + } diff --git a/src/org/rascalmpl/parser/uptr/debug/DebugLogger.java b/src/org/rascalmpl/parser/uptr/debug/DebugLogger.java index 89ba535a3bc..fb05ba5d95d 100644 --- a/src/org/rascalmpl/parser/uptr/debug/DebugLogger.java +++ b/src/org/rascalmpl/parser/uptr/debug/DebugLogger.java @@ -9,6 +9,9 @@ import org.rascalmpl.parser.gtd.stack.AbstractStackNode; import org.rascalmpl.parser.gtd.stack.edge.EdgesSet; import org.rascalmpl.parser.gtd.util.ArrayList; +import org.rascalmpl.parser.gtd.util.DoubleArrayList; +import org.rascalmpl.parser.gtd.util.DoubleStack; +import org.rascalmpl.parser.gtd.util.Stack; import org.rascalmpl.values.parsetrees.ProductionAdapter; import io.usethesource.vallang.IConstructor; @@ -153,4 +156,57 @@ public void filteredByEnterFilter(AbstractStackNode node){ public void filteredByCompletionFilter(AbstractStackNode node, AbstractNode result){ out.println(String.format("Filtered by completion filter: %s", node)); } + + @Override + public void reviving(int[] input, int location, Stack> unexpandableNodes, + Stack> unmatchableLeafNodes, + DoubleStack, AbstractNode>, AbstractStackNode> unmatchableMidProductionNodes, + DoubleStack, AbstractNode> filteredNodes) { + out.print("Reviving at "); + out.print(location); + out.print(": input='"); + for (int i=0; i<8 && location+i < input.length; i++) { + out.print((char) input[location+i]); + } + out.print("', unexpandable="); + + boolean first = true; + for (int i=0; i 0) { + out.print(", unmatchableLeafNodes="); + out.print(unmatchableLeafNodes.getSize()); + } + + if (unmatchableMidProductionNodes.getSize() > 0) { + out.print(", unmatchableMidProductionNodes="); + out.print(unmatchableMidProductionNodes.toString()); + } + + if (filteredNodes.getSize() > 0) { + out.print(", filteredNodes="); + out.print(filteredNodes.getSize()); + } + + out.println(); + } + + @Override + public void revived(DoubleArrayList, AbstractNode> recoveredNodes) { + out.println("Revived nodes:"); + for (int i=0; i implements IDebugListener

{ + + @Override + public void shifting(int offset, int[] input, PositionStore positionStore) { + // Do nothing + } + + @Override + public void iterating() { + // Do nothing + } + + @Override + public void matched(AbstractStackNode

node, AbstractNode result) { + // Do nothing + } + + @Override + public void failedToMatch(AbstractStackNode

node) { + // Do nothing + } + + @Override + public void expanding(AbstractStackNode

node) { + // Do nothing + } + + @Override + public void expanded(AbstractStackNode

node, AbstractStackNode

child) { + // Do nothing + } + + @Override + public void foundIterationCachedNullableResult(AbstractStackNode

node) { + // Do nothing + } + + @Override + public void moving(AbstractStackNode

node, AbstractNode result) { + // Do nothing + } + + @Override + public void progressed(AbstractStackNode

node, AbstractNode result, AbstractStackNode

next) { + // Do nothing + } + + @Override + public void propagated(AbstractStackNode

node, AbstractNode nodeResult, AbstractStackNode

next) { + // Do nothing + } + + @Override + public void reducing(AbstractStackNode

node, Link resultLink, EdgesSet

edges) { + // Do nothing + } + + @Override + public void reduced(AbstractStackNode

parent) { + // Do nothing + } + + @Override + public void filteredByNestingRestriction(AbstractStackNode

parent) { + // Do nothing + } + + @Override + public void filteredByEnterFilter(AbstractStackNode

node) { + // Do nothing + } + + @Override + public void filteredByCompletionFilter(AbstractStackNode

node, AbstractNode result) { + // Do nothing + } + + @Override + public void reviving(int[] input, int location, Stack> unexpandableNodes, + Stack> unmatchableLeafNodes, + DoubleStack, AbstractNode>, AbstractStackNode

> unmatchableMidProductionNodes, + DoubleStack, AbstractNode> filteredNodes) { + // Do nothing + } + + @Override + public void revived(DoubleArrayList, AbstractNode> recoveredNodes) { + // Do nothing + } + +} diff --git a/src/org/rascalmpl/parser/uptr/recovery/CaseInsensitiveLiteralMatcher.java b/src/org/rascalmpl/parser/uptr/recovery/CaseInsensitiveLiteralMatcher.java new file mode 100644 index 00000000000..aa651723510 --- /dev/null +++ b/src/org/rascalmpl/parser/uptr/recovery/CaseInsensitiveLiteralMatcher.java @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2024, NWO-I Centrum Wiskunde & Informatica (CWI) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + +package org.rascalmpl.parser.uptr.recovery; + +public class CaseInsensitiveLiteralMatcher implements InputMatcher { + private final int[] chars; + private final int[] altChars; + + public CaseInsensitiveLiteralMatcher(String literal) { + int length = literal.length(); + chars = new int[length]; + altChars = new int[length]; + for(int i = 0; i InputMatcher createMatcher(AbstractStackNode

stackNode) { + return stackNode.accept(new StackNodeVisitorAdapter() { + @Override + public InputMatcher visit(LiteralStackNode

literal) { + return new LiteralMatcher(literal.getLiteral()); + } + + @Override + public InputMatcher visit(CaseInsensitiveLiteralStackNode

literal) { + return new CaseInsensitiveLiteralMatcher(literal.getLiteral()); + } + }); + } +} diff --git a/src/org/rascalmpl/parser/uptr/recovery/LiteralMatcher.java b/src/org/rascalmpl/parser/uptr/recovery/LiteralMatcher.java new file mode 100644 index 00000000000..2461d0c8f3c --- /dev/null +++ b/src/org/rascalmpl/parser/uptr/recovery/LiteralMatcher.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2022, NWO-I Centrum Wiskunde & Informatica (CWI) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + package org.rascalmpl.parser.uptr.recovery; + +public class LiteralMatcher implements InputMatcher { + private int[] chars; + + public LiteralMatcher(String literal) { + chars = new int[literal.length()]; + for (int i=0; i { + private static final boolean VISUALIZE_RECOVERY_NODES = false; + + private URI uri; + private IdDispenser stackNodeIdDispenser; + private ExpectsProvider expectsProvider; + + private Set processedNodes = new HashSet<>(); + + public ToTokenRecoverer(URI uri, ExpectsProvider expectsProvider, IdDispenser stackNodeIdDispenser) { + this.uri = uri; + this.expectsProvider = expectsProvider; + this.stackNodeIdDispenser = stackNodeIdDispenser; + } + + @Override + public DoubleArrayList, AbstractNode> reviveStacks(int[] input, int location, + Stack> unexpandableNodes, + Stack> unmatchableLeafNodes, + DoubleStack, AbstractNode>, AbstractStackNode> unmatchableMidProductionNodes, + DoubleStack, AbstractNode> filteredNodes) { + + // For now we ignore unmatchable leaf nodes and filtered nodes. At some point we might use those to + // improve error recovery. + + ArrayList> failedNodes = new ArrayList<>(); + collectUnexpandableNodes(unexpandableNodes, failedNodes); + collectUnmatchableMidProductionNodes(location, unmatchableMidProductionNodes, failedNodes); + + return reviveFailedNodes(input, failedNodes); + } + + private DoubleArrayList, AbstractNode> reviveNodes(int[] input, + DoubleArrayList, ArrayList> recoveryNodes) { + DoubleArrayList, AbstractNode> recoveredNodes = new DoubleArrayList<>(); + + // Sort nodes by start location + recoveryNodes + .sort((e1, e2) -> Integer.compare(e2.getLeft().getStartLocation(), e1.getLeft().getStartLocation())); + + if (VISUALIZE_RECOVERY_NODES) { + ParseStateVisualizer visualizer = new ParseStateVisualizer("Recovery"); + visualizer.visualizeRecoveryNodes(recoveryNodes); + } + + for (int i = 0; i < recoveryNodes.size(); i++) { + AbstractStackNode recoveryNode = recoveryNodes.getFirst(i); + ArrayList prods = recoveryNodes.getSecond(i); + + int startLocation = recoveryNode.getStartLocation(); + + // Handle every possible continuation associated with the recovery node (there can be more then one + // because of prefix-sharing). + for (int j = prods.size() - 1; j >= 0; --j) { + IConstructor prod = prods.get(j); + + List> skippingNodes = + findSkippingNodes(input, recoveryNode, prod, startLocation); + for (SkippingStackNode skippingNode : skippingNodes) { + AbstractStackNode continuer = + new RecoveryPointStackNode<>(stackNodeIdDispenser.dispenseId(), prod, recoveryNode); + + EdgesSet edges = new EdgesSet<>(1); + edges.add(continuer); + + continuer.setIncomingEdges(edges); + + skippingNode.initEdges(); + skippingNode.addEdges(edges, startLocation); + recoveredNodes.add(skippingNode, skippingNode.getResult()); + } + } + } + + return recoveredNodes; + } + + private List> findSkippingNodes(int[] input, + AbstractStackNode recoveryNode, IConstructor prod, int startLocation) { + List> nodes = new java.util.ArrayList<>(); + + SkippedNode result; + + // If we are the top-level node, just skip the rest of the input + if (!recoveryNode.isEndNode() && isTopLevelProduction(recoveryNode)) { + result = SkippingStackNode.createResultUntilEndOfInput(uri, input, startLocation); + nodes.add(new SkippingStackNode<>(stackNodeIdDispenser.dispenseId(), prod, result, startLocation)); + return nodes; // No other nodes would be useful + } + + // Try to find whitespace to skip to + // This often creates hopeless recovery attempts, but it might help in some cases. + // Further experimentation should quantify this statement. + /* + * result = SkippingStackNode.createResultUntilCharClass(WHITESPACE, input, startLocation, prod, + * dot); if (result != null) { nodes.add(new SkippingStackNode<>(stackNodeIdDispenser.dispenseId(), + * prod, result, startLocation)); } + */ + + // Find the last token of this production and skip until after that + List endMatchers = findEndMatchers(recoveryNode); + for (InputMatcher endMatcher : endMatchers) { + MatchResult endMatch = endMatcher.findMatch(input, startLocation); + if (endMatch != null) { + result = SkippingStackNode.createResultUntilChar(uri, input, startLocation, endMatch.getEnd()); + nodes.add(new SkippingStackNode<>(stackNodeIdDispenser.dispenseId(), prod, result, startLocation)); + } + } + + // Find the first token of the next production and skip until before that + List nextMatchers = findNextMatchers(recoveryNode); + for (InputMatcher nextMatcher : nextMatchers) { + MatchResult nextMatch = nextMatcher.findMatch(input, startLocation + 1); + if (nextMatch != null) { + result = SkippingStackNode.createResultUntilChar(uri, input, startLocation, nextMatch.getStart()); + nodes.add(new SkippingStackNode<>(stackNodeIdDispenser.dispenseId(), prod, result, startLocation)); + } + } + + return nodes; + } + + // Find matchers for the last token of the current stack node + private List findEndMatchers(AbstractStackNode stackNode) { + final List matchers = new java.util.ArrayList<>(); + + AbstractStackNode[] prod = stackNode.getProduction(); + addEndMatchers(prod, prod.length - 1, matchers, new HashSet<>()); + + return matchers; + } + + private void addEndMatchers(AbstractStackNode[] prod, int dot, List matchers, + Set visitedNodes) { + if (prod == null || dot < 0 || dot >= prod.length) { + return; + } + + AbstractStackNode last = prod[dot]; + if (visitedNodes.contains(last.getId())) { + return; + } + visitedNodes.add(last.getId()); + + if (isNullable(last)) { + addEndMatchers(prod, dot-1, matchers, visitedNodes); + } + + last.accept(new StackNodeVisitorAdapter() { + @Override + public Void visit(LiteralStackNode literal) { + matchers.add(new LiteralMatcher(literal.getLiteral())); + return null; + } + + @Override + public Void visit(CaseInsensitiveLiteralStackNode literal) { + matchers.add(new CaseInsensitiveLiteralMatcher(literal.getLiteral())); + return null; + } + + @Override + public Void visit(NonTerminalStackNode nonTerminal) { + String name = nonTerminal.getName(); + AbstractStackNode[] alternatives = expectsProvider.getExpects(name); + for (AbstractStackNode alternative : alternatives) { + addEndMatchers(alternative.getProduction(), 0, matchers, visitedNodes); + } + return null; + } + }); + } + + private AbstractStackNode getSingleParentStack(AbstractStackNode stackNode) { + if (stackNode == null) { + return null; + } + + IntegerObjectList> edges = stackNode.getEdges(); + if (edges != null) { + EdgesSet edgesList = edges.getValue(0); + if (edgesList != null) { + return edgesList.get(0); + } + } + + return null; + } + + // Find matchers for the first token after the current stack node + private List findNextMatchers(AbstractStackNode stackNode) { + final List matchers = new java.util.ArrayList<>(); + + // Future improvement: use all parents instead of just one + AbstractStackNode parent = getSingleParentStack(stackNode); + if (parent == null) { + return matchers; + } + + addNextMatchers(parent.getProduction(), parent.getDot() + 1, matchers, new HashSet<>()); + + return matchers; + } + + private void addNextMatchers(AbstractStackNode[] prod, int dot, List matchers, + Set visitedNodes) { + if (prod == null || dot < 0 || dot >= prod.length) { + return; + } + + AbstractStackNode next = prod[dot]; + if (visitedNodes.contains(next.getId())) { + return; + } + visitedNodes.add(next.getId()); + + if (isNullable(next)) { + // In the future, when a node can be empty, we should also consider all prefix-shared alternatives. + addNextMatchers(prod, dot+1, matchers, visitedNodes); + } + + next.accept(new StackNodeVisitorAdapter() { + @Override + public Void visit(LiteralStackNode literal) { + matchers.add(new LiteralMatcher(literal.getLiteral())); + return null; + } + + + @Override + + public Void visit(CaseInsensitiveLiteralStackNode literal) { + matchers.add(new CaseInsensitiveLiteralMatcher(literal.getLiteral())); + return null; + } + + @Override + public Void visit(NonTerminalStackNode nonTerminal) { + String name = nonTerminal.getName(); + AbstractStackNode[] alternatives = expectsProvider.getExpects(name); + for (AbstractStackNode alternative : alternatives) { + addNextMatchers(alternative.getProduction(), 0, matchers, visitedNodes); + } + + return null; + } + }); + } + + private boolean isNullable(AbstractStackNode stackNode) { + if (stackNode instanceof NonTerminalStackNode && stackNode.getName().startsWith("layouts_")) { + return true; + } + + if (stackNode instanceof EpsilonStackNode || stackNode instanceof EmptyStackNode) { + return true; + } + + if (stackNode instanceof AbstractExpandableStackNode) { + return stackNode.canBeEmpty(); + } + + return false; + } + + // Check if a node is a top-level production (i.e., its parent production node has no parents and + // starts at position -1) + // As this is experimental code, this method is extremely conservative. + // Any sharing will result in returning 'false'. + // We will need to change this strategy in the future to improve error recovery. + private boolean isTopLevelProduction(AbstractStackNode node) { + + while (node != null && node.getDot() != 0) { + node = getSinglePredecessor(node); + } + + if (node != null) { + node = getSinglePredecessor(node); + return node != null && node.getStartLocation() == -1; + } + + return false; + } + + private AbstractStackNode getSinglePredecessor(AbstractStackNode node) { + IntegerObjectList> edgeMap = node.getEdges(); + if (edgeMap.size() == 1) { + EdgesSet edges = edgeMap.getValue(0); + if (edges.size() == 1) { + return edges.get(0); + } + } + + return null; + } + + + private DoubleArrayList, AbstractNode> reviveFailedNodes(int[] input, + ArrayList> failedNodes) { + DoubleArrayList, ArrayList> recoveryNodes = + new DoubleArrayList<>(); + + for (int i = failedNodes.size() - 1; i >= 0; --i) { + AbstractStackNode failedNode = failedNodes.get(i); + + // Protect against endless loop + long id = (long) failedNode.getId() << 32 | failedNode.getStartLocation(); + if (!processedNodes.add(id)) { + continue; + } + + findRecoveryNodes(failedNodes.get(i), recoveryNodes); + } + + return reviveNodes(input, recoveryNodes); + } + + private static void collectUnexpandableNodes(Stack> unexpandableNodes, + ArrayList> failedNodes) { + for (int i = unexpandableNodes.getSize() - 1; i >= 0; --i) { + failedNodes.add(unexpandableNodes.get(i)); + } + } + + /** + * Make a fresh copy of each unmatchable mid-production node and link in the predecessors of the + * original node. The new copies are added to `failedNodes` + * + * @param location the location where the failure occurs + * @param unmatchableMidProductionNodes each pair consists of a list of predecessors and a node that + * failed to match + * @param failedNodes the list to which failed nodes must be added + */ + private static void collectUnmatchableMidProductionNodes(int location, + DoubleStack, AbstractNode>, AbstractStackNode> unmatchableMidProductionNodes, + ArrayList> failedNodes) { + for (int i = unmatchableMidProductionNodes.getSize() - 1; i >= 0; --i) { + DoubleArrayList, AbstractNode> failedNodePredecessors = + unmatchableMidProductionNodes.getFirst(i); + AbstractStackNode failedNode = + unmatchableMidProductionNodes.getSecond(i).getCleanCopy(location); // Clone it to prevent by-reference + // updates of the static version + + // Merge the information on the predecessors into the failed node. + for (int j = failedNodePredecessors.size() - 1; j >= 0; --j) { + AbstractStackNode predecessor = failedNodePredecessors.getFirst(j); + AbstractNode predecessorResult = failedNodePredecessors.getSecond(j); + failedNode.updateNode(predecessor, predecessorResult); + } + + failedNodes.add(failedNode); + } + } + + /** + * Travels up the parse graph in an attempt to find the closest recoverable parent nodes. + */ + private void findRecoveryNodes(AbstractStackNode failer, + DoubleArrayList, ArrayList> recoveryNodes) { + ObjectKeyedIntegerMap> visited = new ObjectKeyedIntegerMap<>(); + Stack> todo = new Stack<>(); + + todo.push(failer); + + while (!todo.isEmpty()) { + AbstractStackNode node = todo.pop(); + + if (visited.contains(node)) { + continue; // Don't follow cycles + } + + visited.put(node, 0); + + ArrayList recoveryProductions = new ArrayList<>(); + collectProductions(node, recoveryProductions); + if (recoveryProductions.size() > 0) { + addRecoveryNode(node, recoveryProductions, recoveryNodes); + } + + IntegerObjectList> edges = node.getEdges(); + + for (int i = edges.size() - 1; i >= 0; --i) { // Rewind + EdgesSet edgesList = edges.getValue(i); + + if (edgesList != null) { + for (int j = edgesList.size() - 1; j >= 0; --j) { + AbstractStackNode parent = edgesList.get(j); + + todo.push(parent); + } + } + } + } + } + + // Only add recovery nodes that are not already present. + private void addRecoveryNode(AbstractStackNode node, ArrayList productions, + DoubleArrayList, ArrayList> recoveryNodes) { + for (int i = 0; i < recoveryNodes.size(); i++) { + if (recoveryNodes.getFirst(i) == node && equalProductions(productions, recoveryNodes.getSecond(i))) { + return; + } + } + + recoveryNodes.add(node, productions); + } + + private boolean equalProductions(ArrayList prods1, ArrayList prods2) { + if (prods1.size() != prods2.size()) { + return false; + } + + for (int j = 0; j < prods1.size(); j++) { + if (prods1.get(j) != prods2.get(j)) { + return false; + } + } + + return true; + } + + // Gathers all productions that are marked for recovery (the given node can be part of a prefix + // shared production) + private void collectProductions(AbstractStackNode node, ArrayList productions) { + AbstractStackNode[] production = node.getProduction(); + if (production == null) { + return; // The root node does not have a production, so ignore it. + } + + if (node.isEndNode()) { + IConstructor parentProduction = node.getParentProduction(); + if (ProductionAdapter.isContextFree(parentProduction)) { + productions.add(parentProduction); + + if (ProductionAdapter.isList(parentProduction)) { + return; // Don't follow productions in lists productions, since they are 'cyclic'. + } + } + } + + int dot = node.getDot(); + for (int i = dot + 1; i < production.length; ++i) { + AbstractStackNode currentNode = production[i]; + if (currentNode.isEndNode()) { + IConstructor parentProduction = currentNode.getParentProduction(); + if (ProductionAdapter.isContextFree(parentProduction)) { + productions.add(parentProduction); + } + } + + AbstractStackNode[][] alternateProductions = currentNode.getAlternateProductions(); + if (alternateProductions != null) { + for (int j = alternateProductions.length - 1; j >= 0; --j) { + collectProductions(alternateProductions[j][i], productions); + } + } + } + } +} diff --git a/src/org/rascalmpl/parser/util/DebugUtil.java b/src/org/rascalmpl/parser/util/DebugUtil.java new file mode 100644 index 00000000000..0f31c246fed --- /dev/null +++ b/src/org/rascalmpl/parser/util/DebugUtil.java @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2024, NWO-I Centrum Wiskunde & Informatica (CWI) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + package org.rascalmpl.parser.util; + +import io.usethesource.vallang.IConstructor; +import io.usethesource.vallang.IList; +import io.usethesource.vallang.IValue; + +public class DebugUtil { + /** + * Turn a production IConstructor into a string of the form "S -> E1 E2 ..." + */ + + private DebugUtil() { + } + + public static String prodToString(IConstructor prod) { + StringBuilder builder = new StringBuilder("'"); + + IConstructor sort = (IConstructor) prod.get(0); + builder.append(quotedStringToPlain(String.valueOf(sort.get(0)))); + + builder.append(" ->"); + + if (prod.getName().equals("prod")) { + IList children = (IList) prod.get(1); + for (IValue child : children) { + builder.append(" "); + IConstructor conChild = (IConstructor) child; + builder.append(quotedStringToPlain(String.valueOf((conChild).get(0)))); + } + } else { + builder.append(" "); + builder.append(prod.toString()); + } + + builder.append("'"); + + return builder.toString(); + } + + private static String quotedStringToPlain(String s) { + if (s.charAt(0) == '"' && s.charAt(s.length()-1) == '"') { + return s.substring(1, s.length()-1).replace("\\", ""); + } + + return s; + } + + public static void opportunityToBreak() { + // Nop method that allows breakpoints to be set at the call site even if originally there is no code to break on + } + +} diff --git a/src/org/rascalmpl/parser/util/ParseStateVisualizer.java b/src/org/rascalmpl/parser/util/ParseStateVisualizer.java new file mode 100644 index 00000000000..bbe3d573b90 --- /dev/null +++ b/src/org/rascalmpl/parser/util/ParseStateVisualizer.java @@ -0,0 +1,661 @@ +/** + * Copyright (c) 2024, NWO-I Centrum Wiskunde & Informatica (CWI) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + +package org.rascalmpl.parser.util; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; + +import org.apache.commons.io.FileUtils; +import org.rascalmpl.parser.gtd.SGTDBF; +import org.rascalmpl.parser.gtd.result.AbstractNode; +import org.rascalmpl.parser.gtd.result.CharNode; +import org.rascalmpl.parser.gtd.result.EpsilonNode; +import org.rascalmpl.parser.gtd.result.LiteralNode; +import org.rascalmpl.parser.gtd.result.RecoveredNode; +import org.rascalmpl.parser.gtd.result.SkippedNode; +import org.rascalmpl.parser.gtd.result.SortContainerNode; +import org.rascalmpl.parser.gtd.stack.AbstractStackNode; +import org.rascalmpl.parser.gtd.stack.edge.EdgesSet; +import org.rascalmpl.parser.gtd.util.ArrayList; +import org.rascalmpl.parser.gtd.util.DoubleArrayList; +import org.rascalmpl.parser.gtd.util.DoubleStack; +import org.rascalmpl.parser.gtd.util.IntegerObjectList; +import org.rascalmpl.parser.gtd.util.Stack; +import org.rascalmpl.util.visualize.dot.CompassPoint; +import org.rascalmpl.util.visualize.dot.DotAttribute; +import org.rascalmpl.util.visualize.dot.DotEdge; +import org.rascalmpl.util.visualize.dot.DotField; +import org.rascalmpl.util.visualize.dot.DotGraph; +import org.rascalmpl.util.visualize.dot.DotNode; +import org.rascalmpl.util.visualize.dot.DotRecord; +import org.rascalmpl.util.visualize.dot.NodeId; + +import io.usethesource.vallang.IConstructor; + +/** + * The parser uses quite complex datastructures. + * In order to understand what is going on when parsing, this class can generate graphs (as dot files) + * representing the internal datastructurs of the parser. + * + * These graphs are written to files that are relative to a directory specified in the environment + * variable PARSER_VISUALIZATION_PATH. + * + * The parser can generate a large number of snapshots of the parser state during a single parse. + * The file 'replay.html' contains an simple example of a html file to navigate through these snapshots. + */ +public class ParseStateVisualizer { + public static final boolean VISUALIZATION_ENABLED = true; + private static final String VISUALIZATION_URI_PATTERN_ENV = "PARSER_VISUALIZATION_URI_PATTERN"; + private static final String PARSER_VISUALIZATION_PATH_ENV = "PARSER_VISUALIZATION_PATH"; + private static final boolean INCLUDE_PRODUCTIONS = false; + + public static final NodeId PARSER_ID = new NodeId("Parser"); + public static final NodeId TODO_LISTS_ID= new NodeId("todoLists"); + public static final NodeId STACKS_TO_EXPAND_ID = new NodeId("stacksToExpand"); + public static final NodeId TERMINALS_TO_REDUCE_ID = new NodeId("terminalsToReduce"); + public static final NodeId NON_TERMINALS_TO_REDUCE_ID = new NodeId("nonTerminalsToReduce"); + + public static final NodeId ERROR_TRACKING_ID = new NodeId("error"); + public static final NodeId UNEXPANDABLE_NODES_ID = new NodeId("unexpandableNodes"); + public static final NodeId UNMATCHABLE_LEAF_NODES_ID = new NodeId("unmatchableLeafNodes"); + public static final NodeId UNMATCHABLE_MID_PRODUCTION_NODES_ID = new NodeId("unmatchableMidProductionNodes"); + public static final NodeId FILTERED_NODES_ID = new NodeId("filteredNodes"); + + private static final NodeId RECOVERED_NODES_ID = new NodeId("recoveredNodes"); + + public static boolean shouldVisualizeUri(URI inputUri) { + if (!VISUALIZATION_ENABLED) { + return false; + } + + String pattern = System.getenv(VISUALIZATION_URI_PATTERN_ENV); + if (pattern == null) { + return false; + } + + return inputUri.toString().matches(pattern); + } + + private static class StreamGobbler implements Runnable { + private InputStream inputStream; + private Consumer consumer; + + public StreamGobbler(InputStream inputStream, Consumer consumer) { + this.inputStream = inputStream; + this.consumer = consumer; + } + + @Override + public void run() { + new BufferedReader(new InputStreamReader(inputStream)).lines() + .forEach(consumer); + } + } + + private final String name; + private final File basePath; + private final File frameDir; + private final Map stackNodeNodes; + private DotGraph graph; + private int frame; + + + public ParseStateVisualizer(String name) { + // In the future we might want to offer some way to control the path from within Rascal. + String path = System.getenv(PARSER_VISUALIZATION_PATH_ENV); + if (path == null) { + throw new RuntimeException("The environment variable '" + PARSER_VISUALIZATION_PATH_ENV + "' is not set."); + } + basePath = new File(System.getenv(PARSER_VISUALIZATION_PATH_ENV)); + + this.name = name; + stackNodeNodes = new HashMap<>(); + + frameDir = new File(new File(basePath, "frames"), name); + if (frameDir.exists()) { + try { + FileUtils.deleteDirectory(frameDir); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + frameDir.mkdirs(); + } + + public void visualize(AbstractStackNode node) { + if (VISUALIZATION_ENABLED) { + writeGraph(createGraph(node)); + } + } + + public void visualizeRecoveryNodes(DoubleArrayList, ArrayList> recoveryNodes) { + writeGraph(createGraph(recoveryNodes)); + } + + public void visualizeProductionTrees(AbstractStackNode[] nodes) { + writeGraph(createProductionGraph(nodes)); + } + + public int getFrame() { + return frame; + } + + private void reset() { + stackNodeNodes.clear(); + graph = null; + frame++; + } + + public void highlight(NodeId id) { + if (graph != null) { + graph.highlight(id); + } + } + + public void highlightStack(AbstractStackNode stack) { + if (graph != null) { + DotNode dotNode = stackNodeNodes.get(stack.getId()); + highlight(dotNode.getId()); + } + } + + private synchronized DotGraph createGraph(AbstractStackNode stackNode) { + reset(); + graph = new DotGraph(name, true); + addStack(graph, stackNode); + return graph; + } + + private DotGraph createGraph(DoubleArrayList, ArrayList> recoveryNodes) { + reset(); + graph = new DotGraph(name, true); + final NodeId recoveryNodesId = new NodeId("recovery-nodes"); + + DotNode arrayNode = DotNode.createArrayNode(recoveryNodesId, recoveryNodes.size()); + graph.addNode(arrayNode); + + for (int i=0; i[] stackNodes) { + reset(); + graph = new DotGraph(name, true); + for (AbstractStackNode stackNode : stackNodes) { + addProductionNodes(graph, stackNode); + } + return graph; + } + + private

NodeId addProductionNodes(DotGraph graph, AbstractStackNode

stackNode) { + DotNode node = createDotNode(stackNode); + graph.addNode(node); + + AbstractStackNode

[] prods = stackNode.getProduction(); + if (prods != null) { + NodeId prodArrayId = new NodeId(node.getId() + "-prod"); + graph.addArrayNode(prodArrayId, prods.length); + for (int i=0; i child = prods[i]; + DotNode childNode = createDotNode(child); + graph.addNode(childNode); + graph.addEdge(new NodeId(prodArrayId, String.valueOf(i)), childNode.getId()); + } + + graph.addEdge(node.getId(), prodArrayId, "Production"); + } + + return node.getId(); + } + + private void addProductionArray(DotGraph graph, NodeId nodeId, ArrayList productions) { + DotNode arrayNode = DotNode.createArrayNode(nodeId, productions.size()); + graph.addNode(arrayNode); + for (int i=0; i void addRecoveredNodes(DoubleArrayList, AbstractNode> recoveredNodes) { + addStackAndNodeDoubleList(graph, RECOVERED_NODES_ID, recoveredNodes); + graph.addEdge(ERROR_TRACKING_ID, RECOVERED_NODES_ID, "Nodes to revive"); + highlight(RECOVERED_NODES_ID); + } + + private

DotNode addStack(DotGraph graph, AbstractStackNode

stackNode) { + DotNode node = stackNodeNodes.get(stackNode.getId()); + if (node != null) { + return node; + } + + node = createDotNode(stackNode); + + stackNodeNodes.put(stackNode.getId(), node); + + graph.addNode(node); + + if (INCLUDE_PRODUCTIONS) { + addProductionNodes(graph, stackNode); + } + + IntegerObjectList> edges = stackNode.getEdges(); + if (edges != null) { + for (int i = edges.size() - 1; i >= 0; --i) { + EdgesSet

edgesList = edges.getValue(i); + if (edgesList != null) { + for (int j = edgesList.size() - 1; j >= 0; --j) { + AbstractStackNode

parentStackNode = edgesList.get(j); + DotNode parentDotNode = addStack(graph, parentStackNode); + graph.addEdge(node.getId(), parentDotNode.getId()); + } + } + } + } + + return node; + } + + private

DotNode createDotNode(AbstractStackNode

stackNode) { + String type = stackNode.getClass().getSimpleName(); + if (type.endsWith("StackNode")) { + type = type.substring(0, type.length() - "StackNode".length()); + } + + String nodeName; + + try { + nodeName = stackNode.getName(); + } catch (UnsupportedOperationException e) { + nodeName = ""; + } + + if (nodeName.startsWith("layouts_")) { + nodeName = nodeName.substring("layouts_".length()); + } + + int dot = stackNode.getDot(); + + String extraInfo = ""; + if (stackNode.isMatchable()) { + extraInfo += ",matchable"; + } + if (stackNode.isSeparator()) { + extraInfo += ",sep"; + } + if (stackNode.isExpandable()) { + extraInfo += ",expandable"; + } + if (stackNode.isLayout()) { + extraInfo += ",layout"; + } + if (stackNode.isEndNode()) { + extraInfo += ",end"; + } + + DotNode node = new DotNode(getNodeId(stackNode)); + String label = String.format("%s: %s\n.%d@%d %s", + type, nodeName, dot, stackNode.getStartLocation(), extraInfo); + + String shortString = stackNode.toShortString(); + if (shortString != null) { + label += "\n" + shortString; + } + + P parentProduction = stackNode.getParentProduction(); + if (parentProduction instanceof IConstructor) { + label += "\nin: " + DebugUtil.prodToString((IConstructor) parentProduction); + } else { + if (stackNode.getProduction() != null) { + label += "\nin:"; + for (AbstractStackNode

n : stackNode.getProduction()) { + String s = n.toShortString(); + if (!s.startsWith("layouts_")) { + label += " " + n.toShortString(); + } + } + } + } + node.addAttribute(DotAttribute.ATTR_LABEL, label); + + return node; + } + + @SuppressWarnings("unchecked") + private void addParserNode(DotGraph graph, AbstractNode parserNode) { + NodeId id = getNodeId(parserNode); + DotNode dotNode = new DotNode(id); + dotNode.addAttribute(DotAttribute.ATTR_NODE_SHAPE, "octagon"); + + String nodeName = parserNode.getClass().getSimpleName(); + if (nodeName.endsWith("Node")) { + nodeName = nodeName.substring(0, nodeName.length() - "Node".length()); + } + + dotNode.addAttribute(DotAttribute.ATTR_LABEL, nodeName); + + switch (parserNode.getTypeIdentifier()) { + case EpsilonNode.ID: + break; + case CharNode.ID: + enrichCharNode(dotNode, (CharNode) parserNode); + break; + case LiteralNode.ID: + enrichLiteralNode(dotNode, (LiteralNode) parserNode); + break; + case SortContainerNode.ID: + case RecoveredNode.ID: + enrichSortContainerNode(dotNode, (SortContainerNode) parserNode); + break; + case SkippedNode.ID: + enrichSkippedNode(dotNode, (SkippedNode) parserNode); + break; + default: + enrichUnknownParserNode(dotNode, parserNode); + break; + } + + graph.addNode(dotNode); + } + + private void enrichCharNode(DotNode dotNode, CharNode charNode) { + int c = charNode.getCharacter(); + String label = dotNode.getAttributeValue(DotAttribute.ATTR_LABEL) + "\nchar=" + c + "('" + (char) c + "')"; + dotNode.setAttribute(DotAttribute.ATTR_LABEL, label); + } + + private void enrichLiteralNode(DotNode dotNode, LiteralNode literalNode) { + int[] content = literalNode.getContent(); + String label = dotNode.getAttributeValue(DotAttribute.ATTR_LABEL) + " \"" + new String(content, 0, content.length) + "\""; + dotNode.setAttribute(DotAttribute.ATTR_LABEL, label); + } + + private void enrichSkippedNode(DotNode dotNode, SkippedNode skippedNode) { + String label = dotNode.getAttributeValue(DotAttribute.ATTR_LABEL); + int[] skipped = skippedNode.getSkippedChars(); + label += "\n@" + skippedNode.getOffset() + ": " + " \"" + new String(skipped, 0, skipped.length) + "\""; + + dotNode.setAttribute(DotAttribute.ATTR_LABEL, label); + } + + private void enrichSortContainerNode(DotNode dotNode, SortContainerNode sortNode) { + String label = dotNode.getAttributeValue(DotAttribute.ATTR_LABEL); + label += " " + sortNode.getOffset() + "-" + sortNode.getEndOffset(); + label += "\n" + DebugUtil.prodToString(sortNode.getFirstProduction()); + dotNode.setAttribute(DotAttribute.ATTR_LABEL, label); + } + + private void enrichUnknownParserNode(DotNode dotNode, AbstractNode parserNode) { + String label = dotNode.getAttributeValue(DotAttribute.ATTR_LABEL); + label += "\ntype=" + parserNode.getTypeIdentifier(); + dotNode.setAttribute(DotAttribute.ATTR_LABEL, label); + } + + public static

NodeId getNodeId(AbstractStackNode

stackNode) { + return new NodeId(String.valueOf(stackNode.getId())); + } + + private static NodeId getNodeId(Object node) { + return new NodeId(String.valueOf(System.identityHashCode(node))); + } + + public void writeGraph() { + if (graph != null) { + writeGraph(graph); + } + } + + public void createGraph(SGTDBF parser, String step) { + if (!VISUALIZATION_ENABLED) { + return; + } + reset(); + + graph = new DotGraph(name, true); + + int location = parser.getLocation(); + + DotNode parserNode = new DotNode(PARSER_ID); + + int[] inputChars = parser.getInput(); + String input = new String(inputChars, 0, inputChars.length); + + char lookahead = (char) parser.getLookAheadChar(); + if (lookahead == '\0') { + lookahead = '$'; + } + + String label = String.format("Parser\nInput: \"%s\"\nLocation: %d ('%c')\nStep %d: %s", + input, location, lookahead, frame, step); + parserNode.setAttribute(DotAttribute.ATTR_LABEL, label); + graph.addNode(parserNode); + + addTodoLists(parser, graph); + addStacksToExpand(parser, graph); + addTerminalsToReduce(parser, graph); + addNonTerminalsToReduce(parser, graph); + + addErrorNodes(parser, graph); + } + + private void addTodoLists(SGTDBF parser, DotGraph graph) { + DoubleStack, AbstractNode>[] todoLists = parser.getTodoLists(); + int start = parser.getQueueIndex(); + + DotNode todoListsNode = DotNode.createArrayNode(TODO_LISTS_ID, todoLists.length); + + for (int tokenLength=1; tokenLength<=todoLists.length; tokenLength++) { + int index = (start + tokenLength - 1) % todoLists.length; + DoubleStack, AbstractNode> todoList = todoLists[index]; + if (todoList != null && !todoList.isEmpty()) { + NodeId todoListId = new NodeId("todo-" + tokenLength); + addStackAndNodeDoubleStack(graph, todoListId, todoList); + graph.addEdge(DotEdge.createArrayEdge(TODO_LISTS_ID, tokenLength, todoListId)); + } + } + + graph.addNode(todoListsNode); + graph.addEdge(PARSER_ID, TODO_LISTS_ID, "todo lists"); + } + + private void addStacksToExpand(SGTDBF parser, DotGraph graph) { + Stack> stacksToExpand = parser.getStacksToExpand(); + addStackNodeStack(graph, STACKS_TO_EXPAND_ID, stacksToExpand); + graph.addEdge(PARSER_ID, STACKS_TO_EXPAND_ID, "stacks to expand"); + } + + private void addTerminalsToReduce(SGTDBF parser, DotGraph graph) { + addStackAndNodeDoubleStack(graph, TERMINALS_TO_REDUCE_ID, parser.getStacksWithTerminalsToReduce()); + graph.addEdge(PARSER_ID, TERMINALS_TO_REDUCE_ID, "terminals to reduce"); + } + + private void addNonTerminalsToReduce(SGTDBF parser, DotGraph graph) { + addStackAndNodeDoubleStack(graph, NON_TERMINALS_TO_REDUCE_ID, parser.getStacksWithNonTerminalsToReduce()); + graph.addEdge(PARSER_ID, NON_TERMINALS_TO_REDUCE_ID, "non-terminals to reduce"); + } + + private void addErrorNodes(SGTDBF parser, DotGraph graph) { + addUnexpandableNodes(parser, graph); + addUnmatchableLeafNodes(parser, graph); + addUnmatchableMidProductionNodes(parser, graph); + addFilteredNodes(parser, graph); + + graph.addNode(ERROR_TRACKING_ID, "Errors"); + + graph.addEdge(PARSER_ID, ERROR_TRACKING_ID, "error tracking"); + graph.addEdge(ERROR_TRACKING_ID, UNEXPANDABLE_NODES_ID, "unexpandable"); + graph.addEdge(ERROR_TRACKING_ID, UNMATCHABLE_LEAF_NODES_ID, "unmatchable leafs"); + graph.addEdge(ERROR_TRACKING_ID, UNMATCHABLE_MID_PRODUCTION_NODES_ID, "unmatchable mid-prod"); + graph.addEdge(ERROR_TRACKING_ID, FILTERED_NODES_ID, "filtered"); + } + + private void addUnexpandableNodes(SGTDBF parser, DotGraph graph) { + addStackNodeStack(graph, UNEXPANDABLE_NODES_ID, parser.getUnexpandableNodes()); + } + + private void addUnmatchableLeafNodes(SGTDBF parser, DotGraph graph) { + addStackNodeStack(graph, UNMATCHABLE_LEAF_NODES_ID, parser.getUnmatchableLeafNodes()); + } + + private void addUnmatchableMidProductionNodes(SGTDBF parser, DotGraph graph) { + DoubleStack, AbstractNode>, AbstractStackNode

> unmatchableMidProductionNodes = parser.getUnmatchableMidProductionNodes(); + + graph.addArrayNode(UNMATCHABLE_MID_PRODUCTION_NODES_ID, unmatchableMidProductionNodes.getSize()); + for (int i = unmatchableMidProductionNodes.getSize() - 1; i >= 0; --i) { + NodeId failureId = new NodeId("unmatchable-mid-production-" + i); + DotRecord failureRecord = new DotRecord(); + failureRecord.addEntry(new DotField("Failed Node", "failedNode")); + failureRecord.addEntry(new DotField("Predecessors", "predecessors")); + graph.addRecordNode(failureId, failureRecord); + graph.addEdge(new NodeId(UNMATCHABLE_MID_PRODUCTION_NODES_ID, String.valueOf(i)), failureId); + + DoubleArrayList, AbstractNode> failedNodePredecessors = unmatchableMidProductionNodes.getFirst(i); + AbstractStackNode

failedNode = unmatchableMidProductionNodes.getSecond(i); + + DotNode node = addStack(graph, failedNode); + NodeId predecessorsId = new NodeId("unmatchable-mid-production-predecessors-" + i); + addStackAndNodeDoubleList(graph, predecessorsId, failedNodePredecessors); + + graph.addEdge(new NodeId(failureId, "failedNode"), node.getId()); + graph.addEdge(new NodeId(failureId, "predecessors"), predecessorsId); + } + } + + private void addFilteredNodes(SGTDBF parser, DotGraph graph) { + addStackAndNodeDoubleStack(graph, FILTERED_NODES_ID, parser.getFilteredNodes()); + } + + private void addStackAndNodeDoubleStack(DotGraph graph, NodeId nodeId, DoubleStack, N> doubleStack) { + DotNode arrayNode = DotNode.createArrayNode(nodeId, doubleStack == null ? 0 : doubleStack.getSize()); + graph.addNode(arrayNode); + + if (doubleStack == null) { + return; + } + + for (int j=0; j stack = doubleStack.getFirst(j); + DotNode stackDotNode = addStack(graph, stack); + graph.addEdge(new NodeId(nodeId, String.valueOf(j), CompassPoint.SW), stackDotNode.getId(), "Stack"); + + AbstractNode node = doubleStack.getSecond(j); + addParserNode(graph, node); + graph.addEdge(new NodeId(nodeId, String.valueOf(j), CompassPoint.SE), getNodeId(node), "Node"); + } + } + + private void addStackAndNodeDoubleList(DotGraph graph, NodeId nodeId, DoubleArrayList, N> doubleList) { + DotNode arrayNode = DotNode.createArrayNode(nodeId, doubleList.size()); + graph.addNode(arrayNode); + + for (int i=0; i stack = doubleList.getFirst(i); + DotNode stackDotNode = addStack(graph, stack); + graph.addEdge(new NodeId(entryId, "stack", CompassPoint.SW), stackDotNode.getId(), "Stack"); + + AbstractNode node = doubleList.getSecond(i); + addParserNode(graph, node); + graph.addEdge(new NodeId(entryId, "node", CompassPoint.SE), getNodeId(node), "Node"); + + + graph.addEdge(new NodeId(nodeId, String.valueOf(i)), entryId); + } + } + + private

void addStackNodeStack(DotGraph graph, NodeId nodeId, Stack> stack) { + if (stack == null) { + return; + } + + DotNode arrayNode = DotNode.createArrayNode(nodeId, stack.getSize()); + + for (int j=0; j stackNode = stack.get(j); + addStack(graph, stackNode); + + graph.addEdge(DotEdge.createArrayEdge(nodeId, j, getNodeId(stackNode))); + } + + graph.addNode(arrayNode); + } + + private void writeGraph(DotGraph graph) { + try { + File dotFile = new File(basePath, name + ".dot"); + File svgFile = new File(basePath, name + ".svg"); + //File frameDir = new File(basePath, BASE_DIR + "/frames/" + name + "/"; + File frameSvgFile = new File(frameDir, String.format("%04d", frame) + ".svg"); + File frameDotFile = new File(frameDir, String.format("%04d", frame) + ".dot"); + FileWriter writer = new FileWriter(dotFile); + writer.write(graph.toString()); + writer.close(); + + String cmd = String.format("dot -Tsvg %s -o %s", dotFile, svgFile); + Process process = Runtime.getRuntime().exec(cmd); + StreamGobbler streamGobbler = new StreamGobbler(process.getInputStream(), System.out::println); + ExecutorService executorService = Executors.newSingleThreadExecutor(); + Future future = executorService.submit(streamGobbler); + + process.waitFor(); + future.get(10, TimeUnit.SECONDS); + + Files.copy(svgFile.toPath(), frameSvgFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + Files.copy(dotFile.toPath(), frameDotFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + } catch (IOException | InterruptedException | ExecutionException | TimeoutException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/org/rascalmpl/parser/util/replay.html b/src/org/rascalmpl/parser/util/replay.html new file mode 100644 index 00000000000..23e9f700ed0 --- /dev/null +++ b/src/org/rascalmpl/parser/util/replay.html @@ -0,0 +1,48 @@ + + + + + + diff --git a/src/org/rascalmpl/semantics/dynamic/Expression.java b/src/org/rascalmpl/semantics/dynamic/Expression.java index ba5f629189d..6bcdd6d555c 100644 --- a/src/org/rascalmpl/semantics/dynamic/Expression.java +++ b/src/org/rascalmpl/semantics/dynamic/Expression.java @@ -115,7 +115,7 @@ public abstract class Expression extends org.rascalmpl.ast.Expression { private final static boolean instantiateTypeParameters = true; private static final Name IT = ASTBuilder.makeLex("Name", null, ""); - + static public class Addition extends org.rascalmpl.ast.Expression.Addition { public Addition(ISourceLocation __param1, IConstructor tree, org.rascalmpl.ast.Expression __param2, @@ -132,13 +132,13 @@ public IBooleanResult buildBacktracker(IEvaluatorContext __eval) { } - + @Override public Result interpret(IEvaluator> __eval) { - + __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + Result left = this.getLhs().interpret(__eval); Result right = this.getRhs().interpret(__eval); return left.add(right); @@ -166,8 +166,8 @@ public IBooleanResult buildBacktracker(IEvaluatorContext __eval) { public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + java.util.List producers = this .getGenerators(); int size = producers.size(); @@ -234,8 +234,8 @@ public IBooleanResult buildBacktracker(IEvaluatorContext __eval) { public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + return evalBooleanExpression(this, __eval); } @@ -276,8 +276,8 @@ public IBooleanResult buildBacktracker(IEvaluatorContext __eval) { public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + java.util.List generators = this .getGenerators(); int size = generators.size(); @@ -328,13 +328,13 @@ public IMatchingResult buildMatcher(IEvaluatorContext __eval, boolean bindTypePa @Override public Result interpret(IEvaluator> __eval) { - + __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); + __eval.notifyAboutSuspension(this); return this.getExpression().interpret(__eval); } - + @Override public Result isDefined(IEvaluator> __eval) { return getExpression().isDefined(__eval); @@ -363,13 +363,13 @@ public IBooleanResult buildBacktracker(IEvaluatorContext __eval) { @Override public IMatchingResult buildMatcher(IEvaluatorContext eval, boolean bindTypeParameters) { org.rascalmpl.ast.Expression nameExpr = getExpression(); - + if (nameExpr.isQualifiedName()) { if (cachedConstructorType == null) { registerTypeCacheHandler(eval); cachedConstructorType = computeConstructorType(eval, nameExpr); } - + return new NodePattern(eval, this, null, nameExpr.getQualifiedName(), cachedConstructorType, visitArguments(eval, bindTypeParameters), visitKeywordArguments(eval, bindTypeParameters)); } @@ -391,25 +391,25 @@ private java.util.Map visitKeywordArguments(IEvaluatorC private Type computeConstructorType(IEvaluatorContext eval, org.rascalmpl.ast.Expression nameExpr) { java.util.List functions = new LinkedList(); - + String cons = Names.consName(nameExpr.getQualifiedName()); Type adt = eval.getCurrentEnvt().lookupAbstractDataType(Names.moduleName(nameExpr.getQualifiedName())); - + if (adt != null) { eval.getCurrentEnvt().getAllFunctions(adt, cons, functions); } else { eval.getCurrentEnvt().getAllFunctions(cons, functions); } - + if (functions.isEmpty()) { return null; // throw new UndeclaredVariable(Names.fullName(nameExpr.getQualifiedName()), this); } - + Type signature = getArgumentTypes(eval, false); Type constructorType = adt != null ? adt : TF.nodeType(); - + for (AbstractFunction candidate : functions) { if (candidate.getReturnType().isAbstractData() && !candidate.getReturnType().isBottom() && candidate.mayMatch(signature)) { Type decl = eval.getCurrentEnvt().getConstructor(candidate.getReturnType(), cons, signature); @@ -418,7 +418,7 @@ private Type computeConstructorType(IEvaluatorContext eval, org.rascalmpl.ast.Ex } } } - + return constructorType; } @@ -444,7 +444,7 @@ public void handleConstructorDeclaredEvent() { registeredCacheHandler = true; } } - + private void registerTypeCacheHandler(IEvaluatorContext eval) { if (!registeredTypeCacheHandler) { eval.getEvaluator().registerConstructorDeclaredListener( @@ -457,7 +457,7 @@ public void handleConstructorDeclaredEvent() { registeredTypeCacheHandler = true; } } - + private java.util.List visitArguments(IEvaluatorContext eval, boolean bindTypeParameters) { return buildMatchers(getArguments(), eval, bindTypeParameters); } @@ -465,7 +465,7 @@ private java.util.List visitArguments(IEvaluatorContext eval, b @Override public Result interpret(IEvaluator> eval) { eval.setCurrentAST(this); - eval.notifyAboutSuspension(this); + eval.notifyAboutSuspension(this); try { if (eval.isInterrupted()) { @@ -480,10 +480,10 @@ public Result interpret(IEvaluator> eval) { // Also, if we have not yet registered a handler when we cache the result, do so now. if (function == null) { function = this.getExpression().interpret(eval); - + if (this.getExpression().isQualifiedName() && function instanceof ICallableValue && ((ICallableValue) function).isStatic()) { org.rascalmpl.ast.QualifiedName qname = this.getExpression().getQualifiedName(); - + if (eval.getCurrentEnvt().isNameFinal(qname)) { this.cachedPrefix = function; registerCacheHandler(eval); @@ -506,13 +506,13 @@ public Result interpret(IEvaluator> eval) { } actuals[i] = resultElem.getValue(); } - + java.util.Map kwActuals = Collections.emptyMap(); - + if (hasKeywordArguments()) { KeywordArguments_Expression keywordArgs = this.getKeywordArguments(); Type kwFormals = function.getKeywordArgumentTypes(eval.getCurrentEnvt()); - + if (keywordArgs.isDefault()){ kwActuals = new HashMap(); @@ -557,11 +557,11 @@ public Type typeOf(Environment env, IEvaluator> eval, boolean ins if (lambda.isString()) { return TF.nodeType(); } - + if (lambda.isSourceLocation()) { return lambda; } - + if (lambda.isFunction()) { return lambda.getReturnType(); } @@ -586,11 +586,11 @@ public IBooleanResult buildBacktracker(IEvaluatorContext __eval) { } - + @Override public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); + __eval.notifyAboutSuspension(this); Environment env = __eval.getCurrentEnvt(); Parameters parameters = getParameters(); @@ -599,7 +599,7 @@ public Result interpret(IEvaluator> __eval) { Type kwParams = TF.tupleEmpty(); java.util.List kwd = parameters.getKeywordFormals().hasKeywordFormalList() ? parameters.getKeywordFormals().getKeywordFormalList() : Collections.emptyList(); - + if (parameters.hasKeywordFormals() && parameters.getKeywordFormals().hasKeywordFormalList()) { kwParams = TypeDeclarationEvaluator.computeKeywordParametersType(kwd, __eval); } @@ -637,14 +637,14 @@ public IBooleanResult buildBacktracker(IEvaluatorContext __eval) { } - + @Override public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + Result left = this.getLhs().interpret(__eval); Result right = this.getRhs().interpret(__eval); return left.compose(right); @@ -670,14 +670,14 @@ public IBooleanResult buildBacktracker(IEvaluatorContext __eval) { } - + @Override public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + return this.getComprehension().interpret(__eval); } @@ -685,11 +685,11 @@ public Result interpret(IEvaluator> __eval) { } static public class Concrete extends org.rascalmpl.ast.Expression.Concrete { - + public Concrete(ISourceLocation src, IConstructor node, org.rascalmpl.ast.Concrete concrete) { super(src, node, concrete); } - + @Override public Type typeOf(Environment env, IEvaluator> eval, boolean instantiateTypeParameters) { return RascalValueFactory.Tree; @@ -699,13 +699,13 @@ public Type typeOf(Environment env, IEvaluator> eval, boolean ins public Result interpret(IEvaluator> eval) { throw new SyntaxError("concrete syntax fragment", getLocation()); } - + @Override public IMatchingResult buildMatcher(IEvaluatorContext eval, boolean bindTypeParameters) { throw new SyntaxError("concrete syntax fragment", getLocation()); } } - + static public class Descendant extends org.rascalmpl.ast.Expression.Descendant { @@ -737,14 +737,14 @@ public IBooleanResult buildBacktracker(IEvaluatorContext eval) { throw new UnexpectedType(TF.boolType(), interpret(eval.getEvaluator()).getStaticType(), this); } - + @Override public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + Result left = this.getLhs().interpret(__eval); Result right = this.getRhs().interpret(__eval); return left.divide(right); @@ -771,8 +771,8 @@ public IBooleanResult buildBacktracker(IEvaluatorContext eval) { public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + Environment old = __eval.getCurrentEnvt(); try { __eval.pushEnv(); @@ -804,13 +804,13 @@ public IBooleanResult buildBacktracker(IEvaluatorContext __eval) { return new BasicBooleanResult(__eval, this); } - + @Override public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + Result left = this.getLhs().interpret(__eval); Result right = this.getRhs().interpret(__eval); Result result = left.equals(right); @@ -836,9 +836,9 @@ public IBooleanResult buildBacktracker(IEvaluatorContext eval) { @Override public Result interpret(IEvaluator> __eval) { - + __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); + __eval.notifyAboutSuspension(this); return evalBooleanExpression(this, __eval); } @@ -861,14 +861,14 @@ public IBooleanResult buildBacktracker(IEvaluatorContext __eval) { @Override public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); + __eval.notifyAboutSuspension(this); Result expr = this.getExpression().interpret(__eval); String field = org.rascalmpl.interpreter.utils.Names.name(this.getField()); return expr.fieldAccess(field, __eval.getCurrentEnvt().getStore()); } - + @Override public Result isDefined(IEvaluator> __eval) { Result expr = this.getExpression().interpret(__eval); @@ -894,19 +894,19 @@ public IBooleanResult buildBacktracker(IEvaluatorContext __eval) { } - + @Override public Result interpret(IEvaluator> __eval) { - + __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); + __eval.notifyAboutSuspension(this); Result base = this.getExpression().interpret(__eval); java.util.List fields = this.getFields(); return base.fieldSelect(fields.toArray(new Field[0])); } - + } static public class FieldUpdate extends @@ -927,14 +927,14 @@ public IBooleanResult buildBacktracker(IEvaluatorContext __eval) { } - + @Override public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + Result expr = this.getExpression().interpret(__eval); Result repl = this.getReplacement().interpret(__eval); String name = org.rascalmpl.interpreter.utils.Names.name(this @@ -959,14 +959,14 @@ public IBooleanResult buildBacktracker(IEvaluatorContext __eval) { return new BasicBooleanResult(__eval, this); } - + @Override public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + Result base = this.getExpression().interpret(__eval); String annoName = org.rascalmpl.interpreter.utils.Names.name(this .getName()); @@ -1001,14 +1001,14 @@ public IBooleanResult buildBacktracker(IEvaluatorContext __eval) { return new BasicBooleanResult(__eval, this); } - + @Override public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + Result left = this.getLhs().interpret(__eval); Result right = this.getRhs().interpret(__eval); Result result = left.greaterThan(right); @@ -1031,14 +1031,14 @@ public IBooleanResult buildBacktracker(IEvaluatorContext __eval) { return new BasicBooleanResult(__eval, this); } - + @Override public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + Result left = this.getLhs().interpret(__eval); Result right = this.getRhs().interpret(__eval); Result result = left.greaterThanOrEqual(right); @@ -1070,36 +1070,36 @@ private boolean isBootstrapped(IEvaluatorContext eval) { return false; } - private ITree parseObject(IEvaluatorContext eval, IConstructor grammar, ISet filters, ISourceLocation location, char[] input, boolean allowAmbiguity, boolean hasSideEffects) { + private ITree parseObject(IEvaluatorContext eval, IConstructor grammar, ISet filters, ISourceLocation location, char[] input, boolean allowAmbiguity, boolean allowRecovery, boolean hasSideEffects) { RascalFunctionValueFactory vf = eval.getFunctionValueFactory(); IString str = vf.string(new String(input)); - + if (isBootstrapped(eval)) { return (ITree) vf.bootstrapParsers().call(grammar, str, location); } else { - IFunction parser = vf.parser(grammar, vf.bool(allowAmbiguity), vf.bool(hasSideEffects), vf.bool(false), filters); + IFunction parser = vf.parser(grammar, vf.bool(allowAmbiguity), vf.bool(allowRecovery), vf.bool(hasSideEffects), vf.bool(false), filters); return (ITree) parser.call(vf.string(new String(input)), location); } } - private ITree parseObject(IEvaluatorContext eval, IConstructor grammar, ISet filters, ISourceLocation location, boolean allowAmbiguity, boolean hasSideEffects) { + private ITree parseObject(IEvaluatorContext eval, IConstructor grammar, ISet filters, ISourceLocation location, boolean allowAmbiguity, boolean allowRecovery, boolean hasSideEffects) { RascalFunctionValueFactory vf = eval.getFunctionValueFactory(); - + if (isBootstrapped(eval)) { return (ITree) vf.bootstrapParsers().call(grammar, location, location); } else { - IFunction parser = vf.parser(grammar, vf.bool(allowAmbiguity), vf.bool(hasSideEffects), vf.bool(false), filters); + IFunction parser = vf.parser(grammar, vf.bool(allowAmbiguity), vf.bool(allowRecovery), vf.bool(hasSideEffects), vf.bool(false), filters); return (ITree) parser.call(location, location); } } @Override public Result interpret(IEvaluator> __eval) { - + __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); + __eval.notifyAboutSuspension(this); Result result = this.getArgument().interpret(__eval); Type expected = getType().typeOf(__eval.getCurrentEnvt(), __eval, instantiateTypeParameters); @@ -1107,11 +1107,11 @@ public Result interpret(IEvaluator> __eval) { if (!(expected instanceof NonTerminalType)) { throw new UnsupportedOperation("inline parsing", expected, this); } - + if (!result.getStaticType().isSubtypeOf(TF.stringType()) && !result.getStaticType().isSubtypeOf(TF.sourceLocationType())) { throw new UnsupportedOperation("inline parsing", result.getStaticType(), this); } - + IConstructor symbol = ((NonTerminalType) expected).getSymbol(); if (!SymbolAdapter.isSort(symbol) && !SymbolAdapter.isLex(symbol) && !SymbolAdapter.isLayouts(symbol) && !SymbolAdapter.isStartSort(symbol)) { throw new UnsupportedOperation("inline parsing", expected, this); @@ -1120,18 +1120,18 @@ public Result interpret(IEvaluator> __eval) { __eval.__setInterrupt(false); try { IConstructor tree = null; - + IMap gr = isBootstrapped(__eval) ? __eval.getValueFactory().map() : (IMap) __eval.getEvaluator().getGrammar(__eval.getCurrentEnvt()).get("rules"); IConstructor value = ((IRascalValueFactory) __eval.getValueFactory()).reifiedType(symbol, gr); - + if (result.getStaticType().isString()) { tree = parseObject(__eval, value, VF.set(), this.getLocation(), - ((IString) result.getValue()).getValue().toCharArray(), true, false); + ((IString) result.getValue()).getValue().toCharArray(), true, false, false); } else if (result.getStaticType().isSourceLocation()) { - tree = parseObject(__eval, value, VF.set(), (ISourceLocation) result.getValue(), true, false); + tree = parseObject(__eval, value, VF.set(), (ISourceLocation) result.getValue(), true, false, false); } - + assert tree != null; // because we checked earlier return org.rascalmpl.interpreter.result.ResultFactory @@ -1163,9 +1163,9 @@ public IBooleanResult buildBacktracker(IEvaluatorContext eval) { @Override public Result interpret(IEvaluator> __eval) { - + __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); + __eval.notifyAboutSuspension(this); Result result = getExpression().interpret(__eval).has(getName()); return makeResult(TF.boolType(), result.getValue(), __eval); @@ -1184,8 +1184,8 @@ public IfDefinedOtherwise(ISourceLocation __param1, IConstructor tree, @Override public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + if (getLhs().isDefined(__eval).getValue().getValue()) { try { return getLhs().interpret(__eval); @@ -1222,8 +1222,8 @@ public IBooleanResult buildBacktracker(IEvaluatorContext __eval) { public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + Environment old = __eval.getCurrentEnvt(); __eval.pushEnv(); @@ -1264,9 +1264,9 @@ public IBooleanResult buildBacktracker(IEvaluatorContext eval) { @Override public Result interpret(IEvaluator> __eval) { - + __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); + __eval.notifyAboutSuspension(this); return evalBooleanExpression(this, __eval); } @@ -1285,14 +1285,14 @@ public IBooleanResult buildBacktracker(IEvaluatorContext __eval) { return new BasicBooleanResult(__eval, this); } - + @Override public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + Result left = this.getLhs().interpret(__eval); Result right = this.getRhs().interpret(__eval); Result result = right.in(left); @@ -1319,14 +1319,14 @@ public IBooleanResult buildBacktracker(IEvaluatorContext __eval) { } - + @Override public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + Result left = this.getLhs().interpret(__eval); Result right = this.getRhs().interpret(__eval); return left.intersect(right); @@ -1348,9 +1348,9 @@ public IBooleanResult buildBacktracker(IEvaluatorContext eval) { @Override public Result interpret(IEvaluator> __eval) { - + __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); + __eval.notifyAboutSuspension(this); Result result = getExpression().interpret(__eval).is(getName()); return makeResult(TF.boolType(), result.getValue(), __eval); @@ -1373,8 +1373,8 @@ public IBooleanResult buildBacktracker(IEvaluatorContext eval) { public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + return makeResult(TF.boolType(), getArgument().isDefined(__eval).getValue(), __eval); } @@ -1390,8 +1390,8 @@ public It(ISourceLocation __param1, IConstructor tree) { public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + Result v = __eval.getCurrentEnvt().getVariable(IT); if (v == null) { throw new UnguardedIt(this); @@ -1399,7 +1399,7 @@ public Result interpret(IEvaluator> __eval) { return v; } - + @Override public IBooleanResult buildBacktracker(IEvaluatorContext eval) { return new BasicBooleanResult(eval, this); @@ -1418,8 +1418,8 @@ public Join(ISourceLocation __param1, IConstructor tree, org.rascalmpl.ast.Expre public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + Result left = this.getLhs().interpret(__eval); Result right = this.getRhs().interpret(__eval); return left.join(right); @@ -1442,9 +1442,9 @@ public IBooleanResult buildBacktracker(IEvaluatorContext eval) { @Override public Result interpret(IEvaluator> __eval) { - + __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); + __eval.notifyAboutSuspension(this); Result left = this.getLhs().interpret(__eval); Result right = this.getRhs().interpret(__eval); @@ -1467,13 +1467,13 @@ public IBooleanResult buildBacktracker(IEvaluatorContext eval) { return new BasicBooleanResult(eval, this); } - + @Override public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + Result left = this.getLhs().interpret(__eval); Result right = this.getRhs().interpret(__eval); Result result = left.lessThanOrEqual(right); @@ -1505,8 +1505,8 @@ public IMatchingResult buildMatcher(IEvaluatorContext eval, boolean bindTypePara @Override public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + java.util.List elements = getElements0(); Type elementType = TF.voidType(); @@ -1514,7 +1514,7 @@ public Result interpret(IEvaluator> __eval) { for (org.rascalmpl.ast.Expression expr : elements) { boolean isSplicedElem = expr.isSplice() || expr.isSplicePlus(); - + Result resultElem = null; if (!isSplicedElem) { @@ -1525,7 +1525,7 @@ public Result interpret(IEvaluator> __eval) { } } - + if (isSplicedElem){ resultElem = expr.getArgument().interpret(__eval); if (resultElem.getStaticType().isBottom()) { @@ -1541,7 +1541,7 @@ public Result interpret(IEvaluator> __eval) { results.add(val); } continue; - } + } } elementType = elementType.lub(resultElem.getStaticType()); @@ -1581,7 +1581,7 @@ public IBooleanResult buildBacktracker(IEvaluatorContext eval) { throw new UnexpectedType(TF.boolType(), interpret(eval.getEvaluator()).getStaticType(), this); } - + @Override public IMatchingResult buildMatcher(IEvaluatorContext __eval, boolean bindTypeParameters) { return this.getLiteral().buildMatcher(__eval, bindTypeParameters); @@ -1589,9 +1589,9 @@ public IMatchingResult buildMatcher(IEvaluatorContext __eval, boolean bindTypePa @Override public Result interpret(IEvaluator> __eval) { - + __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); + __eval.notifyAboutSuspension(this); return this.getLiteral().interpret(__eval); } @@ -1623,9 +1623,9 @@ public IMatchingResult buildMatcher(IEvaluatorContext __eval, boolean bindTypePa @Override public Result interpret(IEvaluator> __eval) { - + __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); + __eval.notifyAboutSuspension(this); java.util.List mappings = this.getMappings(); java.util.Map seen = new HashMap<>(); @@ -1636,7 +1636,7 @@ public Result interpret(IEvaluator> __eval) { for (Mapping_Expression mapping : mappings) { Result keyResult = mapping.getFrom().interpret(__eval); Result valueResult = mapping.getTo().interpret(__eval); - + if (keyResult.getStaticType().isBottom()) { throw new NonVoidTypeRequired(mapping.getFrom()); } @@ -1644,26 +1644,26 @@ public Result interpret(IEvaluator> __eval) { if (valueResult.getStaticType().isBottom()) { throw new NonVoidTypeRequired(mapping.getTo()); } - + IValue key = keyResult.getValue(); keyType = keyType.lub(keyResult.getStaticType()); valueType = valueType.lub(valueResult.getStaticType()); IValue keyValue = seen.get(key); - + if (keyValue != null) { throw org.rascalmpl.exceptions.RuntimeExceptionFactory .MultipleKey(keyResult.getValue(), keyValue, valueResult.getValue(), mapping.getFrom(), __eval .getStackTrace()); } - + seen.put(key, valueResult.getValue()); w.put(key, valueResult.getValue()); } Type type = TF.mapType(keyType, valueType); - + return org.rascalmpl.interpreter.result.ResultFactory.makeResult(type, w.done(), __eval); } @@ -1672,12 +1672,12 @@ public Result interpret(IEvaluator> __eval) { public Type typeOf(Environment env, IEvaluator> eval, boolean instantiateTypeParameters) { Type keyType = TF.voidType(); Type valueType = TF.valueType(); - + for (Mapping_Expression me : getMappings()) { keyType = keyType.lub(me.getFrom().typeOf(env, eval, instantiateTypeParameters)); valueType = valueType.lub(me.getTo().typeOf(env, eval, instantiateTypeParameters)); } - + return TF.mapType(keyType, valueType); } @@ -1697,10 +1697,10 @@ public IBooleanResult buildBacktracker(IEvaluatorContext eval) { @Override public Result interpret(IEvaluator> __eval) { - + __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + return evalBooleanExpression(this, __eval); } @@ -1724,17 +1724,17 @@ public IBooleanResult buildBacktracker(IEvaluatorContext __eval) { public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + Result left = this.getLhs().interpret(__eval); Result right = this.getRhs().interpret(__eval); - + return left.modulo(right); } } - + static public class Remainder extends org.rascalmpl.ast.Expression.Remainder { public Remainder(ISourceLocation __param1, IConstructor tree, org.rascalmpl.ast.Expression __param2, @@ -1755,8 +1755,8 @@ public IBooleanResult buildBacktracker(IEvaluatorContext __eval) { public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + Result left = this.getLhs().interpret(__eval); Result right = this.getRhs().interpret(__eval); return left.remainder(right); @@ -1782,10 +1782,10 @@ public IMatchingResult buildMatcher(IEvaluatorContext eval, boolean bindTypePara public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); + __eval.notifyAboutSuspension(this); __eval.warning("Var* is deprecated, use *Var or *Type Var instead", this.getLocation()); System.err.println(this.getLocation() + ": Var* is deprecated, use *Var instead"); - + Name name = this.getName(); Result variable = __eval.getCurrentEnvt().getVariable(name); @@ -1810,7 +1810,7 @@ public Type typeOf(Environment env, IEvaluator> eval, boolean ins } } - + static public class Splice extends org.rascalmpl.ast.Expression.Splice { @@ -1825,12 +1825,12 @@ public IMatchingResult buildMatcher(IEvaluatorContext eval, boolean bindTypePara Environment env = eval.getCurrentEnvt(); Type type = arg.getType().typeOf(env, eval.getEvaluator(), instantiateTypeParameters); type = type.instantiate(env.getStaticTypeBindings()); - + // TODO: Question, should we allow non terminal types in splices? if (type instanceof NonTerminalType) { throw new UnsupportedOperation("splicing match", type, this); // throw new ImplementationError(null); - } + } return new TypedMultiVariablePattern(eval, this, type, arg.getName(), bindTypeParameters); } if(arg.hasQualifiedName()){ @@ -1841,9 +1841,9 @@ public IMatchingResult buildMatcher(IEvaluatorContext eval, boolean bindTypePara @Override public Result interpret(IEvaluator> __eval) { - + __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); + __eval.notifyAboutSuspension(this); Name name = this.getName(); Result variable = __eval.getCurrentEnvt().getVariable(name); @@ -1890,9 +1890,9 @@ public IBooleanResult buildBacktracker(IEvaluatorContext eval) { @Override public Result interpret(IEvaluator> __eval) { - + __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); + __eval.notifyAboutSuspension(this); return evalBooleanExpression(this, __eval); } @@ -1916,7 +1916,7 @@ public IBooleanResult buildBacktracker(IEvaluatorContext __eval) { .interpret(__eval.getEvaluator()).getStaticType(), this); } - + @Override public IMatchingResult buildMatcher(IEvaluatorContext __eval, boolean bindTypeParameters) { return new NegativePattern(__eval, this, getArgument().buildMatcher(__eval, bindTypeParameters)); @@ -1924,9 +1924,9 @@ public IMatchingResult buildMatcher(IEvaluatorContext __eval, boolean bindTypePa @Override public Result interpret(IEvaluator> __eval) { - + __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); + __eval.notifyAboutSuspension(this); Result arg = this.getArgument().interpret(__eval); return arg.negative(); @@ -1945,13 +1945,13 @@ public IBooleanResult buildBacktracker(IEvaluatorContext eval) { return new MatchResult(eval, getPattern(), false, getExpression()); } - + @Override public Result interpret(IEvaluator> __eval) { - + __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); + __eval.notifyAboutSuspension(this); return evalBooleanExpression(this, __eval); } @@ -1966,9 +1966,9 @@ public NonEmptyBlock(ISourceLocation __param1, IConstructor tree, java.util.List @Override public Result interpret(IEvaluator> __eval) { - + __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); + __eval.notifyAboutSuspension(this); return ASTBuilder.make("Statement", "NonEmptyBlock", this.getLocation(), ASTBuilder.make("Label", "Empty", this.getLocation()), @@ -1989,14 +1989,14 @@ public IBooleanResult buildBacktracker(IEvaluatorContext eval) { return new BasicBooleanResult(eval, this); } - + @Override public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + Result left = this.getLhs().interpret(__eval); Result right = this.getRhs().interpret(__eval); Result result = left.nonEquals(right); @@ -2017,14 +2017,14 @@ public IBooleanResult buildBacktracker(IEvaluatorContext eval) { return new BasicBooleanResult(eval, this); } - + @Override public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + Result left = this.getLhs().interpret(__eval); Result right = this.getRhs().interpret(__eval); Result result = right.notIn(left); @@ -2048,9 +2048,9 @@ public IBooleanResult buildBacktracker(IEvaluatorContext eval) { @Override public Result interpret(IEvaluator> __eval) { - + __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); + __eval.notifyAboutSuspension(this); return evalBooleanExpression(this, __eval); } @@ -2072,13 +2072,13 @@ public IBooleanResult buildBacktracker(IEvaluatorContext __eval) { } - + @Override public Result interpret(IEvaluator> __eval) { - + __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); + __eval.notifyAboutSuspension(this); Result left = this.getLhs().interpret(__eval); Result right = this.getRhs().interpret(__eval); @@ -2139,8 +2139,8 @@ public IMatchingResult buildMatcher(IEvaluatorContext eval, boolean bindTypePara public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + org.rascalmpl.ast.QualifiedName name = this.getQualifiedName(); Result variable = __eval.getCurrentEnvt().getVariable(name); @@ -2159,18 +2159,18 @@ public Result interpret(IEvaluator> __eval) { return variable; } - + @Override public Result isDefined(IEvaluator> __eval) { org.rascalmpl.ast.QualifiedName name = this.getQualifiedName(); String fullName = Names.fullName(name); Result variable = __eval.getCurrentEnvt().getSimpleVariable(AbstractFunction.makeIsSetKeywordParameterName(fullName)); - + if (variable == null || variable.getValue() == null) { variable = __eval.getCurrentEnvt().getVariable(name); __eval.warning("deprecated feature: run-time check on variable initialization", getLocation()); return org.rascalmpl.interpreter.result.ResultFactory.bool(variable != null && variable.getValue() != null, __eval); - + // TODO: replace above by this // it was not a keyword parameter // throw new UndeclaredKeywordParameter(__eval.getCurrentEnvt().getName(), fullName, this); @@ -2203,14 +2203,14 @@ public IBooleanResult buildBacktracker(IEvaluatorContext __eval) { } - + @Override public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + // IListWriter w = vf.listWriter(tf.integerType()); Result from = this.getFirst().interpret(__eval); Result to = this.getLast().interpret(__eval); @@ -2232,17 +2232,17 @@ public Reducer(ISourceLocation __param1, IConstructor tree, org.rascalmpl.ast.Ex public IBooleanResult getBacktracker(IEvaluatorContext ctx) { return new BasicBooleanResult(ctx, this); } - + @Override public IBooleanResult buildBacktracker(IEvaluatorContext eval) { return getBacktracker(eval); } - + @Override public Result interpret(IEvaluator> __eval) { - + __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); + __eval.notifyAboutSuspension(this); org.rascalmpl.ast.Expression init = getInit(); org.rascalmpl.ast.Expression result = getResult(); @@ -2294,7 +2294,7 @@ public Result interpret(IEvaluator> __eval) { static public class ReifiedType extends org.rascalmpl.ast.Expression.ReifiedType { private static final Type defType = TypeFactory.getInstance().mapType(RascalValueFactory.Symbol, RascalValueFactory.Production); - + public ReifiedType(ISourceLocation __param1, IConstructor tree, org.rascalmpl.ast.Expression __param2, org.rascalmpl.ast.Expression __param3 @@ -2309,30 +2309,30 @@ public IMatchingResult buildMatcher(IEvaluatorContext eval, boolean bindTypePara @Override public Result interpret(IEvaluator> __eval) { - + __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); + __eval.notifyAboutSuspension(this); Result symbol = getSymbol().interpret(__eval); Result declarations = getDefinitions().interpret(__eval); - + if (!symbol.getStaticType().isSubtypeOf(RascalValueFactory.Symbol)) { throw new UnexpectedType(RascalValueFactory.Symbol, symbol.getStaticType(), getSymbol()); } - + if (!declarations.getStaticType().isSubtypeOf(defType)) { throw new UnexpectedType(defType, declarations.getStaticType(), getSymbol()); } - - + + IValue val = IRascalValueFactory.getInstance().reifiedType( - (IConstructor) symbol.getValue(), + (IConstructor) symbol.getValue(), (IMap) declarations.getValue() ); Type typ = RascalValueFactory.Type.instantiate( Collections.singletonMap(RascalValueFactory.TypeParam, TF.valueType())); - + return ResultFactory.makeResult(typ, val, __eval); } @@ -2354,11 +2354,11 @@ public ReifyType(ISourceLocation __param1, IConstructor tree, org.rascalmpl.ast. @Override public Result interpret(IEvaluator> eval) { eval.setCurrentAST(this); - eval.notifyAboutSuspension(this); + eval.notifyAboutSuspension(this); Type t = getType().typeOf(eval.getCurrentEnvt(), eval, instantiateTypeParameters); IMap gr = eval.__getVf().mapWriter().done(); - + if (!t.isTop()) { // if t == value then let's not call the parser generator. // the reason is that #value occurs in the parser generator itself @@ -2366,16 +2366,16 @@ public Result interpret(IEvaluator> eval) { // each other gr = (IMap) eval.getEvaluator().getGrammar(eval.getCurrentEnvt()).get("rules"); } - + IConstructor value; - + if (t instanceof NonTerminalType) { value = ((IRascalValueFactory) eval.getValueFactory()).reifiedType(((NonTerminalType) t).getSymbol(), gr); } else { value = new TypeReifier(eval.__getVf()).typeToValue(t, eval.getCurrentEnvt().getStore(), gr); } - + // the static type of a reified type is always equal to its dynamic type return makeResult(value.getType(), value, eval); } @@ -2407,8 +2407,8 @@ public IMatchingResult buildMatcher(IEvaluatorContext eval, boolean bindTypePara public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + java.util.List elements = this .getElements0(); @@ -2417,7 +2417,7 @@ public Result interpret(IEvaluator> __eval) { for (org.rascalmpl.ast.Expression expr : elements) { Result resultElem; - + if(expr.isSplice() || expr.isSplicePlus()){ resultElem = expr.getArgument().interpret(__eval); if (resultElem.getStaticType().isBottom()) { @@ -2444,11 +2444,11 @@ public Result interpret(IEvaluator> __eval) { elementType = elementType.lub(resultElem.getStaticType()); results.add(results.size(), resultElem.getValue()); } - + Type resultType = TF.setType(elementType); ISetWriter w = __eval.__getVf().setWriter(); w.insertAll(results); - + return makeResult(resultType, w.done(), __eval); } @@ -2459,7 +2459,7 @@ public Type typeOf(Environment env, IEvaluator> eval, boolean ins for (org.rascalmpl.ast.Expression elt : getElements0()) { Type eltType = elt.typeOf(env, eval, instantiateTypeParameters); - + // TODO: here we need to properly deal with splicing operators!!! if (eltType.isSet()) { eltType = eltType.getElementType(); @@ -2492,9 +2492,9 @@ public IBooleanResult buildBacktracker(IEvaluatorContext __eval) { @Override public Result interpret(IEvaluator> __eval) { - + __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); + __eval.notifyAboutSuspension(this); Result base = this.getExpression().interpret(__eval); String annoName = org.rascalmpl.interpreter.utils.Names.name(this @@ -2523,13 +2523,13 @@ public IBooleanResult buildBacktracker(IEvaluatorContext __eval) { } - + @Override public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); + __eval.notifyAboutSuspension(this); Result from = this.getFirst().interpret(__eval); Result to = this.getLast().interpret(__eval); @@ -2539,11 +2539,11 @@ public Result interpret(IEvaluator> __eval) { } } - + static public class Slice extends org.rascalmpl.ast.Expression.Slice { - public Slice(ISourceLocation __param1, IConstructor tree, org.rascalmpl.ast.Expression __param2, + public Slice(ISourceLocation __param1, IConstructor tree, org.rascalmpl.ast.Expression __param2, org.rascalmpl.ast.OptionalExpression __param3, org.rascalmpl.ast.OptionalExpression __param4) { super(__param1, tree, __param2, __param3, __param4); } @@ -2557,21 +2557,21 @@ public IBooleanResult buildBacktracker(IEvaluatorContext eval) { public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); + __eval.notifyAboutSuspension(this); Result expr = this.getExpression().interpret(__eval); - + Result first = this.getOptFirst().hasExpression() ? this.getOptFirst().getExpression().interpret(__eval) : null; Result last = this.getOptLast().hasExpression() ? this.getOptLast().getExpression().interpret(__eval) : null; - + return expr.slice(first, null, last); } } - + static public class SliceStep extends org.rascalmpl.ast.Expression.SliceStep { - public SliceStep(ISourceLocation __param1, IConstructor tree, org.rascalmpl.ast.Expression __param2, + public SliceStep(ISourceLocation __param1, IConstructor tree, org.rascalmpl.ast.Expression __param2, org.rascalmpl.ast.OptionalExpression __param3, org.rascalmpl.ast.Expression __param4, org.rascalmpl.ast.OptionalExpression __param5) { super(__param1, tree, __param2, __param3, __param4, __param5); } @@ -2585,14 +2585,14 @@ public IBooleanResult buildBacktracker(IEvaluatorContext eval) { public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); + __eval.notifyAboutSuspension(this); Result expr = this.getExpression().interpret(__eval); - + Result first = this.getOptFirst().hasExpression() ? this.getOptFirst().getExpression().interpret(__eval) : null; Result second = this.getSecond().interpret(__eval); Result last = this.getOptLast().hasExpression() ? this.getOptLast().getExpression().interpret(__eval) : null; - + return expr.slice(first, second, last); } } @@ -2615,23 +2615,23 @@ public Result isDefined(IEvaluator> __eval) { Result expr = this.getExpression().interpret(__eval); int nSubs = this.getSubscripts().size(); Result subscripts[] = new Result[nSubs]; - + for (int i = 0; i < nSubs; i++) { org.rascalmpl.ast.Expression subsExpr = this.getSubscripts() .get(i); subscripts[i] = isWildCard(subsExpr) ? null : subsExpr.interpret(__eval); } - + return expr.isKeyDefined(subscripts); } - + @Override public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + Result expr = this.getExpression().interpret(__eval); int nSubs = this.getSubscripts().size(); Result subscripts[] = new Result[nSubs]; @@ -2644,7 +2644,7 @@ public Result interpret(IEvaluator> __eval) { return expr.subscript(subscripts); } - + private boolean isWildCard(org.rascalmpl.ast.Expression fieldName) { if (fieldName.isQualifiedName()) { return ((org.rascalmpl.semantics.dynamic.QualifiedName.Default) fieldName.getQualifiedName()).lastName().equals("_"); @@ -2672,14 +2672,14 @@ public IBooleanResult buildBacktracker(IEvaluatorContext __eval) { } - + @Override public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + Result left = this.getLhs().interpret(__eval); Result right = this.getRhs().interpret(__eval); return left.subtract(right); @@ -2704,14 +2704,14 @@ public IBooleanResult buildBacktracker(IEvaluatorContext __eval) { this); } - + @Override public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + return this.getArgument().interpret(__eval).transitiveClosure(); } @@ -2734,8 +2734,8 @@ public IBooleanResult buildBacktracker(IEvaluatorContext __eval) { @Override public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + return this.getArgument().interpret(__eval).transitiveReflexiveClosure(); } @@ -2762,9 +2762,9 @@ public IMatchingResult buildMatcher(IEvaluatorContext eval, boolean bindTypePara @Override public Result interpret(IEvaluator> __eval) { - + __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); + __eval.notifyAboutSuspension(this); java.util.List elements = this .getElements(); @@ -2774,11 +2774,11 @@ public Result interpret(IEvaluator> __eval) { for (int i = 0; i < elements.size(); i++) { Result resultElem = elements.get(i).interpret(__eval); - + if (resultElem.getStaticType().isBottom()) { throw new UnexpectedType(TF.valueType(), TF.voidType(), this); } - + types[i] = resultElem.getStaticType(); values[i] = resultElem.getValue(); } @@ -2825,8 +2825,8 @@ public IMatchingResult buildMatcher(IEvaluatorContext eval, boolean bindTypePara @Override public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + // TODO: should allow qualified names in TypeVariables?!? Result result = __eval.getCurrentEnvt().getFrameVariable(Names.name(this.getName())); @@ -2872,8 +2872,8 @@ public IMatchingResult buildMatcher(IEvaluatorContext eval, boolean bindTypePara public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + return this.getPattern().interpret(__eval); } @@ -2896,7 +2896,7 @@ public VariableBecomes(ISourceLocation __param1, IConstructor tree, Name __param @Override public IMatchingResult buildMatcher(IEvaluatorContext eval, boolean bindTypeParameters) { org.rascalmpl.ast.Expression pattern = this.getPattern(); - + if (pattern instanceof Splice) { throw new UnsupportedPattern("named splices (i.e. name:*pattern)", this); } @@ -2910,10 +2910,10 @@ public IMatchingResult buildMatcher(IEvaluatorContext eval, boolean bindTypePara @Override public Result interpret(IEvaluator> __eval) { - + __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + return this.getPattern().interpret(__eval); } @@ -2935,8 +2935,8 @@ public Visit(ISourceLocation __param1, IConstructor tree, Label __param2, public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.notifyAboutSuspension(this); - + __eval.notifyAboutSuspension(this); + return this.getVisit().interpret(__eval); } @@ -2960,34 +2960,34 @@ public IBooleanResult buildBacktracker(IEvaluatorContext __eval) { } - + @Override public Result interpret(IEvaluator> eval) { eval.setCurrentAST(this); - eval.notifyAboutSuspension(this); + eval.notifyAboutSuspension(this); Parameters parameters = getParameters(); Type formals = parameters.typeOf(eval.getCurrentEnvt(), eval, instantiateTypeParameters); Type kwParams = TF.tupleEmpty(); java.util.List kws = parameters.getKeywordFormals().hasKeywordFormalList() ? parameters.getKeywordFormals().getKeywordFormalList() : Collections.emptyList(); - + if (parameters.hasKeywordFormals() && parameters.getKeywordFormals().hasKeywordFormalList()) { kwParams = TypeDeclarationEvaluator.computeKeywordParametersType(kws, eval); } - return new RascalFunction(this, - eval, - null, - TF.functionType(TF.voidType(), formals, kwParams), + return new RascalFunction(this, + eval, + null, + TF.functionType(TF.voidType(), formals, kwParams), TF.functionType(TF.voidType(), formals, kwParams).instantiate(eval.getCurrentEnvt().getDynamicTypeBindings()), - kws, - this.getParameters().isVarArgs(), - false, - false, - this.getStatements0(), - eval.getCurrentEnvt(), + kws, + this.getParameters().isVarArgs(), + false, + false, + this.getStatements0(), + eval.getCurrentEnvt(), eval.__getAccumulators() ); } @@ -2996,7 +2996,7 @@ public Result interpret(IEvaluator> eval) { public Expression(ISourceLocation __param1, IConstructor tree) { super(__param1, tree); } - + private static java.util.List buildMatchers(java.util.List elements, IEvaluatorContext eval, boolean bindTypeParameters) { ArrayList args = new ArrayList(elements.size()); @@ -3004,10 +3004,10 @@ private static java.util.List buildMatchers(java.util.List evalBooleanExpression(org.rascalmpl.ast.Expression x, IEvaluatorContext ctx) { IBooleanResult mp = x.getBacktracker(ctx); mp.init(); diff --git a/src/org/rascalmpl/semantics/dynamic/Import.java b/src/org/rascalmpl/semantics/dynamic/Import.java index ebb4656494a..35088776167 100644 --- a/src/org/rascalmpl/semantics/dynamic/Import.java +++ b/src/org/rascalmpl/semantics/dynamic/Import.java @@ -487,7 +487,7 @@ public static ITree parseModuleAndFragments(char[] data, ISourceLocation locatio } else if (reg.exists(parserCacheFile)) { // if we cached a ModuleFile.parsers file, we will use the parser from that (typically after deployment time) - parsers = vf.loadParsers(parserCacheFile, vf.bool(false),vf.bool(false),vf.bool(false), vf.set()); + parsers = vf.loadParsers(parserCacheFile, vf.bool(false), vf.bool(false), vf.bool(false), vf.bool(false), vf.set()); } else { // otherwise we have to generate a fresh parser for this module now @@ -495,7 +495,7 @@ else if (reg.exists(parserCacheFile)) { IMap syntaxDefinition = env.getSyntaxDefinition(); IMap grammar = (IMap) eval.getParserGenerator().getGrammarFromModules(eval.getMonitor(),env.getName(), syntaxDefinition).get("rules"); IConstructor reifiedType = vf.reifiedType(dummy, grammar); - parsers = vf.parsers(reifiedType, vf.bool(false), vf.bool(false), vf.bool(false), vf.set()); + parsers = vf.parsers(reifiedType, vf.bool(false), vf.bool(false), vf.bool(false), vf.bool(false), vf.set()); } try { diff --git a/src/org/rascalmpl/util/visualize/dot/AttributeStatement.java b/src/org/rascalmpl/util/visualize/dot/AttributeStatement.java new file mode 100644 index 00000000000..34cd55e08ab --- /dev/null +++ b/src/org/rascalmpl/util/visualize/dot/AttributeStatement.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2024, NWO-I Centrum Wiskunde & Informatica (CWI) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ +package org.rascalmpl.util.visualize.dot; + +import java.io.PrintWriter; + +public class AttributeStatement { + enum Scope { GRAPH, NODE, EDGE } + + private Scope scope; + private DotAttribute attribute; + + void writeSource(PrintWriter writer) { + if (scope != null) { + switch (scope) { + case GRAPH: + writer.write("graph "); + break; + case NODE: + writer.write("node "); + break; + case EDGE: + writer.write("edge "); + break; + } + } + + attribute.writeSource(writer); + } +} diff --git a/src/org/rascalmpl/util/visualize/dot/CompassPoint.java b/src/org/rascalmpl/util/visualize/dot/CompassPoint.java new file mode 100644 index 00000000000..47e1c720ac1 --- /dev/null +++ b/src/org/rascalmpl/util/visualize/dot/CompassPoint.java @@ -0,0 +1,21 @@ +/** + * Copyright (c) 2024, NWO-I Centrum Wiskunde & Informatica (CWI) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + package org.rascalmpl.util.visualize.dot; + + + // Note: C means Center, X means unspecified. +public enum CompassPoint { + N, NE, E, SE, S, SW, W, NW, C, X; +} diff --git a/src/org/rascalmpl/util/visualize/dot/DotAttribute.java b/src/org/rascalmpl/util/visualize/dot/DotAttribute.java new file mode 100644 index 00000000000..ba8f4b438c8 --- /dev/null +++ b/src/org/rascalmpl/util/visualize/dot/DotAttribute.java @@ -0,0 +1,118 @@ +/** + * Copyright (c) 2024, NWO-I Centrum Wiskunde & Informatica (CWI) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + package org.rascalmpl.util.visualize.dot; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.List; + +public class DotAttribute { + public static final String ATTR_LABEL = "label"; + public static final String ATTR_COLOR = "color"; + public static final String ATTR_NODE_SHAPE = "shape"; + public static final String NODE_SHAPE_RECORD = "record"; + + String property; + String value; + + public DotAttribute(String property, String value) { + this.property = property; + this.value = value; + } + + public String getProperty() { + return property; + } + + public String getValue() { + return value; + } + + void setValue(String value) { + this.value = value; + } + + void writeSource(PrintWriter writer) { + NodeId.writeId(writer, property); + writer.write("="); + NodeId.writeId(writer, value); + } + + static void writeAttributes(PrintWriter writer, List attributes) { + if (!attributes.isEmpty()) { + writer.write("["); + boolean first = true; + for (DotAttribute attribute : attributes) { + if (first) { + first = false; + } else { + writer.write(", "); + } + attribute.writeSource(writer); + } + writer.write("]"); + } + } + + public static DotAttribute createRecordLabel(List elements) { + StringBuilder value = new StringBuilder(); + + boolean first = true; + for (String element : elements) { + if (first) { + first = false; + } else { + value.append(" | "); + } + + value.append('<'); + value.append(element); + value.append('>'); + value.append(' '); + value.append(element); + } + + return new DotAttribute(ATTR_LABEL, value.toString()); + } + + public static DotAttribute createArrayLabel(int size) { + StringBuilder value = new StringBuilder(); + + boolean first = true; + for (int i=0; i'); + value.append(' '); + + value.append(i); + } + + return new DotAttribute(ATTR_LABEL, value.toString()); + } + + public static DotAttribute createRecordLabel(DotRecord rec) { + StringWriter writer = new StringWriter(); + rec.writeSource(new PrintWriter(writer, true), true); + return new DotAttribute(ATTR_LABEL, writer.toString()); + } + +} diff --git a/src/org/rascalmpl/util/visualize/dot/DotEdge.java b/src/org/rascalmpl/util/visualize/dot/DotEdge.java new file mode 100644 index 00000000000..e8a7cb158d9 --- /dev/null +++ b/src/org/rascalmpl/util/visualize/dot/DotEdge.java @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2024, NWO-I Centrum Wiskunde & Informatica (CWI) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + package org.rascalmpl.util.visualize.dot; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +public class DotEdge implements DotStatement { + private NodeId from; + private List to; + private boolean directed; + private List attributes; + + public DotEdge(NodeId from) { + this(from, true); + } + + public DotEdge(NodeId from, boolean directed) { + this.from = from; + this.directed = directed; + to = new ArrayList<>(); + attributes = new ArrayList<>(); + } + + public DotEdge(NodeId from, NodeId... to) { + this(from, true, to); + } + + public DotEdge(NodeId from, boolean directed, NodeId... to) { + this(from, directed); + for (NodeId node : to) { + addTo(node); + } + } + + public void addTo(NodeId node) { + to.add(node); + } + + public void addAttribute(String property, String value) { + attributes.add(new DotAttribute(property, value)); + } + + @Override + public void writeSource(PrintWriter writer) { + from.writeSource(writer); + for (NodeId node : to) { + if (directed) { + writer.write(" -> "); + } else { + writer.write(" -- "); + } + + node.writeSource(writer); + } + DotAttribute.writeAttributes(writer, attributes); + } + + public static DotEdge createArrayEdge(NodeId array, int index, NodeId element) { + return new DotEdge(new NodeId(array, String.valueOf(index)), true, element); + } + + public static DotEdge createArrayEdge(NodeId array, int index, CompassPoint direction, NodeId element) { + return new DotEdge(new NodeId(array.getId(), String.valueOf(index), direction), true, element); + } +} + diff --git a/src/org/rascalmpl/util/visualize/dot/DotField.java b/src/org/rascalmpl/util/visualize/dot/DotField.java new file mode 100644 index 00000000000..86cf5f5eeae --- /dev/null +++ b/src/org/rascalmpl/util/visualize/dot/DotField.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2024, NWO-I Centrum Wiskunde & Informatica (CWI) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + package org.rascalmpl.util.visualize.dot; + +import java.io.PrintWriter; + +public class DotField implements DotRecordEntry { + private String label; + private String portId; + + public DotField(String label, String portId) { + this.label = label; + this.portId = portId; + } + + @Override + public void writeSource(PrintWriter writer, boolean topLevel) { + if (portId != null) { + writer.write('<'); + writer.write(portId); + writer.write("> "); + } + NodeId.writeString(writer, label); + } +} diff --git a/src/org/rascalmpl/util/visualize/dot/DotGraph.java b/src/org/rascalmpl/util/visualize/dot/DotGraph.java new file mode 100644 index 00000000000..9e14c448ee8 --- /dev/null +++ b/src/org/rascalmpl/util/visualize/dot/DotGraph.java @@ -0,0 +1,123 @@ +/** + * Copyright (c) 2024, NWO-I Centrum Wiskunde & Informatica (CWI) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + package org.rascalmpl.util.visualize.dot; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/* +Loosely follow (a subset of) the DotGraph syntax definition: +https://graphviz.org/doc/info/lang.html +*/ + +public class DotGraph { + private static final String COLOR_HIGHLIGHT = "red"; + + private String id; + private boolean digraph; + private List statements; + private Map nodes; + + public DotGraph(String id, boolean digraph) { + this.id = id; + this.digraph = digraph; + statements = new ArrayList<>(); + nodes = new HashMap<>(); + } + + private void addStatement(DotStatement statement) { + statements.add(statement); + } + + public void addNode(DotNode node) { + if (!nodes.containsKey(node.getId())) { + addStatement(node); + nodes.put(node.getId(), node); + } + } + + public void addNode(String id, String label) { + addNode(new NodeId(id), label); + } + + public void addNode(NodeId id, String label) { + DotNode node = new DotNode(id); + node.addAttribute(DotAttribute.ATTR_LABEL, label); + addNode(node); + } + + public void addArrayNode(NodeId id, int size) { + DotNode node = new DotNode(id); + node.addAttribute(DotAttribute.ATTR_NODE_SHAPE, DotAttribute.NODE_SHAPE_RECORD); + node.addAttribute(DotAttribute.createArrayLabel(size)); + addNode(node); + } + + public void addRecordNode(NodeId id, DotRecord dotRecord) { + DotNode node = new DotNode(id); + node.addAttribute(DotAttribute.ATTR_NODE_SHAPE, DotAttribute.NODE_SHAPE_RECORD); + node.addAttribute(DotAttribute.createRecordLabel(dotRecord)); + addNode(node); + } + + public void highlight(NodeId id) { + DotNode node = nodes.get(id); + if (node != null) { + node.addAttribute(DotAttribute.ATTR_COLOR, COLOR_HIGHLIGHT); + } + } + + public void addEdge(String from, String to) { + addEdge(new NodeId(from), new NodeId(to)); + } + + public void addEdge(NodeId from, NodeId to) { + addEdge(new DotEdge(from, true, to)); + } + + public void addEdge(NodeId from, NodeId to, String label) { + DotEdge edge = new DotEdge(from, true, to); + edge.addAttribute(DotAttribute.ATTR_LABEL, label); + addEdge(edge); + } + + public void addEdge(DotEdge edge) { + addStatement(edge); + } + + public void writeSource(PrintWriter writer) { + writer.write(digraph ? "digraph" : "graph"); + if (id != null) { + writer.write(" "); + writer.write(id); + } + writer.println(" {"); + for (DotStatement statement : statements) { + statement.writeSource(writer); + writer.println(";"); + } + writer.println("}"); + } + + public String toString() { + StringWriter writer = new StringWriter(); + writeSource(new PrintWriter(writer)); + return writer.toString(); + } +} diff --git a/src/org/rascalmpl/util/visualize/dot/DotNode.java b/src/org/rascalmpl/util/visualize/dot/DotNode.java new file mode 100644 index 00000000000..ae791e83633 --- /dev/null +++ b/src/org/rascalmpl/util/visualize/dot/DotNode.java @@ -0,0 +1,83 @@ +/** + * Copyright (c) 2024, NWO-I Centrum Wiskunde & Informatica (CWI) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + package org.rascalmpl.util.visualize.dot; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +public class DotNode implements DotStatement { + public static DotNode createArrayNode(NodeId id, int size) { + DotNode node = new DotNode(id); + node.addAttribute(DotAttribute.createArrayLabel(size)); + node.addAttribute(DotAttribute.ATTR_NODE_SHAPE, DotAttribute.NODE_SHAPE_RECORD); + return node; + } + + private NodeId id; + private List attributes; + + public DotNode(String id) { + this(new NodeId(id)); + } + + public DotNode(NodeId id) { + this.id = id; + attributes = new ArrayList<>(); + } + + public NodeId getId() { + return id; + } + + public void addAttribute(String property, String value) { + addAttribute(new DotAttribute(property, value)); + } + + public void addAttribute(DotAttribute attribute) { + attributes.add(attribute); + } + + public void setAttribute(String property, String value) { + for (DotAttribute attribute : attributes) { + if (attribute.getProperty().equals(property)) { + attribute.setValue(value); + return; + } + } + + addAttribute(property, value); + } + + public String getAttributeValue(String property) { + for (DotAttribute attribute : attributes) { + if (attribute.getProperty().equals(property)) { + return attribute.getValue(); + } + } + + return null; + } + + public void setLabel(String label) { + setAttribute(DotAttribute.ATTR_LABEL, label); + } + + @Override + public void writeSource(PrintWriter writer) { + id.writeSource(writer); + DotAttribute.writeAttributes(writer, attributes); + } +} diff --git a/src/org/rascalmpl/util/visualize/dot/DotRecord.java b/src/org/rascalmpl/util/visualize/dot/DotRecord.java new file mode 100644 index 00000000000..b503636539a --- /dev/null +++ b/src/org/rascalmpl/util/visualize/dot/DotRecord.java @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2024, NWO-I Centrum Wiskunde & Informatica (CWI) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + package org.rascalmpl.util.visualize.dot; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +public class DotRecord implements DotRecordEntry { + private List entries; + + public DotRecord() { + entries = new ArrayList<>(); + } + + public void addEntry(DotRecordEntry entry) { + entries.add(entry); + } + + @Override + public void writeSource(PrintWriter writer, boolean topLevel) { + if (!topLevel) { + writer.write("{ "); + } + boolean first = true; + for (DotRecordEntry entry : entries) { + if (first) { + first = false; + } else { + writer.write(" | "); + } + + entry.writeSource(writer, false); + } + if (!topLevel) { + writer.write("} "); + } + } +} diff --git a/src/org/rascalmpl/util/visualize/dot/DotRecordEntry.java b/src/org/rascalmpl/util/visualize/dot/DotRecordEntry.java new file mode 100644 index 00000000000..f54127c1bde --- /dev/null +++ b/src/org/rascalmpl/util/visualize/dot/DotRecordEntry.java @@ -0,0 +1,21 @@ +/** + * Copyright (c) 2024, NWO-I Centrum Wiskunde & Informatica (CWI) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + package org.rascalmpl.util.visualize.dot; + +import java.io.PrintWriter; + +public interface DotRecordEntry { + void writeSource(PrintWriter writer, boolean topLevel); +} diff --git a/src/org/rascalmpl/util/visualize/dot/DotStatement.java b/src/org/rascalmpl/util/visualize/dot/DotStatement.java new file mode 100644 index 00000000000..68af11f4749 --- /dev/null +++ b/src/org/rascalmpl/util/visualize/dot/DotStatement.java @@ -0,0 +1,21 @@ +/** + * Copyright (c) 2024, NWO-I Centrum Wiskunde & Informatica (CWI) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + package org.rascalmpl.util.visualize.dot; + +import java.io.PrintWriter; + +public interface DotStatement { + void writeSource(PrintWriter writer); +} diff --git a/src/org/rascalmpl/util/visualize/dot/DotSubgraph.java b/src/org/rascalmpl/util/visualize/dot/DotSubgraph.java new file mode 100644 index 00000000000..ac19707e6ac --- /dev/null +++ b/src/org/rascalmpl/util/visualize/dot/DotSubgraph.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2024, NWO-I Centrum Wiskunde & Informatica (CWI) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + package org.rascalmpl.util.visualize.dot; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +public class DotSubgraph implements DotStatement { + private String id; + private List statements; + + public DotSubgraph(String id) { + this.id = id; + statements = new ArrayList<>(); + } + + public void addStatement(DotStatement statement) { + statements.add(statement); + } + + public String getId() { + return id; + } + + @Override + public void writeSource(PrintWriter writer) { + writer.write("subgraph"); + if (id != null) { + NodeId.writeId(writer, id); + writer.write(" "); + } + writer.println(" {"); + for (DotStatement statement : statements) { + writer.write(" "); + statement.writeSource(writer); + writer.println(";"); + } + + writer.println(" }"); + } +} diff --git a/src/org/rascalmpl/util/visualize/dot/NodeId.java b/src/org/rascalmpl/util/visualize/dot/NodeId.java new file mode 100644 index 00000000000..8b4cbf5cb9f --- /dev/null +++ b/src/org/rascalmpl/util/visualize/dot/NodeId.java @@ -0,0 +1,130 @@ +/** + * Copyright (c) 2024, NWO-I Centrum Wiskunde & Informatica (CWI) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + package org.rascalmpl.util.visualize.dot; + +import java.io.PrintWriter; +import java.util.Objects; + +public class NodeId { + public static void writeId(PrintWriter stream, String id) { + stream.write("\""); + writeString(stream, id); + stream.write("\""); + } + + public static void writeString(PrintWriter stream, String s) { + for (int i=0; i getParserGenerator(), this, caller, parser, allowAmbiguity, hasSideEffects, firstAmbiguity, filters)); + return function(functionType, new ParametrizedParseFunction(() -> getParserGenerator(), this, caller, parser, allowAmbiguity, allowRecovery, hasSideEffects, firstAmbiguity, filters)); } @Override @@ -292,7 +296,7 @@ public void storeParsers(IValue reifiedGrammar, ISourceLocation saveLocation) th } @Override - public IFunction loadParsers(ISourceLocation saveLocation, IBool allowAmbiguity, IBool hasSideEffects, IBool firstAmbiguity, ISet filters) throws IOException, ClassNotFoundException { + public IFunction loadParsers(ISourceLocation saveLocation, IBool allowAmbiguity, IBool allowRecovery, IBool hasSideEffects, IBool firstAmbiguity, ISet filters) throws IOException, ClassNotFoundException { RascalTypeFactory rtf = RascalTypeFactory.getInstance(); TypeFactory tf = TypeFactory.getInstance(); @@ -318,12 +322,12 @@ public IFunction loadParsers(ISourceLocation saveLocation, IBool allowAmbiguity, this, caller, parser, - allowAmbiguity, hasSideEffects, firstAmbiguity, filters) + allowAmbiguity, allowRecovery, hasSideEffects, firstAmbiguity, filters) ); } @Override - public IFunction loadParser(IValue reifiedGrammar, ISourceLocation saveLocation, IBool allowAmbiguity, IBool hasSideEffects, IBool firstAmbiguity, ISet filters) throws IOException, ClassNotFoundException { + public IFunction loadParser(IValue reifiedGrammar, ISourceLocation saveLocation, IBool allowAmbiguity, IBool allowRecovery, IBool hasSideEffects, IBool firstAmbiguity, ISet filters) throws IOException, ClassNotFoundException { TypeFactory tf = TypeFactory.getInstance(); Type functionType = tf.functionType(reifiedGrammar.getType().getTypeParameters().getFieldType(0), @@ -347,7 +351,7 @@ public IFunction loadParser(IValue reifiedGrammar, ISourceLocation saveLocation, name = generator.getParserMethodName(startSort); } - return function(functionType, new ParseFunction(ctx.getValueFactory(), caller, parser, name, allowAmbiguity, hasSideEffects, firstAmbiguity, filters)); + return function(functionType, new ParseFunction(ctx.getValueFactory(), caller, parser, name, allowAmbiguity, allowRecovery, hasSideEffects, firstAmbiguity, filters)); } /** @@ -372,7 +376,7 @@ public IFunction bootstrapParsers() { AbstractAST current = ctx.getCurrentAST(); ISourceLocation caller = current != null ? current.getLocation() : URIUtil.rootLocation("unknown"); - return function(functionType, new ParametrizedParseFunction(() -> getParserGenerator(), this, caller, parser, vf.bool(false), vf.bool(false), vf.bool(false), ctx.getValueFactory().set())); + return function(functionType, new ParametrizedParseFunction(() -> getParserGenerator(), this, caller, parser, vf.bool(false), vf.bool(false), vf.bool(false), vf.bool(false), ctx.getValueFactory().set())); } public IString createHole(ITree part, IInteger index) { @@ -417,19 +421,21 @@ static private class ParseFunction implements BiFunction> parser; protected final String methodName; protected final ISourceLocation caller; - public ParseFunction(IValueFactory vf, ISourceLocation caller, Class> parser, String methodName, IBool allowAmbiguity, IBool hasSideEffects, IBool firstAmbiguity, ISet filters) { + public ParseFunction(IValueFactory vf, ISourceLocation caller, Class> parser, String methodName, IBool allowAmbiguity, IBool allowRecovery, IBool hasSideEffects, IBool firstAmbiguity, ISet filters) { this.vf = vf; this.caller = caller; this.parser = parser; this.methodName = methodName; this.filters = filters; this.allowAmbiguity = allowAmbiguity.getValue() || firstAmbiguity.getValue(); + this.allowRecovery = allowRecovery.getValue(); this.hasSideEffects = hasSideEffects.getValue(); this.firstAmbiguity = firstAmbiguity.getValue(); } @@ -454,10 +460,10 @@ else if (parameters[0].getType().isSourceLocation()) { } if (parameters[0].getType().isString()) { - return parse(methodName, filters, (IString) parameters[0], (ISourceLocation) parameters[1], allowAmbiguity, hasSideEffects); + return parse(methodName, filters, (IString) parameters[0], (ISourceLocation) parameters[1], allowAmbiguity, allowRecovery, hasSideEffects); } else if (parameters[0].getType().isSourceLocation()) { - return parse(methodName, filters, (ISourceLocation) parameters[0], (ISourceLocation) parameters[1], allowAmbiguity, hasSideEffects); + return parse(methodName, filters, (ISourceLocation) parameters[0], (ISourceLocation) parameters[1], allowAmbiguity, allowRecovery, hasSideEffects); } } @@ -477,13 +483,13 @@ private IGTD getParser() { } } - protected IValue parse(String methodName, ISet filters, IString input, ISourceLocation origin, boolean allowAmbiguity, boolean hasSideEffects) { + protected IValue parse(String methodName, ISet filters, IString input, ISourceLocation origin, boolean allowAmbiguity, boolean allowRecovery, boolean hasSideEffects) { try { if (origin == null) { origin = URIUtil.rootLocation("unknown"); } - return parseObject(methodName, origin, input.getValue().toCharArray(), allowAmbiguity, hasSideEffects, filters); + return parseObject(methodName, origin, input.getValue().toCharArray(), allowAmbiguity, allowRecovery, hasSideEffects, filters); } catch (ParseError pe) { ISourceLocation errorLoc = pe.getLocation(); @@ -500,7 +506,7 @@ protected IValue parse(String methodName, ISet filters, IString input, ISourceL protected IValue firstAmbiguity(String methodName, IString input) { try { - return parseObject(methodName, URIUtil.invalidLocation(), input.getValue().toCharArray(), false, false, vf.set()); + return parseObject(methodName, URIUtil.invalidLocation(), input.getValue().toCharArray(), false, false, false, vf.set()); } catch (ParseError pe) { ISourceLocation errorLoc = pe.getLocation(); @@ -516,7 +522,7 @@ protected IValue firstAmbiguity(String methodName, IString input) { protected IValue firstAmbiguity(String methodName, ISourceLocation input) { try { - return parseObject(methodName, input, readAll(input), false, false, vf.set()); + return parseObject(methodName, input, readAll(input), false, false, false, vf.set()); } catch (ParseError pe) { ISourceLocation errorLoc = pe.getLocation(); @@ -543,13 +549,13 @@ private IString printSymbol(IConstructor symbol) { return vf.string(SymbolAdapter.toString(symbol, false)); } - protected IValue parse(String methodName, ISet filters, ISourceLocation input, ISourceLocation origin, boolean allowAmbiguity, boolean hasSideEffects) { + protected IValue parse(String methodName, ISet filters, ISourceLocation input, ISourceLocation origin, boolean allowAmbiguity, boolean allowRecovery, boolean hasSideEffects) { if (origin == null) { origin = input; } try { - return parseObject(methodName, input, readAll(input), allowAmbiguity, hasSideEffects, filters); + return parseObject(methodName, input, readAll(input), allowAmbiguity, allowRecovery, hasSideEffects, filters); } catch (ParseError pe) { ISourceLocation errorLoc = pe.getLocation(); @@ -567,10 +573,17 @@ protected IValue parse(String methodName, ISet filters, ISourceLocation input, I } } - private ITree parseObject(String methodName, ISourceLocation location, char[] input, boolean allowAmbiguity, boolean hasSideEffects, ISet filters) { + private ITree parseObject(String methodName, ISourceLocation location, char[] input, boolean allowAmbiguity, boolean allowRecovery, boolean hasSideEffects, ISet filters) { IActionExecutor exec = filters.isEmpty() ? new NoActionExecutor() : new RascalFunctionActionExecutor(filters, !hasSideEffects); - - return (ITree) getParser().parse(methodName, location.getURI(), input, exec, new DefaultNodeFlattener(), new UPTRNodeFactory(allowAmbiguity), (IRecoverer) null); + IGTD parserInstance = getParser(); + IRecoverer recoverer = null; + IDebugListener debugListener = null; + URI uri = location.getURI(); + if (allowRecovery) { + recoverer = new ToTokenRecoverer(uri, parserInstance, new StackNodeIdDispenser(parserInstance)); + //debugListener = new DebugLogger(new PrintWriter(System.out, true)); + } + return (ITree) parserInstance.parse(methodName, uri, input, exec, new DefaultNodeFlattener<>(), new UPTRNodeFactory(allowAmbiguity), recoverer, debugListener); } } @@ -585,8 +598,8 @@ private ITree parseObject(String methodName, ISourceLocation location, char[] in static private class ParametrizedParseFunction extends ParseFunction { private Supplier generator; - public ParametrizedParseFunction(Supplier generator, IValueFactory vf, ISourceLocation caller, Class> parser, IBool allowAmbiguity, IBool hasSideEffects, IBool firstAmbiguity, ISet filters) { - super(vf, caller, parser, null, allowAmbiguity, hasSideEffects, firstAmbiguity, filters); + public ParametrizedParseFunction(Supplier generator, IValueFactory vf, ISourceLocation caller, Class> parser, IBool allowAmbiguity, IBool allowRecovery, IBool hasSideEffects, IBool firstAmbiguity, ISet filters) { + super(vf, caller, parser, null, allowAmbiguity, allowRecovery, hasSideEffects, firstAmbiguity, filters); this.generator = generator; } @@ -621,10 +634,10 @@ else if (parameters[1].getType().isSourceLocation()) { } if (parameters[1].getType().isString()) { - return parse(name, filters, (IString) parameters[1], (ISourceLocation) parameters[2], allowAmbiguity, hasSideEffects); + return parse(name, filters, (IString) parameters[1], (ISourceLocation) parameters[2], allowAmbiguity, allowRecovery, hasSideEffects); } else if (parameters[1].getType().isSourceLocation()) { - return parse(name, filters, (ISourceLocation) parameters[1], (ISourceLocation) parameters[2], allowAmbiguity, hasSideEffects); + return parse(name, filters, (ISourceLocation) parameters[1], (ISourceLocation) parameters[2], allowAmbiguity, allowRecovery, hasSideEffects); } } diff --git a/src/org/rascalmpl/values/RascalValueFactory.java b/src/org/rascalmpl/values/RascalValueFactory.java index 8ec9fc76a0a..3c4a7218b5d 100644 --- a/src/org/rascalmpl/values/RascalValueFactory.java +++ b/src/org/rascalmpl/values/RascalValueFactory.java @@ -152,6 +152,8 @@ public class RascalValueFactory extends AbstractValueFactoryAdapter implements I public static final Type Production_Priority = tf.constructor(uptr, Production, "priority", Symbol, "def", tf.listType(Production), "choices"); public static final Type Production_Composition = tf.constructor(uptr, Production, "composition", Production, "lhs", Production, "rhs"); public static final Type Production_Associativity = tf.constructor(uptr, Production, "associativity", Symbol, "def", Associativity, "assoc", tf.setType(Production), "alternatives"); + public static final Type Production_Error = tf.constructor(uptr, Production, "error", Symbol, "def", Production, "prod", tf.integerType(), "dot"); + public static final Type Production_Skipped = tf.constructor(uptr, Production, "skipped", Symbol, "def"); /* Constructors for Attr */ public static final Type Attr_Assoc = tf.constructor(uptr, Attr, "assoc", Associativity, "assoc"); diff --git a/src/org/rascalmpl/values/parsetrees/ProductionAdapter.java b/src/org/rascalmpl/values/parsetrees/ProductionAdapter.java index 907ae429a9f..9cb14d57994 100644 --- a/src/org/rascalmpl/values/parsetrees/ProductionAdapter.java +++ b/src/org/rascalmpl/values/parsetrees/ProductionAdapter.java @@ -55,8 +55,8 @@ public static IConstructor getDefined(IConstructor tree) { } public static IConstructor setDefined(IConstructor tree, IConstructor sym) { - return (IConstructor) tree.set(0 /*def */, sym); - } + return (IConstructor) tree.set(0 /*def */, sym); + } public static IList getSymbols(IConstructor tree) { if (isDefault(tree)) { @@ -139,6 +139,10 @@ public static boolean isDefault(IConstructor tree) { public static boolean isRegular(IConstructor tree) { return tree.getConstructorType() == RascalValueFactory.Production_Regular; } + + public static boolean isSkipped(IConstructor tree) { + return tree.getConstructorType() == RascalValueFactory.Production_Skipped; + } public static boolean isSeparatedList(IConstructor tree) { IConstructor rhs = getType(tree); diff --git a/test/org/rascalmpl/test/AllSuite.java b/test/org/rascalmpl/test/AllSuite.java index 2de69a1d039..f41adf2bb10 100644 --- a/test/org/rascalmpl/test/AllSuite.java +++ b/test/org/rascalmpl/test/AllSuite.java @@ -16,6 +16,6 @@ import org.rascalmpl.test.infrastructure.RecursiveTestSuite; @RunWith(RecursiveTestSuite.class) -@RecursiveTest({"basic", "concrete", "extend", "functionality", "imports", "library", "parser", "syntax", "demo", "benchmark"}) +@RecursiveTest({"basic", "concrete", "extend", "functionality", "imports", "library", "parser", "recovery", "syntax", "demo", "benchmark"}) public class AllSuite { } diff --git a/test/org/rascalmpl/test/AllSuiteParallel.java b/test/org/rascalmpl/test/AllSuiteParallel.java index 7fcce545df5..7afc9db367a 100644 --- a/test/org/rascalmpl/test/AllSuiteParallel.java +++ b/test/org/rascalmpl/test/AllSuiteParallel.java @@ -7,6 +7,6 @@ @RunWith(RecursiveTestSuite.class) @RecursiveTest({"parallel"}) -@RecursiveJavaOnlyTest({"basic", "concrete", "functionality", "library", "parser", "demo", "benchmark"}) +@RecursiveJavaOnlyTest({"basic", "concrete", "functionality", "library", "parser", "recovery", "demo", "benchmark"}) public class AllSuiteParallel { } diff --git a/test/org/rascalmpl/test/parser/IParserTest.java b/test/org/rascalmpl/test/parser/IParserTest.java index 9cf98df7cb2..ccf5cd8c5bb 100644 --- a/test/org/rascalmpl/test/parser/IParserTest.java +++ b/test/org/rascalmpl/test/parser/IParserTest.java @@ -13,13 +13,33 @@ import java.io.IOException; +import io.usethesource.vallang.IConstructor; import io.usethesource.vallang.IValue; import io.usethesource.vallang.IValueFactory; + +import org.rascalmpl.parser.gtd.stack.AbstractStackNode; import org.rascalmpl.values.ValueFactoryFactory; import org.rascalmpl.values.parsetrees.ITree; public interface IParserTest{ public final static IValueFactory VF = ValueFactoryFactory.getValueFactory(); + + @SafeVarargs + public static AbstractStackNode[] createExpectArray(IConstructor prod, AbstractStackNode... nodes) { + @SuppressWarnings({"unchecked", "cast"}) + AbstractStackNode[] expectArray = (AbstractStackNode[]) new AbstractStackNode[nodes.length]; + + int index = 0; + for (AbstractStackNode node : nodes) { + expectArray[index] = node; + node.setProduction(expectArray); + index++; + } + + expectArray[index-1].setAlternativeProduction(prod); + + return (AbstractStackNode[]) new AbstractStackNode[]{expectArray[0]}; + } ITree executeParser(); diff --git a/test/org/rascalmpl/test/parser/StackNodeTest.java b/test/org/rascalmpl/test/parser/StackNodeTest.java index bfc52a4f5b8..604bbbffe5a 100644 --- a/test/org/rascalmpl/test/parser/StackNodeTest.java +++ b/test/org/rascalmpl/test/parser/StackNodeTest.java @@ -1,9 +1,22 @@ -package org.rascalmpl.test.parser; +/** + * Copyright (c) 2024, NWO-I Centrum Wiskunde & Informatica (CWI) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + package org.rascalmpl.test.parser; import org.junit.Assert; import org.junit.Test; import org.rascalmpl.parser.gtd.stack.EpsilonStackNode; -import org.rascalmpl.parser.gtd.stack.LiteralStackNode; import org.rascalmpl.parser.gtd.stack.filter.ICompletionFilter; import org.rascalmpl.parser.gtd.stack.filter.IEnterFilter; import org.rascalmpl.parser.gtd.stack.filter.follow.AtEndOfLineRequirement; diff --git a/test/org/rascalmpl/test/recovery/RunRascalTestModules.java b/test/org/rascalmpl/test/recovery/RunRascalTestModules.java new file mode 100644 index 00000000000..b3437975cdf --- /dev/null +++ b/test/org/rascalmpl/test/recovery/RunRascalTestModules.java @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2022, NWO-I Centrum Wiskunde & Informatica (CWI) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **/ + + package org.rascalmpl.test.recovery; + +import org.junit.runner.RunWith; +import org.rascalmpl.test.infrastructure.RascalJUnitTestPrefix; +import org.rascalmpl.test.infrastructure.RascalJUnitTestRunner; + +@RunWith(RascalJUnitTestRunner.class) +@RascalJUnitTestPrefix("lang::rascal::tests::recovery") +public class RunRascalTestModules { + +}