transcendental-lisp/src/test/kotlin/parser/LispParserTest.kt

361 lines
10 KiB
Kotlin

package parser
import error.LispException
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.Assertions.fail
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
import scanner.LispScanner.UnterminatedStringException
import stream.LispIOException
import testutil.TestUtilities.assertIsErrorWithMessage
import testutil.TestUtilities.createIOExceptionThrowingInputStream
import testutil.TestUtilities.createInputStreamFromString
import testutil.TypeAssertions.assertAtSignExpression
import testutil.TypeAssertions.assertBackTickExpression
import testutil.TypeAssertions.assertCommaExpression
import testutil.TypeAssertions.assertList
import testutil.TypeAssertions.assertNil
import testutil.TypeAssertions.assertNumber
import testutil.TypeAssertions.assertString
import testutil.TypeAssertions.assertSymbol
import token.Eof.EofEncounteredException
import token.RightParenthesis.StartsWithRightParenthesisException
import token.TokenFactory.BadCharacterException
@TestInstance(PER_CLASS)
class LispParserTest {
private fun createLispParser(input: String) = LispParser(createInputStreamFromString(input), "testFile")
private fun createIOExceptionThrowingLispParser() = LispParser(createIOExceptionThrowingInputStream(), "testFile")
@Test
fun `EOF check is true with no input`() {
val input = ""
val parser = createLispParser(input)
assertThat(parser.isEof())
}
@Test
fun `EOF check is false after some input`() {
val input = "abc"
val parser = createLispParser(input)
assertThat(parser.isEof()).isFalse()
}
@Test
fun `EOF check is true after some input`() {
val input = "(yyz 9 9 9)"
val parser = createLispParser(input)
parser.nextSExpression()
assertThat(parser.isEof())
}
@Test
fun `EOF check is false after multiple expressions`() {
val input = "()()()"
val parser = createLispParser(input)
assertThat(parser.isEof()).isFalse()
parser.nextSExpression()
assertThat(parser.isEof()).isFalse()
parser.nextSExpression()
assertThat(parser.isEof()).isFalse()
}
@Test
fun `EOF check is true after multiple expressions`() {
val input = "()()()"
val parser = createLispParser(input)
assertThat(parser.isEof()).isFalse()
parser.nextSExpression()
assertThat(parser.isEof()).isFalse()
parser.nextSExpression()
assertThat(parser.isEof()).isFalse()
parser.nextSExpression()
assertThat(parser.isEof()).isTrue()
}
@Test
fun `nil is parsed correctly`() {
val input = "()"
val parser = createLispParser(input)
assertNil(parser.nextSExpression())
}
@Test
fun `number is parsed correctly`() {
val input = "12"
val parser = createLispParser(input)
assertNumber(parser.nextSExpression())
}
@Test
fun `identifier is parsed correctly`() {
val input = "identifier1"
val parser = createLispParser(input)
assertSymbol(parser.nextSExpression())
}
@Test
fun `string is parsed correctly`() {
val input = "\"string\""
val parser = createLispParser(input)
assertString(parser.nextSExpression())
}
@Test
fun `list is parsed correctly`() {
val input = "(1 2)"
val parser = createLispParser(input)
assertList(parser.nextSExpression())
}
@Test
fun `quoted identifier is parsed correctly`() {
val input = "'quoted"
val parser = createLispParser(input)
assertList(parser.nextSExpression())
assertThat(parser.isEof()).isTrue()
}
@Test
fun `complex list is parsed correctly`() {
val input = "(defun f (x) \n (print \n (list \"x is \" x) \n ) \n )"
val parser = createLispParser(input)
assertList(parser.nextSExpression())
assertThat(parser.isEof()).isTrue()
}
@Test
fun `multiple complex lists are parsed correctly`() {
val input = "(defun f (x) \n (print \n (list \"x is \" x) \n ) \n )" +
"(defun f (x) \n (print \n (list \"x is \" x) \n ) \n )"
val parser = createLispParser(input)
assertList(parser.nextSExpression())
assertList(parser.nextSExpression())
assertThat(parser.isEof()).isTrue()
}
@Test
fun `multiple expressions are parsed correctly`() {
val input = "(setq x 2) x \"hi\" () 29"
val parser = createLispParser(input)
assertList(parser.nextSExpression())
assertSymbol(parser.nextSExpression())
assertString(parser.nextSExpression())
assertNil(parser.nextSExpression())
assertNumber(parser.nextSExpression())
assertThat(parser.isEof()).isTrue()
}
@Test
fun `nil is parsed correctly after EOF checks`() {
val input = "()"
val parser = createLispParser(input)
parser.isEof()
parser.isEof()
assertNil(parser.nextSExpression())
assertThat(parser.isEof()).isTrue()
}
@Test
fun `throws exception for a bad token`() {
val input = "["
val parser = createLispParser(input)
assertThrows(BadCharacterException::class.java) { parser.nextSExpression() }
}
@Test
fun `bad token exception is cool`() {
val input = "["
val parser = createLispParser(input)
try {
parser.nextSExpression()
} catch (e: BadCharacterException) {
assertIsErrorWithMessage(e)
}
}
@Test
fun `throws exception for unterminated string`() {
val input = "\"string"
val parser = createLispParser(input)
assertThrows(UnterminatedStringException::class.java) { parser.nextSExpression() }
}
@Test
fun `throws exception for unterminated list`() {
val input = "(bad list"
val parser = createLispParser(input)
assertThrows(EofEncounteredException::class.java) { parser.nextSExpression() }
}
@Test
fun `unterminated list exception is cool`() {
val input = "(bad list"
val parser = createLispParser(input)
try {
parser.nextSExpression()
} catch (e: EofEncounteredException) {
assertIsErrorWithMessage(e)
}
}
@Test
fun `throws exception for unmatched right parenthesis`() {
val input = ")"
val parser = createLispParser(input)
assertThrows(StartsWithRightParenthesisException::class.java) { parser.nextSExpression() }
}
@Test
fun `unmatched right parenthesis exception is cool`() {
val input = ")"
val parser = createLispParser(input)
try {
parser.nextSExpression()
} catch (e: StartsWithRightParenthesisException) {
assertIsErrorWithMessage(e)
}
}
@Test
fun `bad character throws exception after an EOF check`() {
val input = "["
val parser = createLispParser(input)
try {
parser.isEof()
} catch (e: LispException) {
fail<Unit>("Exception thrown too early")
}
assertThrows(BadCharacterException::class.java) { parser.nextSExpression() }
}
@Test
fun `bad character throws exception at the correct time`() {
val input = "id["
val parser = createLispParser(input)
try {
parser.nextSExpression()
parser.isEof()
} catch (e: LispException) {
fail<Unit>("Exception thrown too early")
}
assertThrows(BadCharacterException::class.java) { parser.nextSExpression() }
}
@Test
fun `EOF is returned after an exception`() {
val input = "id["
val parser = createLispParser(input)
parser.nextSExpression()
parser.isEof()
try {
parser.nextSExpression()
fail<Unit>("Expected LispException")
} catch (e: LispException) {
}
assertThat(parser.isEof()).isTrue()
}
@Test
fun `IOException is handled correctly`() {
val parser = createIOExceptionThrowingLispParser()
try {
parser.isEof()
} catch (e: LispException) {
fail<Unit>("Exception thrown too early")
}
assertThrows(LispIOException::class.java) { parser.nextSExpression() }
}
@Test
fun `back tick is parsed correctly`() {
val input = "`(list ,a ,@b)"
val parser = createLispParser(input)
assertBackTickExpression(parser.nextSExpression())
assertThat(parser.isEof()).isTrue()
}
@Test
fun `comma is parsed correctly`() {
val input = ",a"
val parser = createLispParser(input)
assertCommaExpression(parser.nextSExpression())
assertThat(parser.isEof()).isTrue()
}
@Test
fun `at sign is parsed correctly`() {
val input = "@b"
val parser = createLispParser(input)
assertAtSignExpression(parser.nextSExpression())
assertThat(parser.isEof()).isTrue()
}
@Test
fun `back tick is not part of an identifier`() {
val input = "id`ab"
val parser = createLispParser(input)
assertSymbol(parser.nextSExpression())
assertBackTickExpression(parser.nextSExpression())
assertThat(parser.isEof()).isTrue()
}
@Test
fun `comma is not part of an identifier`() {
val input = "id,ab"
val parser = createLispParser(input)
assertSymbol(parser.nextSExpression())
assertCommaExpression(parser.nextSExpression())
assertThat(parser.isEof()).isTrue()
}
@Test
fun `at sign is not part of an identifier`() {
val input = "id@ab"
val parser = createLispParser(input)
assertSymbol(parser.nextSExpression())
assertAtSignExpression(parser.nextSExpression())
assertThat(parser.isEof()).isTrue()
}
}