|
| 1 | +/** |
| 2 | + * Module for parsing access paths from CSV models, both the identifying access path used |
| 3 | + * by dynamic languages, and the input/output specifications for summary steps. |
| 4 | + * |
| 5 | + * This file is used by the shared data flow library and by the JavaScript libraries |
| 6 | + * (which does not use the shared data flow libraries). |
| 7 | + */ |
| 8 | + |
| 9 | +/** Companion module to the `AccessPath` class. */ |
| 10 | +module AccessPath { |
| 11 | + /** A string that should be parsed as an access path. */ |
| 12 | + abstract class Range extends string { |
| 13 | + bindingset[this] |
| 14 | + Range() { any() } |
| 15 | + } |
| 16 | +} |
| 17 | + |
| 18 | +/** Gets the `n`th token on the access path as a string. */ |
| 19 | +private string getRawToken(AccessPath path, int n) { |
| 20 | + // Avoid splitting by '.' since tokens may contain dots, e.g. `Field[foo.Bar.x]`. |
| 21 | + // Instead use regexpFind to match valid tokens, and supplement with a final length |
| 22 | + // check (in `AccessPath.hasSyntaxError`) to ensure all characters were included in a token. |
| 23 | + result = path.regexpFind("\\w+(?:\\[[^\\]]*\\])?(?=\\.|$)", n, _) |
| 24 | +} |
| 25 | + |
| 26 | +/** |
| 27 | + * A string that occurs as an access path (either identifying or input/output spec) |
| 28 | + * which might be relevant for this database. |
| 29 | + */ |
| 30 | +class AccessPath extends string instanceof AccessPath::Range { |
| 31 | + /** Holds if this string is not a syntactically valid access path. */ |
| 32 | + predicate hasSyntaxError() { |
| 33 | + // If the lengths match, all characters must haven been included in a token |
| 34 | + // or seen by the `.` lookahead pattern. |
| 35 | + this != "" and |
| 36 | + not this.length() = sum(int n | | getRawToken(this, n).length() + 1) - 1 |
| 37 | + } |
| 38 | + |
| 39 | + /** Gets the `n`th token on the access path (if there are no syntax errors). */ |
| 40 | + AccessPathToken getToken(int n) { |
| 41 | + result = getRawToken(this, n) and |
| 42 | + not this.hasSyntaxError() |
| 43 | + } |
| 44 | + |
| 45 | + /** Gets the number of tokens on the path (if there are no syntax errors). */ |
| 46 | + int getNumToken() { |
| 47 | + result = count(int n | exists(getRawToken(this, n))) and |
| 48 | + not this.hasSyntaxError() |
| 49 | + } |
| 50 | +} |
| 51 | + |
| 52 | +/** |
| 53 | + * An access part token such as `Argument[1]` or `ReturnValue`, appearing in one or more access paths. |
| 54 | + */ |
| 55 | +class AccessPathToken extends string { |
| 56 | + AccessPathToken() { this = getRawToken(any(AccessPath path), _) } |
| 57 | + |
| 58 | + private string getPart(int part) { |
| 59 | + result = this.regexpCapture("([^\\[]+)(?:\\[([^\\]]*)\\])?", part) |
| 60 | + } |
| 61 | + |
| 62 | + /** Gets the name of the token, such as `Member` from `Member[x]` */ |
| 63 | + string getName() { result = this.getPart(1) } |
| 64 | + |
| 65 | + /** |
| 66 | + * Gets the argument list, such as `1,2` from `Member[1,2]`, |
| 67 | + * or has no result if there are no arguments. |
| 68 | + */ |
| 69 | + string getArgumentList() { result = this.getPart(2) } |
| 70 | + |
| 71 | + /** Gets the `n`th argument to this token, such as `x` or `y` from `Member[x,y]`. */ |
| 72 | + string getArgument(int n) { result = this.getArgumentList().splitAt(",", n).trim() } |
| 73 | + |
| 74 | + /** Gets an argument to this token, such as `x` or `y` from `Member[x,y]`. */ |
| 75 | + string getAnArgument() { result = this.getArgument(_) } |
| 76 | + |
| 77 | + /** Gets the number of arguments to this token, such as 2 for `Member[x,y]` or zero for `ReturnValue`. */ |
| 78 | + int getNumArgument() { result = count(int n | exists(this.getArgument(n))) } |
| 79 | +} |
0 commit comments