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("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("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("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("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() } }