package cangjie_tpc::prism4cj.prism
public abstract class GrammarUtils {
/**
* Helper method to find a token inside grammar. Supports lookup in `inside` grammars. For
* example given the path: {@code first-token/then-another/and-more } this method will do:
* <ul>
* <li>Look for `first-token` at root level of supplied grammar</li>
* <li>If it\'s found search for first pattern with `inside` grammar</li>
* <li>If it\'s found search for `then-another` token in this inside grammar</li>
* <li>etc</li>
* </ul>
* Simple path {@code simple-root-level } is also supported
*
* @param grammar {@link prism4cj.prism.Grammar}
* @param path argument to find a {@link prism4cj.prism.Token}
* @return a found {@link prism4cj.prism.Token} or null
*/
public static func findToken(grammar: Grammar, path: String): ?Token {
let parts: Array<String> = path.split("/")
return findToken(grammar, parts, 0)
}
private static func findToken(grammar: Grammar, parts: Array<String>, index: Int64): ?Token {
let part: String = parts[index]
let last: Bool = index == parts.size - 1
for (token in grammar.tokens()) {
if (part == token.name()) {
if (last) {
return token
} else {
let inside: ?Grammar = findFirstInsideGrammar(token)
if (let Some(v) <- inside) {
return findToken(v, parts, index + 1)
} else {
break
}
}
}
}
return None
}
// won't work if there are multiple patterns provided for a token (each with inside grammar)
public static func insertBeforeToken(grammar: Grammar, path: String, tokens: ArrayList<Token>): Unit {
let parts: Array<String> = path.split("/")
insertBeforeToken(grammar, parts, 0, tokens.toArray())
}
private static func insertBeforeToken(grammar: Grammar, parts: Array<String>, index: Int64, tokens: Array<Token>): Unit {
let part: String = parts[index]
let last: Bool = index == parts.size - 1
let grammarTokens: ArrayList<Token> = grammar.tokens()
var token: Token
for (i in 0..grammarTokens.size) {
token = grammarTokens.get(i).getOrThrow()
if (part == token.name()) {
// here we must decide what to do next:
// - it can be out found one
// - or we need to go deeper (c)
if (last) {
// here we go, it's our token
insertTokensAt(i, grammarTokens, tokens)
} else {
// now we must find a grammar that is inside
// token can have multiple patterns
// but as they are not identified somehow (no name or anything)
// we will try to find first pattern with inside grammar
let inside: ?Grammar = findFirstInsideGrammar(token)
if (let Some(v) <- inside) {
insertBeforeToken(v, parts, index + 1, tokens)
}
}
// break after we have found token with specified name (most likely it won't repeat itself)
break
}
}
}
public static func findFirstInsideGrammar(token: Token): ?Grammar {
var grammar: ?Grammar = None
for (i in 0..token.patterns().size) {
if (let Some(v) <- token.patterns()[i].inside()) {
grammar = v
break
}
}
return grammar
}
private static func insertTokensAt(start: Int64, grammarTokens: ArrayList<Token>, tokens: Array<Token>) {
for (i in 0..tokens.size) {
grammarTokens.add(tokens[i], at: start + i)
}
}
public static func clone(grammar: Grammar): Grammar {
return CLONER.clone(grammar)
}
public static func clone(token: Token): Token {
return CLONER.clone(token)
}
public static func clone(pattern: Pattern): Pattern {
return CLONER.clone(pattern)
}
public static func extendGrammar(grammar: Grammar, name: String, tokens: ?ArrayList<Token>): Grammar {
// we clone the whole grammar, but override top-most tokens that are passed here
let size: Int64 = if (let Some(v) <- tokens) {
v.size
} else {
0
}
if (size == 0) {
return GrammarImpl(name, clone(grammar).tokens())
}
let overrides: HashMap<String, Token> = HashMap<String, Token>(size)
if (let Some(v) <- tokens) {
for (i in 0..v.size) {
overrides.add(v[i].name(), v[i])
}
}
let origins: ArrayList<Token> = grammar.tokens()
var out: ArrayList<Token> = ArrayList<Token>(origins.size)
var override: ?Token
for (i in 0..origins.size) {
override = overrides.get(origins[i].name())
if (let Some(v) <- override) {
out.add(v)
} else {
out.add(clone(origins[i]))
}
}
return GrammarImpl(name, out)
}
public static func extendGrammar(grammar: Grammar, name: String, filter: TokenFilter, tokens: ?ArrayList<Token>): Grammar {
let size: Int64 = if (let Some(v) <- tokens) {
v.size
} else {
0
}
var overrides: HashMap<String, Token>
if (size == 0) {
overrides = HashMap<String, Token>()
} else {
overrides = HashMap<String, Token>(size)
if (let Some(v) <- tokens) {
for (i in 0..v.size) {
overrides.add(v[i].name(), v[i])
}
}
}
let origins: ArrayList<Token> = grammar.tokens()
var out: ArrayList<Token> = ArrayList<Token>(origins.size)
var override: ?Token
for (i in 0..origins.size) {
// filter out undesired tokens
if (!filter.test(origins[i])) {
continue
}
override = overrides.get(origins[i].name())
if (let Some(v) <- override) {
out.add(v)
} else {
out.add(clone(origins[i]))
}
}
return GrammarImpl(name, out)
}
public static func require(prism: Prism, name: String): Grammar {
let requireGrammar: ?Grammar = prism.grammar(name)
if (requireGrammar.isNone()) {
throw IllegalStateException("Unexpected state, requested language is not found: " + name)
}
return requireGrammar.getOrThrow()
}
private GrammarUtils() {
}
private static let CLONER: Cloner = Cloner.create()
}
/**
* Used when extending an existing grammar to filter out tokens that should not be cloned.
*
* @see #extend(Prism4j.Grammar, String, TokenFilter, Prism4j.Token...)
*/
public interface TokenFilter {
/**
* @param token {@link prism4cj.prism.Token} to validate
* @return a boolean indicating if supplied token should be included (passes the test)
*/
func test(token: Token): Bool
}