From 92921676367a7f4593439f0730682c6edbf1b7f7 Mon Sep 17 00:00:00 2001 From: Alex Zerntev Date: Wed, 23 Aug 2023 13:31:18 +0300 Subject: [PATCH] RD-9442 Truffle's CSV query renders `\N` as `\\N` instead of `N` in Scala (#102) - Added escape char handling to CSV parser which defaults to "\" - Added test for it - Formatting --- .../truffle/builtin/TruffleCsvPackage.scala | 6 +- .../rql2/tests/builtin/CsvPackageTest.scala | 56 +- .../reader/parser/IterableParseCsvFile.java | 168 ++- .../reader/parser/IterableParseCsvString.java | 158 ++- .../reader/parser/RawTruffleCsvParser.java | 1163 +++++++++-------- .../parser/RawTruffleCsvParserSettings.java | 3 + 6 files changed, 826 insertions(+), 728 deletions(-) diff --git a/raw-compiler-rql2-truffle/src/main/scala/raw/compiler/rql2/truffle/builtin/TruffleCsvPackage.scala b/raw-compiler-rql2-truffle/src/main/scala/raw/compiler/rql2/truffle/builtin/TruffleCsvPackage.scala index a270eb66f..bcc467162 100644 --- a/raw-compiler-rql2-truffle/src/main/scala/raw/compiler/rql2/truffle/builtin/TruffleCsvPackage.scala +++ b/raw-compiler-rql2-truffle/src/main/scala/raw/compiler/rql2/truffle/builtin/TruffleCsvPackage.scala @@ -53,6 +53,7 @@ class TruffleCsvParseEntry extends CsvParseEntry with TruffleEntryExtension { class CsvColumnParser( encoding: ExpressionNode, skip: ExpressionNode, + escape: ExpressionNode, delimiter: ExpressionNode, quote: ExpressionNode, nulls: ExpressionNode, @@ -76,6 +77,7 @@ class CsvColumnParser( val iterableParser = new IterableParseCsvString( str, skip, + escape, delimiter, quote, new ProgramExpressionNode(lang, frameDescriptor, recordParser), @@ -103,6 +105,7 @@ class CsvColumnParser( url, encoding, skip, + escape, delimiter, quote, new ProgramExpressionNode(lang, frameDescriptor, recordParser), @@ -168,6 +171,7 @@ object CsvColumnParser { val encoding = arg("encoding").getOrElse(new StringNode("utf-8")) val skip = arg("skip").getOrElse(new IntNode("0")) + val escape = arg("escape").getOrElse(OptionSomeNodeGen.create(new StringNode("\\"))) val delimiter = arg("delimiter").getOrElse(new StringNode(",")) val quote = arg("quote").getOrElse(OptionSomeNodeGen.create(new StringNode("\""))) val nulls = @@ -176,7 +180,7 @@ object CsvColumnParser { val timeFormat = arg("timeFormat").getOrElse(new StringNode("HH:mm[:ss[.SSS]]")) val dateFormat = arg("dateFormat").getOrElse(new StringNode("yyyy-M-d")) val timestampFormat = arg("timestampFormat").getOrElse(new StringNode("yyyy-M-d['T'][ ]HH:mm[:ss[.SSS]]")) - new CsvColumnParser(encoding, skip, delimiter, quote, nulls, nans, timeFormat, dateFormat, timestampFormat) + new CsvColumnParser(encoding, skip, escape, delimiter, quote, nulls, nans, timeFormat, dateFormat, timestampFormat) } } diff --git a/raw-compiler-rql2/src/test/scala/raw/compiler/rql2/tests/builtin/CsvPackageTest.scala b/raw-compiler-rql2/src/test/scala/raw/compiler/rql2/tests/builtin/CsvPackageTest.scala index 9a5fced4f..40619caae 100644 --- a/raw-compiler-rql2/src/test/scala/raw/compiler/rql2/tests/builtin/CsvPackageTest.scala +++ b/raw-compiler-rql2/src/test/scala/raw/compiler/rql2/tests/builtin/CsvPackageTest.scala @@ -17,11 +17,19 @@ import raw.compiler.rql2.tests.{CompilerTestContext, FailAfterNServer} trait CsvPackageTest extends CompilerTestContext with FailAfterNServer { + val ttt = "\"\"\"" + private val data = tempFile("""a|b|c |1|10|100 |2|20|200 |3|30|300""".stripMargin) + private val dataWithEscaped = tempFile("""a|b|c + |1|10|\N""".stripMargin) + + private val dataWithQuoted = tempFile("""a|b|c + |1|10|"N"""".stripMargin) + private val headerLessData = tempFile("""1|10|100 |2|20|200 |3|30|300""".stripMargin) @@ -140,12 +148,6 @@ trait CsvPackageTest extends CompilerTestContext with FailAfterNServer { |{3, 30, 300} |]""".stripMargin)) - test(rql"""Csv.InferAndRead("$data")""".stripMargin)(it => it should evaluateTo("""[ - |{a: 1, b: 10, c: 100}, - |{a: 2, b: 20, c: 200}, - |{a: 3, b: 30, c: 300} - |]""".stripMargin)) - test(rql""" |let data = Csv.InferAndRead("$data") |in @@ -480,7 +482,7 @@ trait CsvPackageTest extends CompilerTestContext with FailAfterNServer { ) // Infer and Parse - val ttt = "\"\"\"" + test( s"""Csv.InferAndParse("1,2,3")""" )(_ should evaluateTo("""[{_1: 1, _2: 2, _3: 3}]""")) @@ -525,4 +527,44 @@ trait CsvPackageTest extends CompilerTestContext with FailAfterNServer { test(rql"""Csv.InferAndParse("1,2,3", escape="\\", quotes=["\""])""")(it => it should run) + test(rql"""Csv.InferAndRead("$dataWithEscaped")""".stripMargin)(it => it should evaluateTo("""[ + |{a: 1, b: 10, c: "N"} + |]""".stripMargin)) + + test( + rql"""Csv.Read("$dataWithEscaped", type collection(record(a:int,b:int,c:string)), delimiter="|", skip=1)""".stripMargin + )(it => it should evaluateTo("""[ + |{a: 1, b: 10, c: "N"} + |]""".stripMargin)) + + test( + rql"""Csv.Read("$dataWithEscaped", type collection(record(a:int,b:int,c:string)), delimiter="|", skip=1, escape=null)""".stripMargin + )(it => it should evaluateTo("""[ + |{a: 1, b: 10, c: "\\N"} + |]""".stripMargin)) + + test( + rql"""Csv.Read("$dataWithEscaped", type collection(record(a:int,b:int,c:string)), delimiter="|", skip=1, escape="\\")""".stripMargin + )(it => it should evaluateTo("""[ + |{a: 1, b: 10, c: "N"} + |]""".stripMargin)) + + test( + rql"""Csv.Read("$dataWithQuoted", type collection(record(a:int,b:int,c:string)), delimiter="|", skip=1, quote=null)""".stripMargin + )(it => it should evaluateTo("""[ + |{a: 1, b: 10, c: "\"N\""} + |]""".stripMargin)) + + test( + rql"""Csv.Read("$dataWithQuoted", type collection(record(a:int,b:int,c:string)), delimiter="|", skip=1)""".stripMargin + )(it => it should evaluateTo("""[ + |{a: 1, b: 10, c: "N"} + |]""".stripMargin)) + + test( + rql"""Csv.Read("$dataWithQuoted", type collection(record(a:int,b:int,c:string)), delimiter="|", skip=1 , quote="\"")""".stripMargin + )(it => it should evaluateTo("""[ + |{a: 1, b: 10, c: "N"} + |]""".stripMargin)) + } diff --git a/raw-runtime-rql2-truffle/src/main/java/raw/runtime/truffle/ast/io/csv/reader/parser/IterableParseCsvFile.java b/raw-runtime-rql2-truffle/src/main/java/raw/runtime/truffle/ast/io/csv/reader/parser/IterableParseCsvFile.java index 949176b10..bd5711311 100644 --- a/raw-runtime-rql2-truffle/src/main/java/raw/runtime/truffle/ast/io/csv/reader/parser/IterableParseCsvFile.java +++ b/raw-runtime-rql2-truffle/src/main/java/raw/runtime/truffle/ast/io/csv/reader/parser/IterableParseCsvFile.java @@ -29,81 +29,105 @@ @NodeInfo(shortName = "IterableParseCsvFile") public class IterableParseCsvFile extends ExpressionNode { - @Child private DirectCallNode childDirectCall; - @Child private ExpressionNode location; - @Child private ExpressionNode encodingExp; - @Child private ExpressionNode skipExp; - @Child private ExpressionNode delimiterExp; - @Child private ExpressionNode quoteExp; - @Child private ExpressionNode nullsExp; - @Child private ExpressionNode nansExp; - @Child private ExpressionNode dateFormatExp; - @Child private ExpressionNode timeFormatExp; - @Child private ExpressionNode datetimeFormatExp; + @Child + private DirectCallNode childDirectCall; + @Child + private ExpressionNode location; + @Child + private ExpressionNode encodingExp; + @Child + private ExpressionNode skipExp; + @Child + private ExpressionNode delimiterExp; + @Child + private ExpressionNode quoteExp; + @Child + private ExpressionNode escapeExp; + @Child + private ExpressionNode nullsExp; + @Child + private ExpressionNode nansExp; + @Child + private ExpressionNode dateFormatExp; + @Child + private ExpressionNode timeFormatExp; + @Child + private ExpressionNode datetimeFormatExp; - private final OptionLibrary options = OptionLibrary.getFactory().getUncached(); + private final OptionLibrary options = OptionLibrary.getFactory().getUncached(); - public IterableParseCsvFile( - ExpressionNode location, - ExpressionNode encodingExp, - ExpressionNode skipExp, - ExpressionNode delimiterExp, - ExpressionNode quoteExp, - ProgramExpressionNode columnParser, - ExpressionNode nullsExp, - ExpressionNode nansExp, - ExpressionNode dateFormatExp, - ExpressionNode timeFormatExp, - ExpressionNode datetimeFormatExp) { - this.childDirectCall = DirectCallNode.create(columnParser.getCallTarget()); - this.location = location; - this.encodingExp = encodingExp; - this.skipExp = skipExp; - this.delimiterExp = delimiterExp; - this.quoteExp = quoteExp; - this.nullsExp = nullsExp; - this.nansExp = nansExp; - this.dateFormatExp = dateFormatExp; - this.timeFormatExp = timeFormatExp; - this.datetimeFormatExp = datetimeFormatExp; - } + public IterableParseCsvFile( + ExpressionNode location, + ExpressionNode encodingExp, + ExpressionNode skipExp, + ExpressionNode escapeExp, + ExpressionNode delimiterExp, + ExpressionNode quoteExp, + ProgramExpressionNode columnParser, + ExpressionNode nullsExp, + ExpressionNode nansExp, + ExpressionNode dateFormatExp, + ExpressionNode timeFormatExp, + ExpressionNode datetimeFormatExp) { + this.childDirectCall = DirectCallNode.create(columnParser.getCallTarget()); + this.location = location; + this.encodingExp = encodingExp; + this.skipExp = skipExp; + this.delimiterExp = delimiterExp; + this.quoteExp = quoteExp; + this.escapeExp = escapeExp; + this.nullsExp = nullsExp; + this.nansExp = nansExp; + this.dateFormatExp = dateFormatExp; + this.timeFormatExp = timeFormatExp; + this.datetimeFormatExp = datetimeFormatExp; + } - public Object executeGeneric(VirtualFrame frame) { - LocationObject locationValue = (LocationObject) location.executeGeneric(frame); - RuntimeContext context = RawContext.get(this).getRuntimeContext(); - try { - String encodingValue = encodingExp.executeString(frame); - int skipValue = skipExp.executeInt(frame); - String delimiterValue = delimiterExp.executeString(frame); - Object quoteValue = quoteExp.executeGeneric(frame); - char quoteChar = 0; - boolean useQuote = false; - if (options.isDefined(quoteValue)) { - String quoteCharString = (String) options.get(quoteValue); - if (quoteCharString.length() > 0) { - useQuote = true; - quoteChar = quoteCharString.charAt(0); + public Object executeGeneric(VirtualFrame frame) { + LocationObject locationValue = (LocationObject) location.executeGeneric(frame); + RuntimeContext context = RawContext.get(this).getRuntimeContext(); + try { + String encodingValue = encodingExp.executeString(frame); + int skipValue = skipExp.executeInt(frame); + String delimiterValue = delimiterExp.executeString(frame); + Object quoteValue = quoteExp.executeGeneric(frame); + char quoteChar = 0; + boolean useQuote = false; + if (options.isDefined(quoteValue)) { + String quoteCharString = (String) options.get(quoteValue); + if (!quoteCharString.isEmpty()) { + useQuote = true; + quoteChar = quoteCharString.charAt(0); + } + } + Object escapeValue = escapeExp.executeGeneric(frame); + char escapeChar = 0; + if (options.isDefined(escapeValue)) { + String escapeCharString = (String) options.get(escapeValue); + if (!escapeCharString.isEmpty()) { + escapeChar = escapeCharString.charAt(0); + } + } + String[] nulls = ((StringList) nullsExp.executeGeneric(frame)).getInnerList(); + String[] nans = ((StringList) nansExp.executeGeneric(frame)).getInnerList(); + String dateFormat = dateFormatExp.executeString(frame); + String timeFormat = timeFormatExp.executeString(frame); + String datetimeFormat = datetimeFormatExp.executeString(frame); + RawTruffleCsvParserSettings settings = + new RawTruffleCsvParserSettings( + delimiterValue.charAt(0), + useQuote, + quoteChar, + escapeChar, + skipValue, + nulls, + nans, + dateFormat, + timeFormat, + datetimeFormat); + return new CsvCollection(locationValue, context, childDirectCall, encodingValue, settings); + } catch (UnexpectedResultException ex) { + throw new CsvParserRawTruffleException(ex.getMessage(), 0, 0, ex, this); } - } - String[] nulls = ((StringList) nullsExp.executeGeneric(frame)).getInnerList(); - String[] nans = ((StringList) nansExp.executeGeneric(frame)).getInnerList(); - String dateFormat = dateFormatExp.executeString(frame); - String timeFormat = timeFormatExp.executeString(frame); - String datetimeFormat = datetimeFormatExp.executeString(frame); - RawTruffleCsvParserSettings settings = - new RawTruffleCsvParserSettings( - delimiterValue.charAt(0), - useQuote, - quoteChar, - skipValue, - nulls, - nans, - dateFormat, - timeFormat, - datetimeFormat); - return new CsvCollection(locationValue, context, childDirectCall, encodingValue, settings); - } catch (UnexpectedResultException ex) { - throw new CsvParserRawTruffleException(ex.getMessage(), 0, 0, ex, this); } - } } diff --git a/raw-runtime-rql2-truffle/src/main/java/raw/runtime/truffle/ast/io/csv/reader/parser/IterableParseCsvString.java b/raw-runtime-rql2-truffle/src/main/java/raw/runtime/truffle/ast/io/csv/reader/parser/IterableParseCsvString.java index ad64c3455..2f70c0bce 100644 --- a/raw-runtime-rql2-truffle/src/main/java/raw/runtime/truffle/ast/io/csv/reader/parser/IterableParseCsvString.java +++ b/raw-runtime-rql2-truffle/src/main/java/raw/runtime/truffle/ast/io/csv/reader/parser/IterableParseCsvString.java @@ -26,76 +26,100 @@ @NodeInfo(shortName = "IterableParseCsvString") public class IterableParseCsvString extends ExpressionNode { - @Child private DirectCallNode childDirectCall; - @Child private ExpressionNode strExp; - @Child private ExpressionNode skip; - @Child private ExpressionNode delimiterExp; - @Child private ExpressionNode quoteExp; - @Child private ExpressionNode nullsExp; - @Child private ExpressionNode nansExp; - @Child private ExpressionNode dateFormatExp; - @Child private ExpressionNode timeFormatExp; - @Child private ExpressionNode datetimeFormatExp; + @Child + private DirectCallNode childDirectCall; + @Child + private ExpressionNode strExp; + @Child + private ExpressionNode skip; + @Child + private ExpressionNode delimiterExp; + @Child + private ExpressionNode quoteExp; - private final OptionLibrary options = OptionLibrary.getFactory().getUncached(); + @Child + private ExpressionNode escapeExp; + @Child + private ExpressionNode nullsExp; + @Child + private ExpressionNode nansExp; + @Child + private ExpressionNode dateFormatExp; + @Child + private ExpressionNode timeFormatExp; + @Child + private ExpressionNode datetimeFormatExp; - public IterableParseCsvString( - ExpressionNode strExp, - ExpressionNode skipExp, - ExpressionNode delimiterExp, - ExpressionNode quoteExp, - ProgramExpressionNode columnParser, - ExpressionNode nullsExp, - ExpressionNode nansExp, - ExpressionNode dateFormatExp, - ExpressionNode timeFormatExp, - ExpressionNode datetimeFormatExp) { - this.childDirectCall = DirectCallNode.create(columnParser.getCallTarget()); - this.strExp = strExp; - this.skip = skipExp; - this.delimiterExp = delimiterExp; - this.quoteExp = quoteExp; - this.nullsExp = nullsExp; - this.nansExp = nansExp; - this.dateFormatExp = dateFormatExp; - this.timeFormatExp = timeFormatExp; - this.datetimeFormatExp = datetimeFormatExp; - } + private final OptionLibrary options = OptionLibrary.getFactory().getUncached(); - public Object executeGeneric(VirtualFrame frame) { - try { - String str = strExp.executeString(frame); - int skipValue = skip.executeInt(frame); - String delimiterValue = delimiterExp.executeString(frame); - Object quoteValue = quoteExp.executeGeneric(frame); - char quoteChar = 0; - boolean useQuote = false; - if (options.isDefined(quoteValue)) { - String quoteCharString = (String) options.get(quoteValue); - if (quoteCharString.length() > 0) { - useQuote = true; - quoteChar = quoteCharString.charAt(0); + public IterableParseCsvString( + ExpressionNode strExp, + ExpressionNode skipExp, + ExpressionNode escExp, + ExpressionNode delimiterExp, + ExpressionNode quoteExp, + ProgramExpressionNode columnParser, + ExpressionNode nullsExp, + ExpressionNode nansExp, + ExpressionNode dateFormatExp, + ExpressionNode timeFormatExp, + ExpressionNode datetimeFormatExp) { + this.childDirectCall = DirectCallNode.create(columnParser.getCallTarget()); + this.strExp = strExp; + this.skip = skipExp; + this.delimiterExp = delimiterExp; + this.quoteExp = quoteExp; + this.escapeExp = escExp; + this.nullsExp = nullsExp; + this.nansExp = nansExp; + this.dateFormatExp = dateFormatExp; + this.timeFormatExp = timeFormatExp; + this.datetimeFormatExp = datetimeFormatExp; + } + + public Object executeGeneric(VirtualFrame frame) { + try { + String str = strExp.executeString(frame); + int skipValue = skip.executeInt(frame); + String delimiterValue = delimiterExp.executeString(frame); + Object quoteValue = quoteExp.executeGeneric(frame); + char quoteChar = 0; + boolean useQuote = false; + if (options.isDefined(quoteValue)) { + String quoteCharString = (String) options.get(quoteValue); + if (!quoteCharString.isEmpty()) { + useQuote = true; + quoteChar = quoteCharString.charAt(0); + } + } + Object escapeValue = escapeExp.executeGeneric(frame); + char escapeChar = 0; + if (options.isDefined(escapeValue)) { + String escapeCharString = (String) options.get(escapeValue); + if (!escapeCharString.isEmpty()) { + escapeChar = escapeCharString.charAt(0); + } + } + String[] nulls = ((StringList) nullsExp.executeGeneric(frame)).getInnerList(); + String[] nans = ((StringList) nansExp.executeGeneric(frame)).getInnerList(); + String dateFormat = dateFormatExp.executeString(frame); + String timeFormat = timeFormatExp.executeString(frame); + String datetimeFormat = datetimeFormatExp.executeString(frame); + RawTruffleCsvParserSettings settings = + new RawTruffleCsvParserSettings( + delimiterValue.charAt(0), + useQuote, + quoteChar, + escapeChar, + skipValue, + nulls, + nans, + dateFormat, + timeFormat, + datetimeFormat); + return new CsvFromStringCollection(str, childDirectCall, settings); + } catch (UnexpectedResultException ex) { + throw new CsvParserRawTruffleException(ex.getMessage(), 0, 0, ex, this); } - } - String[] nulls = ((StringList) nullsExp.executeGeneric(frame)).getInnerList(); - String[] nans = ((StringList) nansExp.executeGeneric(frame)).getInnerList(); - String dateFormat = dateFormatExp.executeString(frame); - String timeFormat = timeFormatExp.executeString(frame); - String datetimeFormat = datetimeFormatExp.executeString(frame); - RawTruffleCsvParserSettings settings = - new RawTruffleCsvParserSettings( - delimiterValue.charAt(0), - useQuote, - quoteChar, - skipValue, - nulls, - nans, - dateFormat, - timeFormat, - datetimeFormat); - return new CsvFromStringCollection(str, childDirectCall, settings); - } catch (UnexpectedResultException ex) { - throw new CsvParserRawTruffleException(ex.getMessage(), 0, 0, ex, this); } - } } diff --git a/raw-runtime-rql2-truffle/src/main/java/raw/runtime/truffle/ast/io/csv/reader/parser/RawTruffleCsvParser.java b/raw-runtime-rql2-truffle/src/main/java/raw/runtime/truffle/ast/io/csv/reader/parser/RawTruffleCsvParser.java index ef0ab2604..f2b489292 100644 --- a/raw-runtime-rql2-truffle/src/main/java/raw/runtime/truffle/ast/io/csv/reader/parser/RawTruffleCsvParser.java +++ b/raw-runtime-rql2-truffle/src/main/java/raw/runtime/truffle/ast/io/csv/reader/parser/RawTruffleCsvParser.java @@ -44,603 +44,604 @@ public class RawTruffleCsvParser { - final String[] nulls; - final String[] nans; - final int headerLines; - final String dateFormat, timeFormat, timestampFormat; - final DateTimeFormatter dateFormatter, timeFormatter, timestampFormatter; - - private final CsvParser jacksonParser; - final RawTruffleCharStream stream; - - public RawTruffleCsvParser(RawTruffleCharStream stream, RawTruffleCsvParserSettings settings) { - this.stream = stream; - try { - this.nulls = settings.nulls; - this.nans = settings.nans; - this.headerLines = settings.headerLines; - this.dateFormat = settings.dateFormat; - this.timeFormat = settings.timeFormat; - this.timestampFormat = settings.timestampFormat; - this.dateFormatter = DateTimeFormatCache.get(settings.dateFormat); - this.timeFormatter = DateTimeFormatCache.get(settings.timeFormat); - this.timestampFormatter = DateTimeFormatCache.get(settings.timestampFormat); - CsvFactory csvFactory = new CsvFactory(); - csvFactory.enable(CsvParser.Feature.TRIM_SPACES); - Reader reader = stream.getReader(); - jacksonParser = csvFactory.createParser(reader); - CsvSchema.Builder builder = CsvSchema.builder(); - builder.setColumnSeparator(settings.delimiter); - if (settings.useQuote) { - builder.setQuoteChar(settings.quoteChar); - } else { - builder.disableQuoteChar(); - } - jacksonParser.setSchema(builder.build()); - } catch (IOException | IllegalArgumentException ex) { - throw new CsvReaderRawTruffleException(stream, ex); - } - } - - boolean startingNewLine(ExpressionNode location) { - return jacksonParser.currentToken() == JsonToken.START_ARRAY; - } - - private int line = -1; - private int column = -1; - - void getNextField() { - line = jacksonParser.getCurrentLocation().getLineNr(); - column = jacksonParser.getCurrentLocation().getColumnNr(); - try { - JsonToken token = jacksonParser.nextToken(); - if (token == JsonToken.VALUE_STRING) { - return; - } - if (token == null) { - throw new CsvReaderRawTruffleException("unexpected EOF", this, stream); - } else if (token == JsonToken.END_ARRAY) { - throw new CsvReaderRawTruffleException("not enough columns found", this, stream); - } - } catch (IOException ex) { - throw new CsvReaderRawTruffleException(stream, ex); - } - } - - public int currentTokenLine() { - return line; - } - - public int currentTokenColumn() { - return column; - } - - public void finishLine(ExpressionNode location) { - JsonToken token; - do { - try { - token = jacksonParser.nextToken(); - } catch (IOException ex) { - throw new CsvParserRawTruffleException(this, ex, location); - } - } while (token != null && token != JsonToken.END_ARRAY); - } - - private static final TruffleLogger LOG = - TruffleLogger.getLogger(RawLanguage.ID, RawTruffleRuntimeException.class); - - public void close() { - try { - jacksonParser.close(); - } catch (IOException ex) { - // ignore but log - LOG.severe(ex.getMessage()); - } - } - - public void skipHeaderLines() { - try { - for (int i = 0; i < headerLines; i++) { + final String[] nulls; + final String[] nans; + final int headerLines; + final String dateFormat, timeFormat, timestampFormat; + final DateTimeFormatter dateFormatter, timeFormatter, timestampFormatter; + + private final CsvParser jacksonParser; + final RawTruffleCharStream stream; + + public RawTruffleCsvParser(RawTruffleCharStream stream, RawTruffleCsvParserSettings settings) { + this.stream = stream; + try { + this.nulls = settings.nulls; + this.nans = settings.nans; + this.headerLines = settings.headerLines; + this.dateFormat = settings.dateFormat; + this.timeFormat = settings.timeFormat; + this.timestampFormat = settings.timestampFormat; + this.dateFormatter = DateTimeFormatCache.get(settings.dateFormat); + this.timeFormatter = DateTimeFormatCache.get(settings.timeFormat); + this.timestampFormatter = DateTimeFormatCache.get(settings.timestampFormat); + CsvFactory csvFactory = new CsvFactory(); + csvFactory.enable(CsvParser.Feature.TRIM_SPACES); + Reader reader = stream.getReader(); + jacksonParser = csvFactory.createParser(reader); + CsvSchema.Builder builder = CsvSchema.builder(); + builder.setColumnSeparator(settings.delimiter); + if (settings.useQuote) { + builder.setQuoteChar(settings.quoteChar); + } else { + builder.disableQuoteChar(); + } + builder.setEscapeChar(settings.escapeChar); + jacksonParser.setSchema(builder.build()); + } catch (IOException | IllegalArgumentException ex) { + throw new CsvReaderRawTruffleException(stream, ex); + } + } + + boolean startingNewLine(ExpressionNode location) { + return jacksonParser.currentToken() == JsonToken.START_ARRAY; + } + + private int line = -1; + private int column = -1; + + void getNextField() { + line = jacksonParser.getCurrentLocation().getLineNr(); + column = jacksonParser.getCurrentLocation().getColumnNr(); + try { + JsonToken token = jacksonParser.nextToken(); + if (token == JsonToken.VALUE_STRING) { + return; + } + if (token == null) { + throw new CsvReaderRawTruffleException("unexpected EOF", this, stream); + } else if (token == JsonToken.END_ARRAY) { + throw new CsvReaderRawTruffleException("not enough columns found", this, stream); + } + } catch (IOException ex) { + throw new CsvReaderRawTruffleException(stream, ex); + } + } + + public int currentTokenLine() { + return line; + } + + public int currentTokenColumn() { + return column; + } + + public void finishLine(ExpressionNode location) { JsonToken token; do { - token = jacksonParser.nextToken(); - if (token == null) { - return; - } - } while (token != JsonToken.END_ARRAY); - } - } catch (IOException ex) { - throw new CsvReaderRawTruffleException(stream, ex); - } - } - - public boolean done() { - try { - return jacksonParser.nextToken() == null; - } catch (IOException ex) { - throw new CsvReaderRawTruffleException(stream, ex); - } - } - - @CompilerDirectives.TruffleBoundary - byte getByte(ExpressionNode location) { - try { - try { - return jacksonParser.getByteValue(); - } catch (JsonProcessingException ex) { - String malformed = - jacksonParser.getText(); // shouldn't throw since we read already the token - throw new CsvParserRawTruffleException( - String.format("cannot parse '%s' as a byte", malformed), this, stream, location); - } - } catch (IOException ex) { - throw new CsvReaderRawTruffleException(stream, ex, location); - } - } - - @CompilerDirectives.TruffleBoundary - Object getOptionByte(ExpressionNode location) { - try { - try { - String token = jacksonParser.getText(); - for (String nullToken : nulls) { - if (token.equals(nullToken)) { - return new EmptyOption(); - } + try { + token = jacksonParser.nextToken(); + } catch (IOException ex) { + throw new CsvParserRawTruffleException(this, ex, location); + } + } while (token != null && token != JsonToken.END_ARRAY); + } + + private static final TruffleLogger LOG = + TruffleLogger.getLogger(RawLanguage.ID, RawTruffleRuntimeException.class); + + public void close() { + try { + jacksonParser.close(); + } catch (IOException ex) { + // ignore but log + LOG.severe(ex.getMessage()); } - return new ObjectOption(jacksonParser.getByteValue()); - } catch (JsonProcessingException ex) { - throw new CsvParserRawTruffleException( - String.format("cannot parse '%s' as a byte", jacksonParser.getText()), - this, - stream, - location); - } - } catch (IOException ex) { - throw new CsvReaderRawTruffleException(stream, ex, location); - } - } - - @CompilerDirectives.TruffleBoundary - short getShort(ExpressionNode location) { - try { - try { - return jacksonParser.getShortValue(); - } catch (JsonProcessingException ex) { - String malformed = - jacksonParser.getText(); // shouldn't throw since we read already the token - throw new CsvParserRawTruffleException( - String.format("cannot parse '%s' as a short", malformed), this, stream, location); - } - } catch (IOException ex) { - throw new CsvReaderRawTruffleException(stream, ex, location); - } - } - - @CompilerDirectives.TruffleBoundary - Object getOptionShort(ExpressionNode location) { - try { - try { - String token = jacksonParser.getText(); - for (String nullToken : nulls) { - if (token.equals(nullToken)) { - return new EmptyOption(); - } + } + + public void skipHeaderLines() { + try { + for (int i = 0; i < headerLines; i++) { + JsonToken token; + do { + token = jacksonParser.nextToken(); + if (token == null) { + return; + } + } while (token != JsonToken.END_ARRAY); + } + } catch (IOException ex) { + throw new CsvReaderRawTruffleException(stream, ex); } - return new ObjectOption(jacksonParser.getShortValue()); - } catch (JsonProcessingException ex) { - throw new CsvParserRawTruffleException( - String.format("cannot parse '%s' as a short", jacksonParser.getText()), - this, - stream, - location); - } - } catch (IOException ex) { - throw new CsvReaderRawTruffleException(stream, ex, location); - } - } - - @CompilerDirectives.TruffleBoundary - int getInt(ExpressionNode location) { - try { - try { - return jacksonParser.getIntValue(); - } catch (JsonProcessingException ex) { - String malformed = - jacksonParser.getText(); // shouldn't throw since we read already the token - throw new CsvParserRawTruffleException( - String.format("cannot parse '%s' as an int", malformed), this, stream, location); - } - } catch (IOException ex) { - throw new CsvReaderRawTruffleException(stream, ex, location); - } - } - - @CompilerDirectives.TruffleBoundary - Object getOptionInt(ExpressionNode location) { - try { - try { - String token = jacksonParser.getText(); - for (String nullToken : nulls) { - if (token.equals(nullToken)) { - return new EmptyOption(); - } + } + + public boolean done() { + try { + return jacksonParser.nextToken() == null; + } catch (IOException ex) { + throw new CsvReaderRawTruffleException(stream, ex); } - return new ObjectOption(jacksonParser.getIntValue()); - } catch (JsonProcessingException ex) { - throw new CsvParserRawTruffleException( - String.format("cannot parse '%s' as an int", jacksonParser.getText()), - this, - stream, - location); - } - } catch (IOException ex) { - throw new CsvReaderRawTruffleException(stream, ex, location); - } - } - - @CompilerDirectives.TruffleBoundary - long getLong(ExpressionNode location) { - try { - try { - return jacksonParser.getLongValue(); - } catch (JsonProcessingException ex) { - String malformed = - jacksonParser.getText(); // shouldn't throw since we read already the token - throw new CsvParserRawTruffleException( - String.format("cannot parse '%s' as a long", malformed), this, stream, location); - } - } catch (IOException ex) { - throw new CsvReaderRawTruffleException(stream, ex, location); - } - } - - @CompilerDirectives.TruffleBoundary - Object getOptionLong(ExpressionNode location) { - try { - try { - String token = jacksonParser.getText(); - for (String nullToken : nulls) { - if (token.equals(nullToken)) { - return new EmptyOption(); - } + } + + @CompilerDirectives.TruffleBoundary + byte getByte(ExpressionNode location) { + try { + try { + return jacksonParser.getByteValue(); + } catch (JsonProcessingException ex) { + String malformed = + jacksonParser.getText(); // shouldn't throw since we read already the token + throw new CsvParserRawTruffleException( + String.format("cannot parse '%s' as a byte", malformed), this, stream, location); + } + } catch (IOException ex) { + throw new CsvReaderRawTruffleException(stream, ex, location); } - return new ObjectOption(jacksonParser.getLongValue()); - } catch (JsonProcessingException ex) { - throw new CsvParserRawTruffleException( - String.format("cannot parse '%s' as a long", jacksonParser.getText()), - this, - stream, - location); - } - } catch (IOException ex) { - throw new CsvReaderRawTruffleException(stream, ex, location); - } - } - - @CompilerDirectives.TruffleBoundary - float getFloat(ExpressionNode location) { - try { - try { - String token = jacksonParser.getText(); - for (String nanToken : nans) { - if (token.equals(nanToken)) { - return Float.NaN; - } + } + + @CompilerDirectives.TruffleBoundary + Object getOptionByte(ExpressionNode location) { + try { + try { + String token = jacksonParser.getText(); + for (String nullToken : nulls) { + if (token.equals(nullToken)) { + return new EmptyOption(); + } + } + return new ObjectOption(jacksonParser.getByteValue()); + } catch (JsonProcessingException ex) { + throw new CsvParserRawTruffleException( + String.format("cannot parse '%s' as a byte", jacksonParser.getText()), + this, + stream, + location); + } + } catch (IOException ex) { + throw new CsvReaderRawTruffleException(stream, ex, location); } - return jacksonParser.getFloatValue(); - } catch (JsonProcessingException ex) { - throw new CsvParserRawTruffleException( - String.format("cannot parse '%s' as a float", jacksonParser.getText()), - this, - stream, - location); - } - } catch (IOException ex) { - throw new CsvReaderRawTruffleException(stream, ex, location); - } - } - - @CompilerDirectives.TruffleBoundary - Object getOptionFloat(ExpressionNode location) { - try { - try { - String token = jacksonParser.getText(); - for (String nullToken : nulls) { - if (token.equals(nullToken)) { - return new EmptyOption(); - } + } + + @CompilerDirectives.TruffleBoundary + short getShort(ExpressionNode location) { + try { + try { + return jacksonParser.getShortValue(); + } catch (JsonProcessingException ex) { + String malformed = + jacksonParser.getText(); // shouldn't throw since we read already the token + throw new CsvParserRawTruffleException( + String.format("cannot parse '%s' as a short", malformed), this, stream, location); + } + } catch (IOException ex) { + throw new CsvReaderRawTruffleException(stream, ex, location); } - for (String nanToken : nans) { - if (token.equals(nanToken)) { - return new ObjectOption(Float.NaN); - } + } + + @CompilerDirectives.TruffleBoundary + Object getOptionShort(ExpressionNode location) { + try { + try { + String token = jacksonParser.getText(); + for (String nullToken : nulls) { + if (token.equals(nullToken)) { + return new EmptyOption(); + } + } + return new ObjectOption(jacksonParser.getShortValue()); + } catch (JsonProcessingException ex) { + throw new CsvParserRawTruffleException( + String.format("cannot parse '%s' as a short", jacksonParser.getText()), + this, + stream, + location); + } + } catch (IOException ex) { + throw new CsvReaderRawTruffleException(stream, ex, location); } - return new ObjectOption(jacksonParser.getFloatValue()); - } catch (JsonProcessingException ex) { - throw new CsvParserRawTruffleException( - String.format("cannot parse '%s' as a float", jacksonParser.getText()), - this, - stream, - location); - } - } catch (IOException ex) { - throw new CsvReaderRawTruffleException(stream, ex, location); - } - } - - @CompilerDirectives.TruffleBoundary - double getDouble(ExpressionNode location) { - try { - try { - String token = jacksonParser.getText(); - for (String nanToken : nans) { - if (token.equals(nanToken)) { - return Double.NaN; - } + } + + @CompilerDirectives.TruffleBoundary + int getInt(ExpressionNode location) { + try { + try { + return jacksonParser.getIntValue(); + } catch (JsonProcessingException ex) { + String malformed = + jacksonParser.getText(); // shouldn't throw since we read already the token + throw new CsvParserRawTruffleException( + String.format("cannot parse '%s' as an int", malformed), this, stream, location); + } + } catch (IOException ex) { + throw new CsvReaderRawTruffleException(stream, ex, location); } - return jacksonParser.getDoubleValue(); - } catch (JsonProcessingException ex) { - throw new CsvParserRawTruffleException( - String.format("cannot parse '%s' as a double", jacksonParser.getText()), - this, - stream, - location); - } - } catch (IOException ex) { - throw new CsvReaderRawTruffleException(stream, ex, location); - } - } - - @CompilerDirectives.TruffleBoundary - Object getOptionDouble(ExpressionNode location) { - try { - try { - String token = jacksonParser.getText(); - for (String nullToken : nulls) { - if (token.equals(nullToken)) { - return new EmptyOption(); - } + } + + @CompilerDirectives.TruffleBoundary + Object getOptionInt(ExpressionNode location) { + try { + try { + String token = jacksonParser.getText(); + for (String nullToken : nulls) { + if (token.equals(nullToken)) { + return new EmptyOption(); + } + } + return new ObjectOption(jacksonParser.getIntValue()); + } catch (JsonProcessingException ex) { + throw new CsvParserRawTruffleException( + String.format("cannot parse '%s' as an int", jacksonParser.getText()), + this, + stream, + location); + } + } catch (IOException ex) { + throw new CsvReaderRawTruffleException(stream, ex, location); } - for (String nanToken : nans) { - if (token.equals(nanToken)) { - return new ObjectOption(Double.NaN); - } + } + + @CompilerDirectives.TruffleBoundary + long getLong(ExpressionNode location) { + try { + try { + return jacksonParser.getLongValue(); + } catch (JsonProcessingException ex) { + String malformed = + jacksonParser.getText(); // shouldn't throw since we read already the token + throw new CsvParserRawTruffleException( + String.format("cannot parse '%s' as a long", malformed), this, stream, location); + } + } catch (IOException ex) { + throw new CsvReaderRawTruffleException(stream, ex, location); } - return new ObjectOption(jacksonParser.getDoubleValue()); - } catch (JsonProcessingException ex) { - throw new CsvParserRawTruffleException( - String.format("cannot parse '%s' as a double", jacksonParser.getText()), - this, - stream, - location); - } - } catch (IOException ex) { - throw new CsvReaderRawTruffleException(stream, ex, location); - } - } - - @CompilerDirectives.TruffleBoundary - BigDecimal getDecimal(ExpressionNode location) { - try { - - try { - return jacksonParser.getDecimalValue(); - } catch (JsonProcessingException | NumberFormatException ex) { - String malformed = - jacksonParser.getText(); // shouldn't throw since we read already the token - throw new CsvParserRawTruffleException( - String.format("cannot parse '%s' as a decimal", malformed), this, stream, location); - } - } catch (IOException ex) { - throw new CsvReaderRawTruffleException(stream, ex, location); - } - } - - @CompilerDirectives.TruffleBoundary - Object getOptionDecimal(ExpressionNode location) { - try { - try { - String malformed = jacksonParser.getText(); - for (String nullToken : nulls) { - if (malformed.equals(nullToken)) { - return new EmptyOption(); - } + } + + @CompilerDirectives.TruffleBoundary + Object getOptionLong(ExpressionNode location) { + try { + try { + String token = jacksonParser.getText(); + for (String nullToken : nulls) { + if (token.equals(nullToken)) { + return new EmptyOption(); + } + } + return new ObjectOption(jacksonParser.getLongValue()); + } catch (JsonProcessingException ex) { + throw new CsvParserRawTruffleException( + String.format("cannot parse '%s' as a long", jacksonParser.getText()), + this, + stream, + location); + } + } catch (IOException ex) { + throw new CsvReaderRawTruffleException(stream, ex, location); } - return new ObjectOption(jacksonParser.getDecimalValue()); - } catch (JsonProcessingException | NumberFormatException ex) { - throw new CsvParserRawTruffleException( - String.format("cannot parse '%s' as a decimal", jacksonParser.getText()), - this, - stream, - location); - } - } catch (IOException ex) { - throw new CsvReaderRawTruffleException(stream, ex, location); - } - } - - @CompilerDirectives.TruffleBoundary - boolean getBool(ExpressionNode location) { - try { - String text = jacksonParser.getText(); - String normalized = text.toLowerCase().strip(); - if (Objects.equals(normalized, "true")) { - return true; - } else if (Objects.equals(normalized, "false")) { - return false; - } else { - throw new CsvParserRawTruffleException( - String.format("cannot parse '%s' as a bool", text), this, stream, location); - } - } catch (IOException ex) { - throw new CsvReaderRawTruffleException(stream, ex, location); - } - } - - @CompilerDirectives.TruffleBoundary - Object getOptionBool(ExpressionNode location) { - try { - String text = jacksonParser.getText(); - String normalized = text.toLowerCase().strip(); - if (Objects.equals(normalized, "true")) { - return new ObjectOption(true); - } else if (Objects.equals(normalized, "false")) { - return new ObjectOption(false); - } else { - for (String nullToken : nulls) { - if (normalized.equals(nullToken)) { - return new EmptyOption(); - } + } + + @CompilerDirectives.TruffleBoundary + float getFloat(ExpressionNode location) { + try { + try { + String token = jacksonParser.getText(); + for (String nanToken : nans) { + if (token.equals(nanToken)) { + return Float.NaN; + } + } + return jacksonParser.getFloatValue(); + } catch (JsonProcessingException ex) { + throw new CsvParserRawTruffleException( + String.format("cannot parse '%s' as a float", jacksonParser.getText()), + this, + stream, + location); + } + } catch (IOException ex) { + throw new CsvReaderRawTruffleException(stream, ex, location); } - throw new CsvParserRawTruffleException( - String.format("cannot parse '%s' as a bool", text), this, stream, location); - } - } catch (IOException ex) { - throw new CsvReaderRawTruffleException(stream, ex, location); - } - } - - @CompilerDirectives.TruffleBoundary - String getString(ExpressionNode location) { - try { - return jacksonParser.getText(); - } catch (IOException ex) { - throw new CsvReaderRawTruffleException(stream, ex, location); - } - } - - @CompilerDirectives.TruffleBoundary - Object getOptionString(ExpressionNode location) { - try { - String token = jacksonParser.getText(); - for (String nullToken : nulls) { - if (token.equals(nullToken)) { - return new EmptyOption(); + } + + @CompilerDirectives.TruffleBoundary + Object getOptionFloat(ExpressionNode location) { + try { + try { + String token = jacksonParser.getText(); + for (String nullToken : nulls) { + if (token.equals(nullToken)) { + return new EmptyOption(); + } + } + for (String nanToken : nans) { + if (token.equals(nanToken)) { + return new ObjectOption(Float.NaN); + } + } + return new ObjectOption(jacksonParser.getFloatValue()); + } catch (JsonProcessingException ex) { + throw new CsvParserRawTruffleException( + String.format("cannot parse '%s' as a float", jacksonParser.getText()), + this, + stream, + location); + } + } catch (IOException ex) { + throw new CsvReaderRawTruffleException(stream, ex, location); } - } - return new ObjectOption(token); - } catch (IOException ex) { - throw new CsvReaderRawTruffleException(stream, ex, location); - } - } - - @CompilerDirectives.TruffleBoundary - public DateObject getDate(ExpressionNode location) { - try { - String token = jacksonParser.getText(); - return new DateObject(LocalDate.parse(token, dateFormatter)); - } catch (DateTimeParseException ex) { - throw new CsvParserRawTruffleException( - String.format( - "string '%s' does not match date template '%s'", ex.getParsedString(), dateFormat), - this, - stream, - location); - } catch (IOException ex) { - throw new CsvReaderRawTruffleException(stream, ex, location); - } - } - - @CompilerDirectives.TruffleBoundary - Object getOptionDate(ExpressionNode location) { - try { - String token = jacksonParser.getText(); - for (String nullToken : nulls) { - if (token.equals(nullToken)) { - return new EmptyOption(); + } + + @CompilerDirectives.TruffleBoundary + double getDouble(ExpressionNode location) { + try { + try { + String token = jacksonParser.getText(); + for (String nanToken : nans) { + if (token.equals(nanToken)) { + return Double.NaN; + } + } + return jacksonParser.getDoubleValue(); + } catch (JsonProcessingException ex) { + throw new CsvParserRawTruffleException( + String.format("cannot parse '%s' as a double", jacksonParser.getText()), + this, + stream, + location); + } + } catch (IOException ex) { + throw new CsvReaderRawTruffleException(stream, ex, location); + } + } + + @CompilerDirectives.TruffleBoundary + Object getOptionDouble(ExpressionNode location) { + try { + try { + String token = jacksonParser.getText(); + for (String nullToken : nulls) { + if (token.equals(nullToken)) { + return new EmptyOption(); + } + } + for (String nanToken : nans) { + if (token.equals(nanToken)) { + return new ObjectOption(Double.NaN); + } + } + return new ObjectOption(jacksonParser.getDoubleValue()); + } catch (JsonProcessingException ex) { + throw new CsvParserRawTruffleException( + String.format("cannot parse '%s' as a double", jacksonParser.getText()), + this, + stream, + location); + } + } catch (IOException ex) { + throw new CsvReaderRawTruffleException(stream, ex, location); + } + } + + @CompilerDirectives.TruffleBoundary + BigDecimal getDecimal(ExpressionNode location) { + try { + + try { + return jacksonParser.getDecimalValue(); + } catch (JsonProcessingException | NumberFormatException ex) { + String malformed = + jacksonParser.getText(); // shouldn't throw since we read already the token + throw new CsvParserRawTruffleException( + String.format("cannot parse '%s' as a decimal", malformed), this, stream, location); + } + } catch (IOException ex) { + throw new CsvReaderRawTruffleException(stream, ex, location); + } + } + + @CompilerDirectives.TruffleBoundary + Object getOptionDecimal(ExpressionNode location) { + try { + try { + String malformed = jacksonParser.getText(); + for (String nullToken : nulls) { + if (malformed.equals(nullToken)) { + return new EmptyOption(); + } + } + return new ObjectOption(jacksonParser.getDecimalValue()); + } catch (JsonProcessingException | NumberFormatException ex) { + throw new CsvParserRawTruffleException( + String.format("cannot parse '%s' as a decimal", jacksonParser.getText()), + this, + stream, + location); + } + } catch (IOException ex) { + throw new CsvReaderRawTruffleException(stream, ex, location); } - } - return new ObjectOption(new DateObject(LocalDate.parse(token, dateFormatter))); - } catch (DateTimeParseException ex) { - throw new CsvParserRawTruffleException( - String.format( - "string '%s' does not match date template '%s'", ex.getParsedString(), dateFormat), - this, - stream, - location); - } catch (IOException ex) { - throw new CsvReaderRawTruffleException(stream, ex, location); - } - } - - @CompilerDirectives.TruffleBoundary - public TimeObject getTime(ExpressionNode location) { - try { - String token = jacksonParser.getText(); - return new TimeObject(LocalTime.parse(token, timeFormatter)); - } catch (DateTimeParseException ex) { - throw new CsvParserRawTruffleException( - String.format( - "string '%s' does not match time template '%s'", ex.getParsedString(), timeFormat), - this, - stream, - location); - } catch (IOException ex) { - throw new CsvReaderRawTruffleException(stream, ex, location); - } - } - - @CompilerDirectives.TruffleBoundary - Object getOptionTime(ExpressionNode location) { - try { - String token = jacksonParser.getText(); - for (String nullToken : nulls) { - if (token.equals(nullToken)) { - return new EmptyOption(); + } + + @CompilerDirectives.TruffleBoundary + boolean getBool(ExpressionNode location) { + try { + String text = jacksonParser.getText(); + String normalized = text.toLowerCase().strip(); + if (Objects.equals(normalized, "true")) { + return true; + } else if (Objects.equals(normalized, "false")) { + return false; + } else { + throw new CsvParserRawTruffleException( + String.format("cannot parse '%s' as a bool", text), this, stream, location); + } + } catch (IOException ex) { + throw new CsvReaderRawTruffleException(stream, ex, location); + } + } + + @CompilerDirectives.TruffleBoundary + Object getOptionBool(ExpressionNode location) { + try { + String text = jacksonParser.getText(); + String normalized = text.toLowerCase().strip(); + if (Objects.equals(normalized, "true")) { + return new ObjectOption(true); + } else if (Objects.equals(normalized, "false")) { + return new ObjectOption(false); + } else { + for (String nullToken : nulls) { + if (normalized.equals(nullToken)) { + return new EmptyOption(); + } + } + throw new CsvParserRawTruffleException( + String.format("cannot parse '%s' as a bool", text), this, stream, location); + } + } catch (IOException ex) { + throw new CsvReaderRawTruffleException(stream, ex, location); } - } - return new ObjectOption(new TimeObject(LocalTime.parse(token, timeFormatter))); - } catch (DateTimeParseException ex) { - throw new CsvParserRawTruffleException( - String.format( - "string '%s' does not match time template '%s'", ex.getParsedString(), timeFormat), - this, - stream, - location); - } catch (IOException ex) { - throw new CsvReaderRawTruffleException(stream, ex, location); - } - } - - @CompilerDirectives.TruffleBoundary - public TimestampObject getTimestamp(ExpressionNode location) { - try { - String token = jacksonParser.getText(); - return new TimestampObject(LocalDateTime.parse(token, timestampFormatter)); - } catch (DateTimeParseException ex) { - throw new CsvParserRawTruffleException( - String.format( - "string '%s' does not match timestamp template '%s'", - ex.getParsedString(), timestampFormat), - this, - stream, - location); - } catch (IOException ex) { - throw new CsvReaderRawTruffleException(stream, ex, location); - } - } - - @CompilerDirectives.TruffleBoundary - Object getOptionTimestamp(ExpressionNode location) { - try { - String token = jacksonParser.getText(); - for (String nullToken : nulls) { - if (token.equals(nullToken)) { - return new EmptyOption(); + } + + @CompilerDirectives.TruffleBoundary + String getString(ExpressionNode location) { + try { + return jacksonParser.getText(); + } catch (IOException ex) { + throw new CsvReaderRawTruffleException(stream, ex, location); + } + } + + @CompilerDirectives.TruffleBoundary + Object getOptionString(ExpressionNode location) { + try { + String token = jacksonParser.getText(); + for (String nullToken : nulls) { + if (token.equals(nullToken)) { + return new EmptyOption(); + } + } + return new ObjectOption(token); + } catch (IOException ex) { + throw new CsvReaderRawTruffleException(stream, ex, location); } - } - return new ObjectOption(new TimestampObject(LocalDateTime.parse(token, timestampFormatter))); - } catch (DateTimeParseException ex) { - throw new CsvParserRawTruffleException( - String.format( - "string '%s' does not match timestamp template '%s'", - ex.getParsedString(), timeFormat), - this, - stream, - location); - } catch (IOException ex) { - throw new CsvReaderRawTruffleException(stream, ex, location); - } - } + } + + @CompilerDirectives.TruffleBoundary + public DateObject getDate(ExpressionNode location) { + try { + String token = jacksonParser.getText(); + return new DateObject(LocalDate.parse(token, dateFormatter)); + } catch (DateTimeParseException ex) { + throw new CsvParserRawTruffleException( + String.format( + "string '%s' does not match date template '%s'", ex.getParsedString(), dateFormat), + this, + stream, + location); + } catch (IOException ex) { + throw new CsvReaderRawTruffleException(stream, ex, location); + } + } + + @CompilerDirectives.TruffleBoundary + Object getOptionDate(ExpressionNode location) { + try { + String token = jacksonParser.getText(); + for (String nullToken : nulls) { + if (token.equals(nullToken)) { + return new EmptyOption(); + } + } + return new ObjectOption(new DateObject(LocalDate.parse(token, dateFormatter))); + } catch (DateTimeParseException ex) { + throw new CsvParserRawTruffleException( + String.format( + "string '%s' does not match date template '%s'", ex.getParsedString(), dateFormat), + this, + stream, + location); + } catch (IOException ex) { + throw new CsvReaderRawTruffleException(stream, ex, location); + } + } + + @CompilerDirectives.TruffleBoundary + public TimeObject getTime(ExpressionNode location) { + try { + String token = jacksonParser.getText(); + return new TimeObject(LocalTime.parse(token, timeFormatter)); + } catch (DateTimeParseException ex) { + throw new CsvParserRawTruffleException( + String.format( + "string '%s' does not match time template '%s'", ex.getParsedString(), timeFormat), + this, + stream, + location); + } catch (IOException ex) { + throw new CsvReaderRawTruffleException(stream, ex, location); + } + } + + @CompilerDirectives.TruffleBoundary + Object getOptionTime(ExpressionNode location) { + try { + String token = jacksonParser.getText(); + for (String nullToken : nulls) { + if (token.equals(nullToken)) { + return new EmptyOption(); + } + } + return new ObjectOption(new TimeObject(LocalTime.parse(token, timeFormatter))); + } catch (DateTimeParseException ex) { + throw new CsvParserRawTruffleException( + String.format( + "string '%s' does not match time template '%s'", ex.getParsedString(), timeFormat), + this, + stream, + location); + } catch (IOException ex) { + throw new CsvReaderRawTruffleException(stream, ex, location); + } + } + + @CompilerDirectives.TruffleBoundary + public TimestampObject getTimestamp(ExpressionNode location) { + try { + String token = jacksonParser.getText(); + return new TimestampObject(LocalDateTime.parse(token, timestampFormatter)); + } catch (DateTimeParseException ex) { + throw new CsvParserRawTruffleException( + String.format( + "string '%s' does not match timestamp template '%s'", + ex.getParsedString(), timestampFormat), + this, + stream, + location); + } catch (IOException ex) { + throw new CsvReaderRawTruffleException(stream, ex, location); + } + } + + @CompilerDirectives.TruffleBoundary + Object getOptionTimestamp(ExpressionNode location) { + try { + String token = jacksonParser.getText(); + for (String nullToken : nulls) { + if (token.equals(nullToken)) { + return new EmptyOption(); + } + } + return new ObjectOption(new TimestampObject(LocalDateTime.parse(token, timestampFormatter))); + } catch (DateTimeParseException ex) { + throw new CsvParserRawTruffleException( + String.format( + "string '%s' does not match timestamp template '%s'", + ex.getParsedString(), timeFormat), + this, + stream, + location); + } catch (IOException ex) { + throw new CsvReaderRawTruffleException(stream, ex, location); + } + } } diff --git a/raw-runtime-rql2-truffle/src/main/java/raw/runtime/truffle/ast/io/csv/reader/parser/RawTruffleCsvParserSettings.java b/raw-runtime-rql2-truffle/src/main/java/raw/runtime/truffle/ast/io/csv/reader/parser/RawTruffleCsvParserSettings.java index c97c5cd53..10f6c85f0 100644 --- a/raw-runtime-rql2-truffle/src/main/java/raw/runtime/truffle/ast/io/csv/reader/parser/RawTruffleCsvParserSettings.java +++ b/raw-runtime-rql2-truffle/src/main/java/raw/runtime/truffle/ast/io/csv/reader/parser/RawTruffleCsvParserSettings.java @@ -17,6 +17,7 @@ public class RawTruffleCsvParserSettings { public final char delimiter; public final boolean useQuote; public final char quoteChar; + public final char escapeChar; public final int headerLines; public final String[] nulls; public final String[] nans; @@ -28,6 +29,7 @@ public RawTruffleCsvParserSettings( char delimiter, boolean useQuote, char quoteChar, + char escapeChar, int headerLines, String[] nulls, String[] nans, @@ -37,6 +39,7 @@ public RawTruffleCsvParserSettings( this.delimiter = delimiter; this.useQuote = useQuote; this.quoteChar = quoteChar; + this.escapeChar = escapeChar; this.headerLines = headerLines; this.nulls = nulls; this.nans = nans;