+ intro SourceCode, tying together source code text with its *origin*; Prog8Parser now only accepts this

This commit is contained in:
meisl 2021-06-21 12:02:36 +02:00
parent 7b89228fa7
commit af209ad50e
7 changed files with 307 additions and 135 deletions

View File

@ -8,12 +8,12 @@ import prog8.ast.base.Position
import prog8.ast.base.SyntaxError import prog8.ast.base.SyntaxError
import prog8.ast.statements.Directive import prog8.ast.statements.Directive
import prog8.ast.statements.DirectiveArg import prog8.ast.statements.DirectiveArg
import java.io.InputStream import kotlin.io.FileSystemException
import java.net.URL
import java.nio.file.FileSystems import java.nio.file.FileSystems
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path // TODO: use kotlin.io.paths.Path instead
import java.nio.file.Paths import java.nio.file.Paths // TODO: use kotlin.io.paths.Path instead
fun moduleName(fileName: Path) = fileName.toString().substringBeforeLast('.') fun moduleName(fileName: Path) = fileName.toString().substringBeforeLast('.')
@ -38,7 +38,7 @@ class ModuleImporter(private val program: Program,
else else
println("") println("")
val module = Prog8Parser.parseModule(filePath) val module = Prog8Parser.parseModule(SourceCode.fromPath(filePath))
module.program = program module.program = program
module.linkParents(program.namespace) module.linkParents(program.namespace)
@ -65,7 +65,7 @@ class ModuleImporter(private val program: Program,
private fun importModule(stream: CharStream, modulePath: Path): Module { private fun importModule(stream: CharStream, modulePath: Path): Module {
val parser = Prog8Parser val parser = Prog8Parser
val sourceText = stream.toString() val sourceText = stream.toString()
val moduleAst = parser.parseModule(sourceText) val moduleAst = parser.parseModule(SourceCode.of(sourceText))
moduleAst.program = program moduleAst.program = program
moduleAst.linkParents(program.namespace) moduleAst.linkParents(program.namespace)
program.modules.add(moduleAst) program.modules.add(moduleAst)
@ -92,16 +92,13 @@ class ModuleImporter(private val program: Program,
if(existing!=null) if(existing!=null)
return null return null
val rsc = tryGetModuleFromResource("$moduleName.p8", compilationTargetName) val srcCode = tryGetModuleFromResource("$moduleName.p8", compilationTargetName)
val importedModule = val importedModule =
if(rsc!=null) { if (srcCode != null) { // found in resources
// load the module from the embedded resource // load the module from the embedded resource
val (resource, resourcePath) = rsc println("importing '$moduleName' (library): ${srcCode.origin}")
resource.use { val path = Path.of(URL(srcCode.origin).file)
println("importing '$moduleName' (library)") importModule(srcCode.getCharStream(), path)
val content = it.reader().readText().replace("\r\n", "\n")
importModule(CharStreams.fromString(content), Module.pathForResource(resourcePath))
}
} else { } else {
val modulePath = tryGetModuleFromFile(moduleName, source, import.position) val modulePath = tryGetModuleFromFile(moduleName, source, import.position)
importModule(modulePath) importModule(modulePath)
@ -120,17 +117,16 @@ class ModuleImporter(private val program: Program,
importedModule.statements.addAll(0, directives) importedModule.statements.addAll(0, directives)
} }
private fun tryGetModuleFromResource(name: String, compilationTargetName: String): Pair<InputStream, String>? { private fun tryGetModuleFromResource(name: String, compilationTargetName: String): SourceCode? {
val targetSpecificPath = "/prog8lib/$compilationTargetName/$name" // try target speficic first
val targetSpecificResource = object{}.javaClass.getResourceAsStream(targetSpecificPath) try {
if(targetSpecificResource!=null) return SourceCode.fromResources("/prog8lib/$compilationTargetName/$name")
return Pair(targetSpecificResource, targetSpecificPath) } catch (e: FileSystemException) {
}
val generalPath = "/prog8lib/$name" try {
val generalResource = object{}.javaClass.getResourceAsStream(generalPath) return SourceCode.fromResources("/prog8lib/$name")
if(generalResource!=null) } catch (e: FileSystemException) {
return Pair(generalResource, generalPath) }
return null return null
} }

View File

@ -27,17 +27,9 @@ private fun RecognitionException.getPosition(provenance: String) : Position {
object Prog8Parser { object Prog8Parser {
fun parseModule(srcPath: Path): Module { fun parseModule(src: SourceCode): Module {
return parseModule(CharStreams.fromPath(srcPath), srcPath.fileName.toString()) val antlrErrorListener = AntlrErrorListener(src.origin)
} val lexer = Prog8ANTLRLexer(src.getCharStream())
fun parseModule(srcText: String): Module {
return parseModule(CharStreams.fromString(srcText), "<String@${System.identityHashCode(srcText).toString(16)}>")
}
private fun parseModule(chars: CharStream, provenance: String): Module {
val antlrErrorListener = AntlrErrorListener(provenance)
val lexer = Prog8ANTLRLexer(chars)
lexer.removeErrorListeners() lexer.removeErrorListeners()
lexer.addErrorListener(antlrErrorListener) lexer.addErrorListener(antlrErrorListener)
val tokens = CommonTokenStream(lexer) val tokens = CommonTokenStream(lexer)

View File

@ -0,0 +1,118 @@
package prog8.parser
import org.antlr.v4.runtime.CharStream
import org.antlr.v4.runtime.CharStreams
import java.io.File
import java.nio.file.Path
import kotlin.io.path.*
/**
* Encapsulates - and ties together - actual source code (=text)
* and its [origin].
*/
abstract class SourceCode() {
/**
* Where this [SourceCode] instance came from.
* This can be one of the following:
* * a normal string representation of a [java.nio.file.Path], if it originates from a file (see [fromPath])
* * `<String@44c56085>` if was created via [of]
* * `<res:/x/y/z.ext>` if it came from resources (see [fromResources])
*/
abstract val origin: String
abstract fun getCharStream(): CharStream
/**
* The source code as plain string.
* *Note: this is meant for testing and debugging, do NOT use in application code!*
*/
fun asString() = this.getCharStream().toString()
/**
* Deliberately does NOT return the actual text.
* Use [getCharStream].
*/
final override fun toString() = super.toString()
// "static" factory methods
companion object {
/**
* Turn a plain String into a [SourceCode] object.
* [origin] will be something like `<String@44c56085>`.
*/
fun of(text: String): SourceCode {
return object : SourceCode() {
override val origin = "<String@${System.identityHashCode(text).toString(16)}>"
override fun getCharStream(): CharStream {
return CharStreams.fromString(text)
}
}
}
/**
* Get [SourceCode] from the file represented by the specified Path.
* This does not actually *access* the file, but it does check
* whether it
* * exists
* * is a regular file (ie: not a directory)
* * and is actually readable
*
* [origin] will be the given path in absolute and normalized form.
* @throws NoSuchFileException if the file does not exist
* @throws AccessDeniedException if the given path points to a directory or the file is non-readable for some other reason
*/
fun fromPath(path: Path): SourceCode {
if (!path.exists())
throw NoSuchFileException(path.toFile())
if (path.isDirectory())
throw AccessDeniedException(path.toFile(), reason = "Not a file but a directory")
if (!path.isReadable())
throw AccessDeniedException(path.toFile(), reason = "Is not readable")
val normalized = path.normalize()
return object : SourceCode() {
override val origin = normalized.absolutePathString()
override fun getCharStream(): CharStream {
return CharStreams.fromPath(normalized)
}
}
}
/**
* [origin]: `<res:/x/y/z.p8>` for a given `pathString` of "x/y/z.p8"
*/
fun fromResources(pathString: String): SourceCode {
val path = Path.of(pathString).normalize()
val sep = "/"
val normalized = sep + path.toMutableList().joinToString(sep)
val rscURL = object{}.javaClass.getResource(normalized)
if (rscURL == null) {
val rscRoot = object{}.javaClass.getResource("/")
throw NoSuchFileException(
File(normalized),
reason = "looked in resources rooted at $rscRoot")
}
return object : SourceCode() {
override val origin = "<res:$normalized>"
override fun getCharStream(): CharStream {
val inpStr = object{}.javaClass.getResourceAsStream(normalized)
val chars = CharStreams.fromStream(inpStr)
return chars
}
}
}
// TODO: possibly more, like fromURL(..)
/* // For `jar:..` URLs
// see https://stackoverflow.com/questions/22605666/java-access-files-in-jar-causes-java-nio-file-filesystemnotfoundexception
var url = URL("jar:file:/E:/x16/prog8(meisl)/compiler/build/libs/prog8compiler-7.0-BETA3-all.jar!/prog8lib/c64/textio.p8")
val uri = url.toURI()
val parts = uri.toString().split("!")
val fs = FileSystems.newFileSystem(URI.create(parts[0]), mutableMapOf(Pair("", "")) )
val path = fs.getPath(parts[1])
*/
}
}

View File

@ -1,5 +1,11 @@
package prog8tests package prog8tests
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import kotlin.test.*
import java.nio.file.Path // TODO: use kotlin.io.path.Path instead
import kotlin.io.path.*
import prog8.ast.IBuiltinFunctions import prog8.ast.IBuiltinFunctions
import prog8.ast.IMemSizer import prog8.ast.IMemSizer
import prog8.ast.IStringEncoding import prog8.ast.IStringEncoding
@ -11,11 +17,6 @@ import prog8.ast.expressions.InferredTypes
import prog8.ast.expressions.NumericLiteralValue import prog8.ast.expressions.NumericLiteralValue
import prog8.parser.ModuleImporter import prog8.parser.ModuleImporter
import prog8.parser.ParseError import prog8.parser.ParseError
import java.nio.file.Path
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import kotlin.io.path.*
import kotlin.test.*
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ -51,7 +52,7 @@ class TestModuleImporter {
val srcPath = Path.of("test", "fixtures", "i_do_not_exist") val srcPath = Path.of("test", "fixtures", "i_do_not_exist")
assertFalse(srcPath.exists(), "sanity check: file should not exist") assertFalse(srcPath.exists(), "sanity check: file should not exist")
assertFailsWith<java.nio.file.NoSuchFileException> { importer.importModule(srcPath) } assertFailsWith<NoSuchFileException> { importer.importModule(srcPath) }
} }
@Test @Test
@ -66,18 +67,7 @@ class TestModuleImporter {
// fn importModule(Path) used to check *.isReadable()*, but NOT .isRegularFile(): // fn importModule(Path) used to check *.isReadable()*, but NOT .isRegularFile():
assertTrue(srcPath.isReadable(), "sanity check: should still be readable") assertTrue(srcPath.isReadable(), "sanity check: should still be readable")
assertFailsWith<java.nio.file.AccessDeniedException> { importer.importModule(srcPath) } assertFailsWith<AccessDeniedException> { importer.importModule(srcPath) }
}
@Test
fun testImportLibraryModuleWithNonExistingPath() {
val program = Program("foo", mutableListOf(), DummyFunctions, DummyMemsizer)
val importer = ModuleImporter(program, DummyEncoding, "blah", listOf("./test/fixtures"))
val srcPath = Path.of("i_do_not_exist.p8")
assertFalse(srcPath.exists(), "sanity check: file should not exist")
assertFailsWith<java.nio.file.NoSuchFileException> { importer.importLibraryModule(srcPath.nameWithoutExtension) }
} }
@Test @Test
@ -86,13 +76,14 @@ class TestModuleImporter {
val importer = ModuleImporter(program, DummyEncoding, "blah", listOf("./test/fixtures")) val importer = ModuleImporter(program, DummyEncoding, "blah", listOf("./test/fixtures"))
val filename = "file_with_syntax_error.p8" val filename = "file_with_syntax_error.p8"
val act = { importer.importModule(Path.of("test", "fixtures", filename )) } val path = Path.of("test", "fixtures", filename)
val act = { importer.importModule(path) }
assertFailsWith<ParseError> { act() } assertFailsWith<ParseError> { act() }
try { try {
act() act()
} catch (e: ParseError) { } catch (e: ParseError) {
assertEquals(filename, e.position.file, "provenance; should be the path's filename, incl. extension '.p8'") assertEquals(path.absolutePathString(), e.position.file)
assertEquals(2, e.position.line, "line; should be 1-based") assertEquals(2, e.position.line, "line; should be 1-based")
assertEquals(6, e.position.startCol, "startCol; should be 0-based" ) assertEquals(6, e.position.startCol, "startCol; should be 0-based" )
assertEquals(6, e.position.endCol, "endCol; should be 0-based") assertEquals(6, e.position.endCol, "endCol; should be 0-based")
@ -114,13 +105,28 @@ class TestModuleImporter {
try { try {
act() act()
} catch (e: ParseError) { } catch (e: ParseError) {
assertEquals(imported.fileName.toString(), e.position.file, "provenance; should be the importED file's filename, incl. extension '.p8'") val expectedProvenance = imported.absolutePathString()
assertEquals(expectedProvenance, e.position.file)
assertEquals(2, e.position.line, "line; should be 1-based") assertEquals(2, e.position.line, "line; should be 1-based")
assertEquals(6, e.position.startCol, "startCol; should be 0-based" ) assertEquals(6, e.position.startCol, "startCol; should be 0-based" )
assertEquals(6, e.position.endCol, "endCol; should be 0-based") assertEquals(6, e.position.endCol, "endCol; should be 0-based")
} }
} }
@Test
fun testImportLibraryModuleWithNonExistingName() {
val program = Program("foo", mutableListOf(), DummyFunctions, DummyMemsizer)
val importer = ModuleImporter(program, DummyEncoding, "blah", listOf("./test/fixtures"))
val filenameNoExt = "i_do_not_exist"
val filenameWithExt = filenameNoExt + ".p8"
val srcPathNoExt = Path.of("test", "fixtures", filenameNoExt)
val srcPathWithExt = Path.of("test", "fixtures", filenameWithExt)
assertFalse(srcPathNoExt.exists(), "sanity check: file should not exist")
assertFalse(srcPathWithExt.exists(), "sanity check: file should not exist")
assertFailsWith<NoSuchFileException> { importer.importLibraryModule(filenameNoExt) }
assertFailsWith<NoSuchFileException> { importer.importLibraryModule(filenameWithExt) }
}
@Test @Test
fun testImportLibraryModuleWithSyntaxError() { fun testImportLibraryModuleWithSyntaxError() {
@ -128,17 +134,16 @@ class TestModuleImporter {
val importer = ModuleImporter(program, DummyEncoding, "blah", listOf("./test/fixtures")) val importer = ModuleImporter(program, DummyEncoding, "blah", listOf("./test/fixtures"))
val filename = "file_with_syntax_error" val filename = "file_with_syntax_error"
val act = { importer.importLibraryModule(filename) } val act = { importer.importLibraryModule(filename) }
assertFailsWith<ParseError> { act() } assertFailsWith<ParseError> { act() }
try { try {
act() act()
} catch (e: ParseError) { } catch (e: ParseError) {
assertEquals( val expectedProvenance = Path.of("test", "fixtures", filename + ".p8")
filename + ".p8", .absolutePathString()
e.position.file, assertEquals(expectedProvenance, e.position.file)
"provenance; should be the path's filename, incl. extension '.p8'"
)
assertEquals(2, e.position.line, "line; should be 1-based") assertEquals(2, e.position.line, "line; should be 1-based")
assertEquals(6, e.position.startCol, "startCol; should be 0-based") assertEquals(6, e.position.startCol, "startCol; should be 0-based")
assertEquals(6, e.position.endCol, "endCol; should be 0-based") assertEquals(6, e.position.endCol, "endCol; should be 0-based")
@ -148,7 +153,8 @@ class TestModuleImporter {
@Test @Test
fun testImportLibraryModuleWithImportingBadModule() { fun testImportLibraryModuleWithImportingBadModule() {
val program = Program("foo", mutableListOf(), DummyFunctions, DummyMemsizer) val program = Program("foo", mutableListOf(), DummyFunctions, DummyMemsizer)
val importer = ModuleImporter(program, DummyEncoding, "blah", listOf("./test/fixtures")) val libdirs = listOf("./test/fixtures")
val importer = ModuleImporter(program, DummyEncoding, "blah", libdirs)
val importing = "import_file_with_syntax_error" val importing = "import_file_with_syntax_error"
val imported = "file_with_syntax_error" val imported = "file_with_syntax_error"
@ -158,11 +164,10 @@ class TestModuleImporter {
try { try {
act() act()
} catch (e: ParseError) { } catch (e: ParseError) {
assertEquals( val expectedProvenance = Path.of(libdirs[0], imported + ".p8")
imported + ".p8", .normalize()
e.position.file, .absolutePathString()
"provenance; should be the importED file's name, incl. extension '.p8'" assertEquals(expectedProvenance, e.position.file)
)
assertEquals(2, e.position.line, "line; should be 1-based") assertEquals(2, e.position.line, "line; should be 1-based")
assertEquals(6, e.position.startCol, "startCol; should be 0-based") assertEquals(6, e.position.startCol, "startCol; should be 0-based")
assertEquals(6, e.position.endCol, "endCol; should be 0-based") assertEquals(6, e.position.endCol, "endCol; should be 0-based")

View File

@ -1,26 +1,24 @@
package prog8tests package prog8tests
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import kotlin.test.*
import java.nio.file.Path // TODO: use kotlin.io.path.Path instead
import kotlin.io.path.*
import prog8.ast.statements.Block import prog8.ast.statements.Block
import prog8.parser.ParseError import prog8.parser.ParseError
import prog8.parser.Prog8Parser
import prog8.parser.Prog8Parser.parseModule import prog8.parser.Prog8Parser.parseModule
import java.nio.file.Path import prog8.parser.SourceCode
import kotlin.io.path.exists
import kotlin.io.path.isDirectory
import kotlin.io.path.isReadable
import kotlin.io.path.isRegularFile
import kotlin.test.*
class TestProg8Parser { class TestProg8Parser {
@Test @Test
fun testModuleSourceNeedNotEndWithNewline() { fun testModuleSourceNeedNotEndWithNewline() {
val nl = "\n" // say, Unix-style (different flavours tested elsewhere) val nl = "\n" // say, Unix-style (different flavours tested elsewhere)
val srcText = "foo {" + nl + "}" // source ends with '}' (= NO newline, issue #40) val src = SourceCode.of("foo {" + nl + "}") // source ends with '}' (= NO newline, issue #40)
// #45: Prog8ANTLRParser would report (throw) "missing <EOL> at '<EOF>'" // #45: Prog8ANTLRParser would report (throw) "missing <EOL> at '<EOF>'"
val module = parseModule(srcText) val module = parseModule(src)
assertEquals(1, module.statements.size) assertEquals(1, module.statements.size)
} }
@ -28,7 +26,7 @@ class TestProg8Parser {
fun testModuleSourceMayEndWithNewline() { fun testModuleSourceMayEndWithNewline() {
val nl = "\n" // say, Unix-style (different flavours tested elsewhere) val nl = "\n" // say, Unix-style (different flavours tested elsewhere)
val srcText = "foo {" + nl + "}" + nl // source does end with a newline (issue #40) val srcText = "foo {" + nl + "}" + nl // source does end with a newline (issue #40)
val module = parseModule(srcText) val module = parseModule(SourceCode.of(srcText))
assertEquals(1, module.statements.size) assertEquals(1, module.statements.size)
} }
@ -42,8 +40,8 @@ class TestProg8Parser {
// GOOD: 2nd block `bar` does start on a new line; however, a nl at the very end ain't needed // 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 + "}" val srcGood = "foo {" + nl + "}" + nl + "bar {" + nl + "}"
assertFailsWith<ParseError> { parseModule(srcBad) } assertFailsWith<ParseError> { parseModule(SourceCode.of(srcBad)) }
val module = parseModule(srcGood) val module = parseModule(SourceCode.of(srcGood))
assertEquals(2, module.statements.size) assertEquals(2, module.statements.size)
} }
@ -71,7 +69,7 @@ class TestProg8Parser {
"}" + "}" +
nlUnix // end with newline (see testModuleSourceNeedNotEndWithNewline) nlUnix // end with newline (see testModuleSourceNeedNotEndWithNewline)
val module = parseModule(srcText) val module = parseModule(SourceCode.of(srcText))
assertEquals(2, module.statements.size) assertEquals(2, module.statements.size)
} }
@ -86,7 +84,7 @@ class TestProg8Parser {
blockA { blockA {
} }
""" """
val module = parseModule(srcText) val module = parseModule(SourceCode.of(srcText))
assertEquals(1, module.statements.size) assertEquals(1, module.statements.size)
} }
@ -103,7 +101,7 @@ class TestProg8Parser {
blockB { blockB {
} }
""" """
val module = parseModule(srcText) val module = parseModule(SourceCode.of(srcText))
assertEquals(2, module.statements.size) assertEquals(2, module.statements.size)
} }
@ -118,7 +116,7 @@ class TestProg8Parser {
; comment ; comment
""" """
val module = parseModule(srcText) val module = parseModule(SourceCode.of(srcText))
assertEquals(1, module.statements.size) assertEquals(1, module.statements.size)
} }
@ -127,66 +125,43 @@ class TestProg8Parser {
// issue: #47 // issue: #47
// block and block // block and block
assertFailsWith<ParseError>{ parseModule(""" assertFailsWith<ParseError>{ parseModule(SourceCode.of("""
blockA { blockA {
} blockB { } blockB {
} }
""") } """)) }
// block and directive // block and directive
assertFailsWith<ParseError>{ parseModule(""" assertFailsWith<ParseError>{ parseModule(SourceCode.of("""
blockB { blockB {
} %import textio } %import textio
""") } """)) }
// The following two are bogus due to directive *args* expected to follow the directive name. // The following two are bogus due to directive *args* expected to follow the directive name.
// Leaving them in anyways. // Leaving them in anyways.
// dir and block // dir and block
assertFailsWith<ParseError>{ parseModule(""" assertFailsWith<ParseError>{ parseModule(SourceCode.of("""
%import textio blockB { %import textio blockB {
} }
""") } """)) }
assertFailsWith<ParseError>{ parseModule(""" assertFailsWith<ParseError>{ parseModule(SourceCode.of("""
%import textio %import syslib %import textio %import syslib
""") } """)) }
} }
@Test @Test
fun testParseModuleWithDirectoryPath() { fun parseModuleShouldNotLookAtImports() {
val srcPath = Path.of("test", "fixtures") val imported = "i_do_not_exist"
assertTrue(srcPath.isDirectory(), "sanity check: should be a directory") val pathNoExt = Path.of(imported).absolute()
assertFailsWith<java.nio.file.AccessDeniedException> { Prog8Parser.parseModule(srcPath) } val pathWithExt = Path.of("${pathNoExt}.p8")
} val text = "%import $imported"
@Test assertFalse(pathNoExt.exists(), "sanity check: file should not exist: $pathNoExt")
fun testParseModuleWithNonExistingPath() { assertFalse(pathWithExt.exists(), "sanity check: file should not exist: $pathWithExt")
val srcPath = Path.of("test", "fixtures", "i_do_not_exist")
assertFalse(srcPath.exists(), "sanity check: file should not exist")
assertFailsWith<java.nio.file.NoSuchFileException> { Prog8Parser.parseModule(srcPath) }
}
@Test val module = parseModule(SourceCode.of(text))
fun testParseModuleWithPathMissingExtension_p8() {
val srcPathWithoutExt = Path.of("test", "fixtures", "file_with_syntax_error")
val srcPathWithExt = Path.of(srcPathWithoutExt.toString() + ".p8")
assertTrue(srcPathWithExt.isRegularFile(), "sanity check: should be normal file")
assertTrue(srcPathWithExt.isReadable(), "sanity check: should be readable")
assertFailsWith<java.nio.file.NoSuchFileException> { Prog8Parser.parseModule(srcPathWithoutExt) }
}
@Test
fun testParseModuleWithStringShouldNotLookAtImports() {
val srcText = "%import i_do_not_exist"
val module = Prog8Parser.parseModule(srcText)
assertEquals(1, module.statements.size)
}
@Test
fun testParseModuleWithPathShouldNotLookAtImports() {
val srcPath = Path.of("test", "fixtures", "import_nonexisting.p8")
val module = Prog8Parser.parseModule(srcPath)
assertEquals(1, module.statements.size) assertEquals(1, module.statements.size)
} }
@ -194,9 +169,9 @@ class TestProg8Parser {
fun testErrorLocationForSourceFromString() { fun testErrorLocationForSourceFromString() {
val srcText = "bad * { }\n" val srcText = "bad * { }\n"
assertFailsWith<ParseError> { parseModule(srcText) } assertFailsWith<ParseError> { parseModule(SourceCode.of(srcText)) }
try { try {
parseModule(srcText) parseModule(SourceCode.of(srcText))
} catch (e: ParseError) { } catch (e: ParseError) {
// Note: assertContains expects *actual* value first // Note: assertContains expects *actual* value first
assertContains(e.position.file, Regex("^<String@[0-9a-f]+>$")) assertContains(e.position.file, Regex("^<String@[0-9a-f]+>$"))
@ -211,11 +186,11 @@ class TestProg8Parser {
val filename = "file_with_syntax_error.p8" val filename = "file_with_syntax_error.p8"
val path = Path.of("test", "fixtures", filename) val path = Path.of("test", "fixtures", filename)
assertFailsWith<ParseError> { parseModule(path) } assertFailsWith<ParseError> { parseModule(SourceCode.fromPath(path)) }
try { try {
parseModule(path) parseModule(SourceCode.fromPath(path))
} catch (e: ParseError) { } catch (e: ParseError) {
assertEquals(filename, e.position.file, "provenance; should be the path's filename, incl. extension '.p8'") assertEquals(path.absolutePathString(), e.position.file, "provenance; should be the path's filename, incl. extension '.p8'")
assertEquals(2, e.position.line, "line; should be 1-based") assertEquals(2, e.position.line, "line; should be 1-based")
assertEquals(6, e.position.startCol, "startCol; should be 0-based" ) assertEquals(6, e.position.startCol, "startCol; should be 0-based" )
assertEquals(6, e.position.endCol, "endCol; should be 0-based") assertEquals(6, e.position.endCol, "endCol; should be 0-based")
@ -224,13 +199,13 @@ class TestProg8Parser {
@Test @Test
fun testProg8Ast() { fun testProg8Ast() {
val module = parseModule(""" val module = parseModule(SourceCode.of("""
main { main {
sub start() { sub start() {
return return
} }
} }
""") """))
assertIs<Block>(module.statements.first()) assertIs<Block>(module.statements.first())
assertEquals((module.statements.first() as Block).name, "main") assertEquals((module.statements.first() as Block).name, "main")
} }

View File

@ -0,0 +1,82 @@
package prog8tests
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import kotlin.test.*
import java.nio.file.Path // TODO: use kotlin.io.path.Path instead
import kotlin.io.path.*
import prog8.parser.SourceCode
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestSourceCode {
@Test
fun testFactoryMethod_Of() {
val text = """
main { }
""".trimIndent()
val src = SourceCode.of(text)
val actualText = src.getCharStream().toString()
assertContains(src.origin, Regex("^<String@[0-9a-f]+>$"))
assertEquals(text, actualText)
}
@Test
fun testFromPathWithNonExistingPath() {
val filename = "i_do_not_exist.p8"
val path = Path.of("test", "fixtures", filename)
assertFalse(path.exists(), "sanity check: file should not exist: ${path.absolute()}")
assertFailsWith<NoSuchFileException> { SourceCode.fromPath(path) }
}
@Test
fun testFromPathWithMissingExtension_p8() {
val pathWithoutExt = Path.of("test", "fixtures", "simple_main")
val pathWithExt = Path.of(pathWithoutExt.toString() + ".p8")
assertTrue(pathWithExt.isRegularFile(), "sanity check: should be normal file: ${pathWithExt.absolute()}")
assertTrue(pathWithExt.isReadable(), "sanity check: should be readable: ${pathWithExt.absolute()}")
assertFailsWith<NoSuchFileException> { SourceCode.fromPath(pathWithoutExt) }
}
@Test
fun testFromPathWithDirectory() {
val path = Path.of("test", "fixtures")
assertTrue(path.isDirectory(), "sanity check: should be a directory")
assertFailsWith<AccessDeniedException> { SourceCode.fromPath(path) }
}
@Test
fun testFromPathWithExistingPath() {
val filename = "simple_main.p8"
val path = Path.of("test", "fixtures", filename)
val src = SourceCode.fromPath(path)
val expectedOrigin = path.normalize().absolutePathString()
assertEquals(expectedOrigin, src.origin)
val expectedSrcText = path.toFile().readText()
val actualSrcText = src.getCharStream().toString()
assertEquals(expectedSrcText, actualSrcText)
}
@Test
fun testFromPathWithExistingNonNormalizedPath() {
val filename = "simple_main.p8"
val path = Path.of(".", "test", "..", "test", "fixtures", filename)
val src = SourceCode.fromPath(path)
val expectedOrigin = path.normalize().absolutePathString()
assertEquals(expectedOrigin, src.origin)
val expectedSrcText = path.toFile().readText()
val actualSrcText = src.getCharStream().toString()
assertEquals(expectedSrcText, actualSrcText)
}
}

View File

@ -0,0 +1,4 @@
main {
sub start() {
}
}