Skip to content

Commit 539c23d

Browse files
author
Steve Ramage
committed
feat: Add support for Token Based validators (WIP #2)
1 parent e35885b commit 539c23d

File tree

14 files changed

+422
-416
lines changed

14 files changed

+422
-416
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar
2+
3+
import kotlin.math.max
4+
5+
/**
6+
* This is a sequence of tokens that must match any of them.
7+
*/
8+
class AlternativeCombinator(vararg val tokens: Combinator) : Combinator {
9+
10+
fun match(value: String, offset: Int, f: (Combinator, String, Int) -> MatchResult): MatchResult {
11+
12+
13+
var longestTokenMatch = emptyList<String>()
14+
var longestTerminalMatch = emptyList<TerminalCombinator>()
15+
var maxLength = 0
16+
17+
for (token in tokens) {
18+
val match = f(token, value, offset)
19+
if (match.matchResult != -1) {
20+
return match
21+
}
22+
23+
24+
25+
if (match.tokens.size > longestTerminalMatch.size) {
26+
longestTerminalMatch = match.terminals
27+
longestTokenMatch = match.tokens
28+
maxLength = max(maxLength, match.longestMatch)
29+
}
30+
}
31+
32+
return MatchResult(longestTokenMatch, -1, longestTerminalMatch, maxLength)
33+
}
34+
35+
override fun SyntacticMatch(value: String, offset: Int): MatchResult {
36+
return match(value, offset, Combinator::SyntacticMatch)
37+
}
38+
39+
override fun SemanticMatch(value: String, offset: Int): MatchResult {
40+
return match(value, offset, Combinator::SemanticMatch)
41+
}
42+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar
2+
3+
interface Combinator {
4+
5+
// TODO add longest prefix match
6+
// Note about coloring on this.
7+
8+
/**
9+
* WARNING: At the current time this combinator implementation doesn't necessarily guarantee a match.
10+
*
11+
* Seq(ZeroOrMore(Literal("fizz")), Literal("fizz"))
12+
*
13+
* If you try and match "fizz", the ZeroOrMore would greedily consume the fizz, and the second wouldn't match.
14+
*
15+
* I'm unclear if this will actually be a problem, and whether it's worth fixing.
16+
*/
17+
18+
/**
19+
* This checks the value string, starting at offset for a syntactic match.
20+
*
21+
* In a nutshell a syntactic match might accept things that we should color and try and analyze
22+
* but might be incorrect.
23+
*
24+
* For example if you something accepts a positive number, a syntactic regex should match any number even negative or floats
25+
*
26+
* The return value is -1 for no match, or a new offset if this token matched something.
27+
*/
28+
fun SyntacticMatch(value : String, offset: Int): MatchResult
29+
30+
/**
31+
* This checks the value string, starting at offset for a semantic match.
32+
*
33+
* In a nutshell a semantic match means we understood and it valid as far as the grammar is concerned.
34+
*
35+
* The return value is -1 for no match, or a new offset if this token matched something.
36+
*/
37+
fun SemanticMatch(value : String, offset: Int): MatchResult
38+
39+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar
2+
3+
val BYTES = RegexTerminal("[0-9]+[a-zA-Z]*\\s*", "[0-9]+[KMGT]?\\s*")
4+
val DEVICE = RegexTerminal("\\S+\\s*", "/[^\\u0000. ]+\\s*")
5+
val IOPS = RegexTerminal("[0-9]+[a-zA-Z]*\\s*", "[0-9]+[KMGT]?\\s*")
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar
2+
3+
class EOF : Combinator {
4+
override fun SyntacticMatch(value: String, offset: Int): MatchResult {
5+
return if (offset == value.length) {
6+
MatchResult(emptyList(), offset, emptyList(), value.length)
7+
} else {
8+
NoMatch
9+
}
10+
}
11+
12+
override fun SemanticMatch(value: String, offset: Int): MatchResult {
13+
return if (offset == value.length) {
14+
MatchResult(emptyList(), offset, emptyList(), value.length)
15+
} else {
16+
NoMatch
17+
}
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar
2+
3+
import kotlin.math.max
4+
5+
class FlexibleLiteralChoiceTerminal(vararg val choices: String) : TerminalCombinator {
6+
7+
init {
8+
choices.sortBy { -it.length }
9+
}
10+
11+
val syntaticMatch: Regex
12+
13+
init {
14+
15+
var dash = false
16+
var specialChars = ""
17+
var lowerCase = false
18+
var upperCase = false
19+
var numbers = false
20+
var maxLength = 0
21+
22+
for (choice in choices) {
23+
maxLength = max(maxLength, choice.length)
24+
for (char in choice) {
25+
if (char in 'a'..'z') {
26+
lowerCase = true
27+
continue
28+
}
29+
30+
if (char in 'A'..'Z') {
31+
upperCase = true
32+
continue
33+
}
34+
35+
if (char in '0'..'9') {
36+
numbers = true
37+
continue
38+
}
39+
40+
if (char == '-') {
41+
dash = true
42+
continue
43+
}
44+
45+
specialChars += char
46+
}
47+
}
48+
49+
var regexClass = ""
50+
51+
if (lowerCase) {
52+
regexClass += "a-z"
53+
}
54+
55+
if (upperCase) {
56+
regexClass += "A-Z"
57+
}
58+
59+
if (numbers) {
60+
regexClass += "0-9"
61+
}
62+
63+
regexClass += specialChars
64+
65+
if (dash) {
66+
regexClass += "-"
67+
}
68+
69+
syntaticMatch = ("[" + regexClass + "]{1,$maxLength}").toRegex()
70+
}
71+
72+
73+
override fun SyntacticMatch(value: String, offset: Int): MatchResult {
74+
val matchResult = syntaticMatch.matchAt(value, offset) ?: return NoMatch
75+
76+
return MatchResult(listOf(matchResult.value), offset + matchResult.value.length, listOf(this), offset + matchResult.value.length)
77+
}
78+
79+
override fun SemanticMatch(value: String, offset: Int): MatchResult {
80+
for (choice in choices) {
81+
if (value.substring(offset).startsWith(choice)) {
82+
return MatchResult(listOf(choice), offset + choice.length, listOf(this), offset + choice.length)
83+
}
84+
}
85+
return NoMatch.copy(longestMatch = offset)
86+
}
87+
88+
89+
}

0 commit comments

Comments
 (0)