Skip to content
Snippets Groups Projects
Commit 6fceb27b authored by Tim Kluge's avatar Tim Kluge
Browse files

Initial commit

parents
No related branches found
No related tags found
No related merge requests found
Pipeline #9725 passed
Showing
with 811 additions and 0 deletions
.gradle
.idea
build
\ No newline at end of file
image: java:8-jdk
stages:
- build
- test
- deploy
before_script:
- export GRADLE_USER_HOME=`pwd`/.gradle
- chmod u+x ./gradlew
cache:
paths:
- .gradle/wrapper
- .gradle/caches
build:
stage: build
script:
- ./gradlew assemble
only:
- master
test:
stage: test
script:
- ./gradlew check
deploy:
script:
- ./gradlew publish
only:
- master
\ No newline at end of file
LICENSE 0 → 100644
MIT License
Copyright (c) 2020-2021 Tim Kluge
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
jackrat
============================
This library allows to construct backtracking top down packrat parsers in Kotlin using parser combination. Packrat parsing enables the parsing of PEG Grammars in linear time. Parsers are combinated using the following basic parsers:
- `AtomParser`: Matches only a specified UTF8 string
- `RegexParser`: Matches a regular expression
- `AndParser`: Matches a given list of parsers sequentially
- `OrParser`: Matches if any of a given list of parsers matches
- `KleeneParser`: Matches a parser 0 to `n` times, optionally separated by another parser
- `ManyParser`: Matches a parser 1 to `n` times, optionally separated by another parser
- `MaybeParser`: Matches a parser 0 or 1 times
- `EmptyParser`: Does not read any input and matches in every case
- `EndParser`: Matches only if the scanner has reached the end of the input string
By default, `Atom` and `Regex` parsers skip (but do not match on) leading whitespace. This can be configured per parser.
If a parser matches, it returns a syntax tree `Node`. Every node points to the parser that produced it, the matched text, and a list of child nodes. Callbacks are not provided atm, so a full syntax tree traversal is needed to process the parse results.
To construct recursive parsers, create parser combinators with `nil` as the sub parser. After creating the sub parser that itself uses the parent parser, use the `Set` function on the parent parser to update its children. The [JSON parser](./json_test.go) provides an example for this.
This library is currently used in production, but some rarely used features may be broken. Additional documentation is ToDo.
Example
-----------
A full example in form of a working json parser is provided in [JsonTests.kt](https://git-st.inf.tu-dresden.de/timklge/jackrat/-/blob/master/src/commonTest/kotlin/JsonTests.kt).
```kotlin
val input = "Hello world"
val scanner = Scanner(input)
val helloParser = AtomParser("Hello", false)
val worldParser = AtomParser("world", false)
val helloAndWorldParser = AndParser(helloParser, worldParser)
val node = helloAndWorldParser.parse(scanner)
// node is the syntax tree root node
// node.children is a list containing a node for both the Hello and World parser
```
Use case
-----------
Using this library, you can dynamically define and parse PEG grammars at runtime. Parsing time is proportional to the input length and grammar complexity. Note that if you do not need to build grammars at runtime, consider using a standard LR parser generator.
Left recursion
-----------
Left recursion is supported via the method proposed by [Warth et al. (2008)](https://doi.org/10.1145/1328408.1328424).
License
------------
Licensed with MIT License. See [LICENSE](../../../LICENSE).
\ No newline at end of file
plugins {
id 'org.jetbrains.kotlin.multiplatform' version '1.4.32'
id 'java'
id 'maven-publish'
}
group 'de.timklge'
version '1.0'
repositories {
mavenCentral()
}
kotlin {
jvm()
/* js {
nodejs()
} */
sourceSets {
commonMain {
dependencies {
implementation kotlin('stdlib-common')
}
}
commonTest {
dependencies {
implementation kotlin('test-common')
implementation kotlin('test-annotations-common')
}
}
jvmTest {
dependencies {
implementation(kotlin("test-junit"))
implementation("junit:junit:4.12")
}
}
/* jsTest {
dependencies {
implementation(kotlin("test-js"))
}
} */
}
}
publishing {
publications {
library(MavenPublication) {
from components.java
}
}
repositories {
maven {
url "https://git-st.inf.tu-dresden.de/api/v4/projects/963/packages/maven"
name "GitLab"
credentials(HttpHeaderCredentials) {
name = 'Job-Token'
value = System.getenv("CI_JOB_TOKEN")
}
authentication {
header(HttpHeaderAuthentication)
}
}
}
}
\ No newline at end of file
kotlin.code.style=official
\ No newline at end of file
File added
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
gradlew 0 → 100644
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
rootProject.name = 'jackrat'
package de.timklge.jackrat
import jackrat.de.timklge.jackrat.Scanner
class AndParser(children: List<Parser>, nodeFunc: NodeTransform = ::emptyNodeTransform) : ParserWithChildren(children, nodeFunc) {
override val typeName: String = "AndParser"
override fun Match(s: Scanner): Node? {
val startPosition = s.position
val nodes = children.map<Parser, Node> {
val node = s.applyRule(it)
if(node == null){
s.position = startPosition
return null
}
node
}
val builder = StringBuilder()
nodes.forEach { builder.append(it.matched) }
val matched = builder.toString()
return Node(matched, this, nodes)
}
}
class OrParser(children: List<Parser>, nodeFunc: NodeTransform = ::emptyNodeTransform): ParserWithChildren(children, nodeFunc) {
override val typeName: String = "OrParser"
override fun Match(s: Scanner): Node? {
val startPosition = s.position
children.forEach {
val node = s.applyRule(it)
if(node != null){
return Node(node.matched, this, listOf(node))
}
s.position = startPosition
}
return null
}
}
\ No newline at end of file
package de.timklge.jackrat
class AtomParser(atom: String, caseInsensitive: Boolean = false, skipWs: Boolean = true, nodeFunc: NodeTransform = ::emptyNodeTransform)
: RegexParser(Regex.escape(atom), caseInsensitive, skipWs, nodeFunc){
override val typeName: String = "AtomParser"
}
\ No newline at end of file
package de.timklge.jackrat
import jackrat.de.timklge.jackrat.Scanner
class EmptyParser(transform: NodeTransform = ::emptyNodeTransform) : Parser(transform) {
override val typeName: String = "EmptyParser"
override fun Match(s: Scanner): Node? {
return Node("", this, listOf())
}
}
class EndParser(val skipWhitespace: Boolean = true) : Parser() {
override val typeName: String = "EndParser"
override fun Match(s: Scanner): Node? {
val startPosition = s.position
if(skipWhitespace){
s.skipWhitespace()
}
if(s.position == s.input.length){
return Node("", this, listOf())
}
s.position = startPosition
return null
}
}
\ No newline at end of file
package de.timklge.jackrat
import jackrat.de.timklge.jackrat.Scanner
class KleeneParser(var subParser: Parser, var sepParser: Parser? = null, nodeTransform: NodeTransform = ::emptyNodeTransform) : ParserWithChildren(emptyList(), nodeTransform) {
override val typeName: String = "KleeneParser"
init {
children = if(sepParser != null) {
listOf(subParser, sepParser!!)
} else {
listOf(subParser)
}
}
override fun Match(s: Scanner): Node? {
val nodes = mutableListOf<Node>()
var i = 0
var lastValidPosition = s.position
while(true){
var matchedsep = false
var sepnode: Node? = null
if(i > 0 && sepParser != null){
sepnode = s.applyRule(sepParser!!) ?: break
matchedsep = true
}
i++
val node = s.applyRule(subParser) ?: break
if(matchedsep) nodes.add(sepnode!!)
nodes.add(node)
lastValidPosition = s.position
}
s.position = lastValidPosition
val builder = StringBuilder()
nodes.forEach { builder.append(it.matched) }
val matched = builder.toString()
return Node(matched, this, nodes)
}
}
\ No newline at end of file
package de.timklge.jackrat
import jackrat.de.timklge.jackrat.Scanner
class ManyParser(var subParser: Parser?, var sepParser: Parser? = null, nodeTransform: NodeTransform = ::emptyNodeTransform) : ParserWithChildren(emptyList(), nodeTransform) {
override val typeName: String = "ManyParser"
init {
children = if(subParser != null && sepParser != null) {
listOf(subParser!!, sepParser!!)
} else if(subParser != null){
listOf(subParser!!)
} else {
emptyList()
}
}
override fun Match(s: Scanner): Node? {
var i = 0
var lastValidPos = s.position
var nodes = mutableListOf<Node>()
while(true){
var matchedsep = false
var sepnode: Node? = null
if(i > 0 && sepParser != null){
sepnode = s.applyRule(sepParser!!)
if(sepnode == null) break
matchedsep = true
}
i++
val node = s.applyRule(subParser!!) ?: break
if(matchedsep) nodes.add(sepnode!!)
nodes.add(node)
lastValidPos = s.position
}
s.position = lastValidPos
if(nodes.count() >= 1){
val builder = StringBuilder()
nodes.forEach { builder.append(it.matched) }
val matched = builder.toString()
return Node(matched, this, nodes)
}
return null
}
}
\ No newline at end of file
package de.timklge.jackrat
import jackrat.de.timklge.jackrat.Scanner
class MaybeParser(var subParser: Parser, nodeTransform: NodeTransform = ::emptyNodeTransform) : ParserWithChildren(listOf(subParser), nodeTransform) {
override val typeName: String = "MaybeParser"
override fun Match(s: Scanner): Node? {
val startPosition = s.position
val node = s.applyRule(subParser)
if(node == null){
s.position = startPosition
return Node("", this, listOf())
}
return Node(node.matched, this, listOf(node))
}
}
\ No newline at end of file
package de.timklge.jackrat
typealias NodeTransform = (node: Node) -> Node
fun emptyNodeTransform(node: Node) = node
open class Node(val matched: String, val parser: Parser, val children: List<Node>) {
var parent: Node? = null
fun findParser(p: Parser, skipParser: Parser? = null): Node? {
return if(parser == p) this else findChildParser(p, skipParser)
}
fun findChildParser(p: Parser, skipParser: Parser? = null): Node? {
children.forEach { child ->
if(child.parser != skipParser) {
val sub = child.findParser(p)
if (sub != null) return sub
}
}
return null
}
fun findParsers(vararg ps: Parser): List<Node> {
return if(ps.contains(parser)) listOf(this) else findChildParsers(*ps)
}
fun findChildParsers(vararg ps: Parser): List<Node> {
val result = mutableListOf<Node>()
children.forEach { child ->
val subs = child.findParsers(*ps)
result.addAll(subs)
}
return result
}
fun containsParser(p: Parser): Boolean = findParser(p) != null
fun findParserType(typeName: String): Node? {
return if(parser.typeName == typeName) this else findChildParserType(typeName)
}
fun findChildParserType(typeName: String): Node? {
children.forEach { child ->
val sub = child.findParserType(typeName)
if(sub != null) return sub
}
return null
}
internal fun mapTransforms(): Node {
val node = Node(matched, parser, children.map { n ->
n.parent = this
val node = n.parser.nodeTransform(n)
node.parent = this
node
})
return parser.nodeTransform(node)
}
}
\ No newline at end of file
package de.timklge.jackrat
import jackrat.de.timklge.jackrat.Scanner
data class ParserError(val parser: Parser, val line: Int, val column: Int, val position: Int, val failedParsers: List<Parser>?, val input: String) :
Error() {
override fun toString(): String {
val startPos = if(position - 1 >= 0) position - 1 else 0
val endPos = if(position + 10 < input.length) input.length - 1 else {
if(input.length-1 >= 0) input.length - 1 else 0
}
return "Parser failed at line $line, column $column (position $position of input string) near ${input.slice(startPos..endPos)}"
}
}
abstract class Parser(val nodeTransform: NodeTransform = ::emptyNodeTransform) {
abstract val typeName: String
abstract fun Match(s: Scanner): Node?
fun parsePartial(originalScanner: Scanner): Node? {
return originalScanner.applyRule(this)?.mapTransforms()
}
fun parse(originalScanner: Scanner): Node {
val node = originalScanner.applyRule(this)?.mapTransforms()
if(node != null){
if(originalScanner.position < originalScanner.input.length){
val consumed = originalScanner.input.substring(originalScanner.position)
val line = consumed.count { it == '\n' } + 1
val column = originalScanner.position - consumed.lastIndexOf('\n') + 1
throw ParserError(this, line, column, originalScanner.position, null, originalScanner.input)
}
return node
}
var maxPos = 0
val failedParsers: MutableList<Parser> = mutableListOf()
for(index in originalScanner.input.length downTo 0){
if(!originalScanner.memoization.containsKey(index) || originalScanner.memoization[index]!!.isEmpty()) continue
maxPos = index
failedParsers += originalScanner.memoization[index]!!.keys
if(failedParsers.isNotEmpty()) break
}
if(maxPos >= originalScanner.input.length) maxPos = originalScanner.input.length - 1
val consumed = originalScanner.input.substring(0..maxPos)
val line = consumed.count { it == '\n'} + 1
val lastBreak = if(consumed.lastIndexOf('\n') >= 0) consumed.lastIndexOf('\n') else 0
val column = maxPos - lastBreak + 1
throw ParserError(this, line, column, maxPos, failedParsers, originalScanner.input)
}
}
abstract class ParserWithChildren(var children: List<Parser>, nodeFunc: NodeTransform = ::emptyNodeTransform) : Parser(nodeFunc){
}
package de.timklge.jackrat
import jackrat.de.timklge.jackrat.Scanner
open class RegexParser(rs: String, caseInsensitive: Boolean = false, val skipWhitespace: Boolean = true, nodeTransform: NodeTransform = ::emptyNodeTransform) : Parser(nodeTransform) {
override val typeName: String = "RegexParser"
val regex = if(caseInsensitive) Regex("^$rs", RegexOption.IGNORE_CASE) else Regex("^$rs")
override fun Match(s: Scanner): Node? {
val startPosition = s.position
if (skipWhitespace) {
s.skipWhitespace()
if (!s.isAtBreak()) {
s.position = startPosition
return null
}
}
val matched = s.match(regex)
if (matched == null) {
s.position = startPosition
return null
}
if (skipWhitespace) {
if (!s.isAtBreak()) {
s.position = startPosition
return null
}
}
return Node(matched, this, listOf())
}
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment