2021-06-13 12:59:57 +00:00
|
|
|
package prog8tests
|
|
|
|
|
|
|
|
import org.antlr.v4.runtime.*
|
2021-06-14 19:58:03 +00:00
|
|
|
import org.antlr.v4.runtime.misc.ParseCancellationException
|
2021-06-13 12:59:57 +00:00
|
|
|
import org.junit.jupiter.api.Test
|
2021-06-18 19:55:03 +00:00
|
|
|
import prog8.ast.IBuiltinFunctions
|
|
|
|
import prog8.ast.IMemSizer
|
2021-06-13 12:59:57 +00:00
|
|
|
import prog8.ast.IStringEncoding
|
2021-06-18 19:55:03 +00:00
|
|
|
import prog8.ast.Program
|
2021-06-13 12:59:57 +00:00
|
|
|
import prog8.ast.antlr.toAst
|
2021-06-18 19:55:03 +00:00
|
|
|
import prog8.ast.base.DataType
|
|
|
|
import prog8.ast.base.Position
|
|
|
|
import prog8.ast.expressions.Expression
|
|
|
|
import prog8.ast.expressions.InferredTypes
|
|
|
|
import prog8.ast.expressions.NumericLiteralValue
|
2021-06-13 12:59:57 +00:00
|
|
|
import prog8.ast.statements.Block
|
2021-06-18 19:55:03 +00:00
|
|
|
import prog8.parser.*
|
2021-06-13 12:59:57 +00:00
|
|
|
import java.nio.file.Path
|
|
|
|
import kotlin.test.*
|
|
|
|
|
|
|
|
class TestAntlrParser {
|
|
|
|
|
|
|
|
class MyErrorListener: ConsoleErrorListener() {
|
|
|
|
override fun syntaxError(recognizer: Recognizer<*, *>?, offendingSymbol: Any?, line: Int, charPositionInLine: Int, msg: String, e: RecognitionException?) {
|
2021-06-14 19:58:03 +00:00
|
|
|
throw ParsingFailedError("line $line:$charPositionInLine $msg")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class MyErrorStrategy: BailErrorStrategy() {
|
|
|
|
override fun recover(recognizer: Parser?, e: RecognitionException?) {
|
|
|
|
try {
|
2021-06-19 10:35:04 +00:00
|
|
|
// let it
|
|
|
|
super.recover(recognizer, e) // fills in exception e in all the contexts
|
|
|
|
// ...then throws ParseCancellationException, which is
|
|
|
|
// *deliberately* not a RecognitionException. However, we don't try any
|
|
|
|
// error recovery, therefore report an error in this case, too.
|
2021-06-14 19:58:03 +00:00
|
|
|
} catch (pce: ParseCancellationException) {
|
|
|
|
reportError(recognizer, e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun recoverInline(recognizer: Parser?): Token {
|
|
|
|
throw InputMismatchException(recognizer)
|
2021-06-13 12:59:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-19 10:35:04 +00:00
|
|
|
private fun parseModule(srcText: String): Prog8ANTLRParser.ModuleContext {
|
2021-06-14 19:58:03 +00:00
|
|
|
return parseModule(CharStreams.fromString(srcText))
|
|
|
|
}
|
|
|
|
|
2021-06-19 10:35:04 +00:00
|
|
|
private fun parseModule(srcFile: Path): Prog8ANTLRParser.ModuleContext {
|
2021-06-14 19:58:03 +00:00
|
|
|
return parseModule(CharStreams.fromPath(srcFile))
|
|
|
|
}
|
|
|
|
|
2021-06-19 10:35:04 +00:00
|
|
|
private fun parseModule(srcStream: CharStream): Prog8ANTLRParser.ModuleContext {
|
2021-06-14 19:58:03 +00:00
|
|
|
val errorListener = MyErrorListener()
|
2021-06-19 10:35:04 +00:00
|
|
|
val lexer = Prog8ANTLRLexer(srcStream)
|
2021-06-14 19:58:03 +00:00
|
|
|
lexer.removeErrorListeners()
|
|
|
|
lexer.addErrorListener(errorListener)
|
2021-06-13 18:08:50 +00:00
|
|
|
val tokens = CommonTokenStream(lexer)
|
2021-06-19 10:35:04 +00:00
|
|
|
val parser = Prog8ANTLRParser(tokens)
|
2021-06-14 19:58:03 +00:00
|
|
|
parser.errorHandler = MyErrorStrategy()
|
|
|
|
parser.removeErrorListeners()
|
|
|
|
parser.addErrorListener(errorListener)
|
2021-06-13 18:08:50 +00:00
|
|
|
return parser.module()
|
|
|
|
}
|
|
|
|
|
2021-06-18 19:55:03 +00:00
|
|
|
object DummyEncoding: IStringEncoding {
|
2021-06-13 12:59:57 +00:00
|
|
|
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
|
|
|
|
TODO("Not yet implemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String {
|
|
|
|
TODO("Not yet implemented")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-18 19:55:03 +00:00
|
|
|
object DummyFunctions: IBuiltinFunctions {
|
|
|
|
override val names: Set<String> = emptySet()
|
|
|
|
override val purefunctionNames: Set<String> = emptySet()
|
|
|
|
override fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue? = null
|
|
|
|
override fun returnType(name: String, args: MutableList<Expression>) = InferredTypes.InferredType.unknown()
|
|
|
|
}
|
|
|
|
|
|
|
|
object DummyMemsizer: IMemSizer {
|
|
|
|
override fun memorySize(dt: DataType): Int = 0
|
|
|
|
}
|
|
|
|
|
2021-06-13 12:59:57 +00:00
|
|
|
@Test
|
2021-06-13 18:28:01 +00:00
|
|
|
fun testModuleSourceNeedNotEndWithNewline() {
|
|
|
|
val nl = "\n" // say, Unix-style (different flavours tested elsewhere)
|
|
|
|
val srcText = "foo {" + nl + "}" // source ends with '}' (= NO newline, issue #40)
|
2021-06-13 18:08:50 +00:00
|
|
|
|
2021-06-19 10:35:04 +00:00
|
|
|
// before the fix, Prog8ANTLRParser would have reported (thrown) "missing <EOL> at '<EOF>'"
|
2021-06-13 18:08:50 +00:00
|
|
|
val parseTree = parseModule(srcText)
|
|
|
|
assertEquals(parseTree.block().size, 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
2021-06-13 18:28:01 +00:00
|
|
|
fun testModuleSourceMayEndWithNewline() {
|
|
|
|
val nl = "\n" // say, Unix-style (different flavours tested elsewhere)
|
|
|
|
val srcText = "foo {" + nl + "}" + nl // source does end with a newline (issue #40)
|
2021-06-13 18:08:50 +00:00
|
|
|
val parseTree = parseModule(srcText)
|
|
|
|
assertEquals(parseTree.block().size, 1)
|
2021-06-13 12:59:57 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 18:47:14 +00:00
|
|
|
@Test
|
|
|
|
fun testAllBlocksButLastMustEndWithNewline() {
|
|
|
|
val nl = "\n" // say, Unix-style (different flavours tested elsewhere)
|
|
|
|
|
|
|
|
// BAD: 2nd block `bar` does NOT start on new line; however, there's is a nl at the very end
|
|
|
|
val srcBad = "foo {" + nl + "}" + " bar {" + nl + "}" + nl
|
|
|
|
|
|
|
|
// GOOD: 2nd block `bar` does start on a new line; however, a nl at the very end ain't needed
|
|
|
|
val srcGood = "foo {" + nl + "}" + nl + "bar {" + nl + "}"
|
|
|
|
|
|
|
|
assertFailsWith<ParsingFailedError> { parseModule(srcBad) }
|
|
|
|
val parseTree = parseModule(srcGood)
|
|
|
|
assertEquals(parseTree.block().size, 2)
|
|
|
|
}
|
2021-06-13 20:49:54 +00:00
|
|
|
|
|
|
|
@Test
|
|
|
|
fun testWindowsAndMacNewlinesAreAlsoFine() {
|
|
|
|
val nlWin = "\r\n"
|
|
|
|
val nlUnix = "\n"
|
|
|
|
val nlMac = "\r"
|
|
|
|
|
2021-06-14 19:58:03 +00:00
|
|
|
//parseModule(Paths.get("test", "fixtures", "mac_newlines.p8").toAbsolutePath())
|
|
|
|
|
2021-06-13 20:49:54 +00:00
|
|
|
// a good mix of all kinds of newlines:
|
|
|
|
val srcText =
|
|
|
|
"foo {" +
|
2021-06-14 19:58:03 +00:00
|
|
|
nlMac +
|
2021-06-13 20:49:54 +00:00
|
|
|
nlWin +
|
|
|
|
"}" +
|
2021-06-14 19:58:03 +00:00
|
|
|
nlMac + // <-- do test a single \r (!) where an EOL is expected
|
2021-06-13 20:49:54 +00:00
|
|
|
"bar {" +
|
2021-06-14 19:58:03 +00:00
|
|
|
nlUnix +
|
|
|
|
"}" +
|
|
|
|
nlUnix + nlMac // both should be "eaten up" by just one EOL token
|
|
|
|
"combi {" +
|
|
|
|
nlMac + nlWin + nlUnix // all three should be "eaten up" by just one EOL token
|
|
|
|
"}" +
|
|
|
|
nlUnix // end with newline (see testModuleSourceNeedNotEndWithNewline)
|
2021-06-13 20:49:54 +00:00
|
|
|
|
|
|
|
val parseTree = parseModule(srcText)
|
|
|
|
assertEquals(parseTree.block().size, 2)
|
|
|
|
}
|
|
|
|
|
2021-06-18 19:55:03 +00:00
|
|
|
@Test
|
|
|
|
fun testInterleavedEolAndCommentBeforeFirstBlock() {
|
|
|
|
// issue: #47
|
|
|
|
val srcText = """
|
|
|
|
; comment
|
|
|
|
|
|
|
|
; comment
|
|
|
|
|
|
|
|
blockA {
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
val parseTree = parseModule(srcText)
|
|
|
|
assertEquals(parseTree.block().size, 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
fun testInterleavedEolAndCommentBetweenBlocks() {
|
|
|
|
// issue: #47
|
|
|
|
val srcText = """
|
|
|
|
blockA {
|
|
|
|
}
|
|
|
|
; comment
|
|
|
|
|
|
|
|
; comment
|
|
|
|
|
|
|
|
blockB {
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
val parseTree = parseModule(srcText)
|
|
|
|
assertEquals(parseTree.block().size, 2)
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
fun testInterleavedEolAndCommentAfterLastBlock() {
|
|
|
|
// issue: #47
|
|
|
|
val srcText = """
|
|
|
|
blockA {
|
|
|
|
}
|
|
|
|
; comment
|
|
|
|
|
|
|
|
; comment
|
|
|
|
|
|
|
|
"""
|
|
|
|
val parseTree = parseModule(srcText)
|
|
|
|
assertEquals(parseTree.block().size, 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
fun testNewlineBetweenTwoBlocksOrDirectivesStillRequired() {
|
|
|
|
// issue: #47
|
|
|
|
|
|
|
|
// block and block
|
|
|
|
assertFailsWith<ParsingFailedError>{ parseModule("""
|
|
|
|
blockA {
|
|
|
|
} blockB {
|
|
|
|
}
|
|
|
|
""") }
|
|
|
|
|
|
|
|
// block and directive
|
|
|
|
assertFailsWith<ParsingFailedError>{ parseModule("""
|
|
|
|
blockB {
|
|
|
|
} %import textio
|
|
|
|
""") }
|
|
|
|
|
|
|
|
// The following two are bogus due to directive *args* expected to follow the directive name.
|
|
|
|
// Leaving them in anyways.
|
|
|
|
|
|
|
|
// dir and block
|
|
|
|
assertFailsWith<ParsingFailedError>{ parseModule("""
|
|
|
|
%import textio blockB {
|
|
|
|
}
|
|
|
|
""") }
|
|
|
|
|
|
|
|
assertFailsWith<ParsingFailedError>{ parseModule("""
|
|
|
|
%import textio %import syslib
|
|
|
|
""") }
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
@Test
|
|
|
|
fun testImportLibraryModule() {
|
|
|
|
val program = Program("foo", mutableListOf(), DummyFunctions, DummyMemsizer)
|
|
|
|
val importer = ModuleImporter(program, DummyEncoding, "blah", listOf("./test/fixtures"))
|
|
|
|
|
|
|
|
//assertFailsWith<ParsingFailedError>(){ importer.importLibraryModule("import_file_with_syntax_error") }
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
2021-06-13 12:59:57 +00:00
|
|
|
@Test
|
|
|
|
fun testProg8Ast() {
|
2021-06-19 10:35:04 +00:00
|
|
|
val parseTree = parseModule("""
|
2021-06-13 12:59:57 +00:00
|
|
|
main {
|
|
|
|
sub start() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
""")
|
2021-06-19 10:35:04 +00:00
|
|
|
val ast = parseTree.toAst("test", Path.of(""), DummyEncoding)
|
2021-06-13 12:59:57 +00:00
|
|
|
assertIs<Block>(ast.statements.first())
|
|
|
|
assertEquals((ast.statements.first() as Block).name, "main")
|
|
|
|
}
|
|
|
|
}
|