2021-06-13 12:59:57 +00:00
|
|
|
package prog8tests
|
|
|
|
|
|
|
|
import org.junit.jupiter.api.Test
|
2021-06-21 10:02:36 +00:00
|
|
|
import kotlin.test.*
|
|
|
|
import java.nio.file.Path // TODO: use kotlin.io.path.Path instead
|
|
|
|
import kotlin.io.path.*
|
2021-07-11 10:53:01 +00:00
|
|
|
|
2021-06-19 16:10:26 +00:00
|
|
|
import prog8.parser.ParseError
|
|
|
|
import prog8.parser.Prog8Parser.parseModule
|
2021-06-21 10:02:36 +00:00
|
|
|
import prog8.parser.SourceCode
|
2021-07-11 10:53:01 +00:00
|
|
|
import prog8.ast.Node
|
|
|
|
import prog8.ast.base.Position
|
|
|
|
import prog8.ast.statements.*
|
2021-06-21 10:02:36 +00:00
|
|
|
|
2021-06-13 12:59:57 +00:00
|
|
|
|
2021-06-19 16:10:26 +00:00
|
|
|
class TestProg8Parser {
|
2021-07-04 16:09:16 +00:00
|
|
|
val workingDir = Path("").absolute() // Note: Path(".") does NOT work..!
|
|
|
|
val fixturesDir = workingDir.resolve("test/fixtures")
|
|
|
|
|
|
|
|
@Test
|
|
|
|
fun testDirectoriesSanityCheck() {
|
|
|
|
assertEquals("compilerAst", workingDir.fileName.toString())
|
|
|
|
assertTrue(fixturesDir.isDirectory(), "sanity check; should be directory: $fixturesDir")
|
|
|
|
}
|
2021-06-18 19:55:03 +00:00
|
|
|
|
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)
|
2021-06-21 10:02:36 +00:00
|
|
|
val src = SourceCode.of("foo {" + nl + "}") // source ends with '}' (= NO newline, issue #40)
|
2021-06-13 18:08:50 +00:00
|
|
|
|
2021-06-19 16:10:26 +00:00
|
|
|
// #45: Prog8ANTLRParser would report (throw) "missing <EOL> at '<EOF>'"
|
2021-06-21 10:02:36 +00:00
|
|
|
val module = parseModule(src)
|
2021-06-19 16:10:26 +00:00
|
|
|
assertEquals(1, module.statements.size)
|
2021-06-13 18:08:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@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-21 10:02:36 +00:00
|
|
|
val module = parseModule(SourceCode.of(srcText))
|
2021-06-19 16:10:26 +00:00
|
|
|
assertEquals(1, module.statements.size)
|
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 + "}"
|
|
|
|
|
2021-06-21 10:02:36 +00:00
|
|
|
assertFailsWith<ParseError> { parseModule(SourceCode.of(srcBad)) }
|
|
|
|
val module = parseModule(SourceCode.of(srcGood))
|
2021-06-19 16:10:26 +00:00
|
|
|
assertEquals(2, module.statements.size)
|
2021-06-13 18:47:14 +00:00
|
|
|
}
|
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
|
|
|
|
2021-06-21 10:02:36 +00:00
|
|
|
val module = parseModule(SourceCode.of(srcText))
|
2021-06-19 16:10:26 +00:00
|
|
|
assertEquals(2, module.statements.size)
|
2021-06-13 20:49:54 +00:00
|
|
|
}
|
|
|
|
|
2021-06-18 19:55:03 +00:00
|
|
|
@Test
|
|
|
|
fun testInterleavedEolAndCommentBeforeFirstBlock() {
|
|
|
|
// issue: #47
|
|
|
|
val srcText = """
|
|
|
|
; comment
|
|
|
|
|
|
|
|
; comment
|
|
|
|
|
|
|
|
blockA {
|
|
|
|
}
|
|
|
|
"""
|
2021-06-21 10:02:36 +00:00
|
|
|
val module = parseModule(SourceCode.of(srcText))
|
2021-06-19 16:10:26 +00:00
|
|
|
assertEquals(1, module.statements.size)
|
2021-06-18 19:55:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
fun testInterleavedEolAndCommentBetweenBlocks() {
|
|
|
|
// issue: #47
|
|
|
|
val srcText = """
|
|
|
|
blockA {
|
|
|
|
}
|
|
|
|
; comment
|
|
|
|
|
|
|
|
; comment
|
|
|
|
|
|
|
|
blockB {
|
|
|
|
}
|
|
|
|
"""
|
2021-06-21 10:02:36 +00:00
|
|
|
val module = parseModule(SourceCode.of(srcText))
|
2021-06-19 16:10:26 +00:00
|
|
|
assertEquals(2, module.statements.size)
|
2021-06-18 19:55:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
fun testInterleavedEolAndCommentAfterLastBlock() {
|
|
|
|
// issue: #47
|
|
|
|
val srcText = """
|
|
|
|
blockA {
|
|
|
|
}
|
|
|
|
; comment
|
|
|
|
|
|
|
|
; comment
|
|
|
|
|
|
|
|
"""
|
2021-06-21 10:02:36 +00:00
|
|
|
val module = parseModule(SourceCode.of(srcText))
|
2021-06-19 16:10:26 +00:00
|
|
|
assertEquals(1, module.statements.size)
|
2021-06-18 19:55:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
fun testNewlineBetweenTwoBlocksOrDirectivesStillRequired() {
|
|
|
|
// issue: #47
|
|
|
|
|
|
|
|
// block and block
|
2021-06-21 10:02:36 +00:00
|
|
|
assertFailsWith<ParseError>{ parseModule(SourceCode.of("""
|
2021-06-18 19:55:03 +00:00
|
|
|
blockA {
|
|
|
|
} blockB {
|
|
|
|
}
|
2021-06-21 10:02:36 +00:00
|
|
|
""")) }
|
2021-06-18 19:55:03 +00:00
|
|
|
|
|
|
|
// block and directive
|
2021-06-21 10:02:36 +00:00
|
|
|
assertFailsWith<ParseError>{ parseModule(SourceCode.of("""
|
2021-06-18 19:55:03 +00:00
|
|
|
blockB {
|
|
|
|
} %import textio
|
2021-06-21 10:02:36 +00:00
|
|
|
""")) }
|
2021-06-18 19:55:03 +00:00
|
|
|
|
|
|
|
// The following two are bogus due to directive *args* expected to follow the directive name.
|
|
|
|
// Leaving them in anyways.
|
|
|
|
|
|
|
|
// dir and block
|
2021-06-21 10:02:36 +00:00
|
|
|
assertFailsWith<ParseError>{ parseModule(SourceCode.of("""
|
2021-06-18 19:55:03 +00:00
|
|
|
%import textio blockB {
|
|
|
|
}
|
2021-06-21 10:02:36 +00:00
|
|
|
""")) }
|
2021-06-18 19:55:03 +00:00
|
|
|
|
2021-06-21 10:02:36 +00:00
|
|
|
assertFailsWith<ParseError>{ parseModule(SourceCode.of("""
|
2021-06-18 19:55:03 +00:00
|
|
|
%import textio %import syslib
|
2021-06-21 10:02:36 +00:00
|
|
|
""")) }
|
2021-06-19 18:27:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
2021-06-21 10:02:36 +00:00
|
|
|
fun parseModuleShouldNotLookAtImports() {
|
|
|
|
val imported = "i_do_not_exist"
|
|
|
|
val pathNoExt = Path.of(imported).absolute()
|
|
|
|
val pathWithExt = Path.of("${pathNoExt}.p8")
|
|
|
|
val text = "%import $imported"
|
2021-06-19 18:27:04 +00:00
|
|
|
|
2021-06-21 10:02:36 +00:00
|
|
|
assertFalse(pathNoExt.exists(), "sanity check: file should not exist: $pathNoExt")
|
|
|
|
assertFalse(pathWithExt.exists(), "sanity check: file should not exist: $pathWithExt")
|
2021-06-19 18:27:04 +00:00
|
|
|
|
2021-06-21 10:02:36 +00:00
|
|
|
val module = parseModule(SourceCode.of(text))
|
2021-06-19 18:27:04 +00:00
|
|
|
assertEquals(1, module.statements.size)
|
|
|
|
}
|
|
|
|
|
2021-07-04 16:09:16 +00:00
|
|
|
|
2021-07-10 19:03:14 +00:00
|
|
|
@Test
|
|
|
|
fun testParseModuleWithEmptyString() {
|
|
|
|
val module = parseModule(SourceCode.of(""))
|
|
|
|
assertEquals(0, module.statements.size)
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
fun testParseModuleWithEmptyFile() {
|
|
|
|
val path = fixturesDir.resolve("empty.p8")
|
|
|
|
assertTrue(path.isRegularFile(), "sanity check: should be regular file: $path")
|
|
|
|
|
|
|
|
val module = parseModule(SourceCode.fromPath(path))
|
|
|
|
assertEquals(0, module.statements.size)
|
|
|
|
}
|
|
|
|
|
2021-07-04 16:09:16 +00:00
|
|
|
@Test
|
|
|
|
fun testModuleNameForSourceFromString() {
|
|
|
|
val srcText = """
|
|
|
|
main {
|
|
|
|
}
|
|
|
|
""".trimIndent()
|
|
|
|
val module = parseModule(SourceCode.of(srcText))
|
|
|
|
|
|
|
|
// Note: assertContains has *actual* as first param
|
|
|
|
assertContains(module.name, Regex("^anonymous_[0-9a-f]+$"))
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
fun testModuleNameForSourceFromPath() {
|
|
|
|
val path = fixturesDir.resolve("simple_main.p8")
|
|
|
|
|
|
|
|
val module = parseModule(SourceCode.fromPath(path))
|
|
|
|
|
|
|
|
assertEquals(path.nameWithoutExtension, module.name)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-07-04 16:09:16 +00:00
|
|
|
fun assertPosition(actual: Position, expFile: String? = null, expLine: Int? = null, expStartCol: Int? = null, expEndCol: Int? = null) {
|
|
|
|
require(!listOf(expLine, expStartCol, expEndCol).all { it == null })
|
|
|
|
if (expLine != null) assertEquals(expLine, actual.line, ".position.line (1-based)")
|
|
|
|
if (expStartCol != null) assertEquals(expStartCol, actual.startCol, ".position.startCol (0-based)" )
|
|
|
|
if (expEndCol != null) assertEquals(expEndCol, actual.endCol, ".position.endCol (0-based)")
|
|
|
|
if (expFile != null) assertEquals(expFile, actual.file, ".position.file")
|
2021-07-04 16:09:16 +00:00
|
|
|
}
|
|
|
|
|
2021-07-04 16:09:16 +00:00
|
|
|
fun assertPosition(actual: Position, expFile: Regex? = null, expLine: Int? = null, expStartCol: Int? = null, expEndCol: Int? = null) {
|
|
|
|
require(!listOf(expLine, expStartCol, expEndCol).all { it == null })
|
|
|
|
if (expLine != null) assertEquals(expLine, actual.line, ".position.line (1-based)")
|
|
|
|
if (expStartCol != null) assertEquals(expStartCol, actual.startCol, ".position.startCol (0-based)" )
|
|
|
|
if (expEndCol != null) assertEquals(expEndCol, actual.endCol, ".position.endCol (0-based)")
|
2021-07-04 16:09:16 +00:00
|
|
|
// Note: assertContains expects *actual* value first
|
2021-07-04 16:09:16 +00:00
|
|
|
if (expFile != null) assertContains(actual.file, expFile, ".position.file")
|
2021-07-04 16:09:16 +00:00
|
|
|
}
|
|
|
|
|
2021-07-04 16:09:16 +00:00
|
|
|
fun assertPositionOf(actual: Node, expFile: String? = null, expLine: Int? = null, expStartCol: Int? = null, expEndCol: Int? = null) =
|
2021-07-04 16:09:16 +00:00
|
|
|
assertPosition(actual.position, expFile, expLine, expStartCol, expEndCol)
|
|
|
|
|
2021-07-04 16:09:16 +00:00
|
|
|
fun assertPositionOf(actual: Node, expFile: Regex? = null, expLine: Int? = null, expStartCol: Int? = null, expEndCol: Int? = null) =
|
2021-07-04 16:09:16 +00:00
|
|
|
assertPosition(actual.position, expFile, expLine, expStartCol, expEndCol)
|
|
|
|
|
|
|
|
|
2021-06-18 19:55:03 +00:00
|
|
|
@Test
|
2021-06-19 16:10:26 +00:00
|
|
|
fun testErrorLocationForSourceFromString() {
|
|
|
|
val srcText = "bad * { }\n"
|
2021-06-18 19:55:03 +00:00
|
|
|
|
2021-06-21 10:02:36 +00:00
|
|
|
assertFailsWith<ParseError> { parseModule(SourceCode.of(srcText)) }
|
2021-06-19 16:10:26 +00:00
|
|
|
try {
|
2021-06-21 10:02:36 +00:00
|
|
|
parseModule(SourceCode.of(srcText))
|
2021-06-19 16:10:26 +00:00
|
|
|
} catch (e: ParseError) {
|
2021-07-04 16:09:16 +00:00
|
|
|
assertPosition(e.position, Regex("^<String@[0-9a-f]+>$"), 1, 4, 4)
|
2021-07-04 16:09:16 +00:00
|
|
|
}
|
2021-06-19 16:10:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
fun testErrorLocationForSourceFromPath() {
|
2021-07-04 16:09:16 +00:00
|
|
|
val path = fixturesDir.resolve("file_with_syntax_error.p8")
|
2021-06-19 16:10:26 +00:00
|
|
|
|
2021-06-21 10:02:36 +00:00
|
|
|
assertFailsWith<ParseError> { parseModule(SourceCode.fromPath(path)) }
|
2021-06-19 16:10:26 +00:00
|
|
|
try {
|
2021-06-21 10:02:36 +00:00
|
|
|
parseModule(SourceCode.fromPath(path))
|
2021-06-19 16:10:26 +00:00
|
|
|
} catch (e: ParseError) {
|
2021-07-04 16:09:16 +00:00
|
|
|
assertPosition(e.position, path.absolutePathString(), 2, 6) // TODO: endCol wrong
|
2021-06-19 16:10:26 +00:00
|
|
|
}
|
2021-06-18 19:55:03 +00:00
|
|
|
}
|
|
|
|
|
2021-07-04 16:09:16 +00:00
|
|
|
@Test
|
|
|
|
fun testModulePositionForSourceFromString() {
|
|
|
|
val srcText = """
|
|
|
|
main {
|
|
|
|
}
|
|
|
|
""".trimIndent()
|
|
|
|
val module = parseModule(SourceCode.of(srcText))
|
2021-07-04 16:09:16 +00:00
|
|
|
assertPositionOf(module, Regex("^<String@[0-9a-f]+>$"), 1, 0) // TODO: endCol wrong
|
2021-07-04 16:09:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
fun testModulePositionForSourceFromPath() {
|
|
|
|
val path = fixturesDir.resolve("simple_main.p8")
|
|
|
|
|
|
|
|
val module = parseModule(SourceCode.fromPath(path))
|
2021-07-04 16:09:16 +00:00
|
|
|
assertPositionOf(module, path.absolutePathString(), 1, 0) // TODO: endCol wrong
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
fun testInnerNodePositionsForSourceFromPath() {
|
|
|
|
val path = fixturesDir.resolve("simple_main.p8")
|
|
|
|
|
|
|
|
val module = parseModule(SourceCode.fromPath(path))
|
|
|
|
val mpf = module.position.file
|
|
|
|
|
|
|
|
assertPositionOf(module, path.absolutePathString(), 1, 0) // TODO: endCol wrong
|
|
|
|
val mainBlock = module.statements.filterIsInstance<Block>()[0]
|
|
|
|
assertPositionOf(mainBlock, mpf, 1, 0) // TODO: endCol wrong!
|
|
|
|
val startSub = mainBlock.statements.filterIsInstance<Subroutine>()[0]
|
|
|
|
assertPositionOf(startSub, mpf, 2, 4) // TODO: endCol wrong!
|
2021-07-04 16:09:16 +00:00
|
|
|
}
|
|
|
|
|
2021-07-04 16:09:16 +00:00
|
|
|
/**
|
|
|
|
* TODO: this test is testing way too much at once
|
|
|
|
*/
|
|
|
|
@Test
|
|
|
|
fun testInnerNodePositionsForSourceFromString() {
|
|
|
|
val srcText = """
|
|
|
|
%target 16, "abc" ; DirectiveArg directly inherits from Node - neither an Expression nor a Statement..?
|
|
|
|
main {
|
|
|
|
sub start() {
|
|
|
|
ubyte foo = 42
|
|
|
|
ubyte bar
|
|
|
|
when (foo) {
|
|
|
|
23 -> bar = 'x' ; WhenChoice, also directly inheriting Node
|
|
|
|
42 -> bar = 'y'
|
|
|
|
else -> bar = 'z'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
""".trimIndent()
|
|
|
|
val module = parseModule(SourceCode.of(srcText))
|
|
|
|
val mpf = module.position.file
|
|
|
|
|
|
|
|
val targetDirective = module.statements.filterIsInstance<Directive>()[0]
|
|
|
|
assertPositionOf(targetDirective, mpf, 1, 0) // TODO: endCol wrong!
|
|
|
|
val mainBlock = module.statements.filterIsInstance<Block>()[0]
|
|
|
|
assertPositionOf(mainBlock, mpf, 2, 0) // TODO: endCol wrong!
|
|
|
|
val startSub = mainBlock.statements.filterIsInstance<Subroutine>()[0]
|
|
|
|
assertPositionOf(startSub, mpf, 3, 4) // TODO: endCol wrong!
|
|
|
|
val declFoo = startSub.statements.filterIsInstance<VarDecl>()[0]
|
|
|
|
assertPositionOf(declFoo, mpf, 4, 8) // TODO: endCol wrong!
|
|
|
|
val rhsFoo = declFoo.value!!
|
|
|
|
assertPositionOf(rhsFoo, mpf, 4, 20) // TODO: endCol wrong!
|
|
|
|
val declBar = startSub.statements.filterIsInstance<VarDecl>()[1]
|
|
|
|
assertPositionOf(declBar, mpf, 5, 8) // TODO: endCol wrong!
|
|
|
|
val whenStmt = startSub.statements.filterIsInstance<WhenStatement>()[0]
|
|
|
|
assertPositionOf(whenStmt, mpf, 6, 8) // TODO: endCol wrong!
|
|
|
|
assertPositionOf(whenStmt.choices[0], mpf, 7, 12) // TODO: endCol wrong!
|
|
|
|
assertPositionOf(whenStmt.choices[1], mpf, 8, 12) // TODO: endCol wrong!
|
|
|
|
assertPositionOf(whenStmt.choices[2], mpf, 9, 12) // TODO: endCol wrong!
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-06-13 12:59:57 +00:00
|
|
|
@Test
|
|
|
|
fun testProg8Ast() {
|
2021-06-21 10:02:36 +00:00
|
|
|
val module = parseModule(SourceCode.of("""
|
|
|
|
main {
|
|
|
|
sub start() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"""))
|
2021-06-19 16:10:26 +00:00
|
|
|
assertIs<Block>(module.statements.first())
|
2021-07-04 16:09:16 +00:00
|
|
|
assertEquals("main", (module.statements.first() as Block).name)
|
2021-06-13 12:59:57 +00:00
|
|
|
}
|
|
|
|
}
|