mirror of
https://github.com/irmen/prog8.git
synced 2025-01-12 04:30:03 +00:00
+ intro SourceCode, tying together source code text with its *origin*; Prog8Parser now only accepts this
This commit is contained in:
parent
7b89228fa7
commit
af209ad50e
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
118
compilerAst/src/prog8/parser/SourceCode.kt
Normal file
118
compilerAst/src/prog8/parser/SourceCode.kt
Normal 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])
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
@ -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")
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
|
82
compilerAst/test/TestSourceCode.kt
Normal file
82
compilerAst/test/TestSourceCode.kt
Normal 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
4
compilerAst/test/fixtures/simple_main.p8
vendored
Normal file
4
compilerAst/test/fixtures/simple_main.p8
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
main {
|
||||||
|
sub start() {
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user