| //===-- runtime/io-api.cpp --------------------------------------*- C++ -*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| // Implements the I/O statement API |
| |
| #include "io-api.h" |
| #include "edit-input.h" |
| #include "edit-output.h" |
| #include "environment.h" |
| #include "format.h" |
| #include "io-stmt.h" |
| #include "memory.h" |
| #include "terminator.h" |
| #include "tools.h" |
| #include "unit.h" |
| #include <cstdlib> |
| #include <memory> |
| |
| namespace Fortran::runtime::io { |
| |
| template <Direction DIR> |
| Cookie BeginInternalArrayListIO(const Descriptor &descriptor, |
| void ** /*scratchArea*/, std::size_t /*scratchBytes*/, |
| const char *sourceFile, int sourceLine) { |
| Terminator oom{sourceFile, sourceLine}; |
| return &New<InternalListIoStatementState<DIR>>{}( |
| oom, descriptor, sourceFile, sourceLine) |
| .ioStatementState(); |
| } |
| |
| Cookie IONAME(BeginInternalArrayListOutput)(const Descriptor &descriptor, |
| void **scratchArea, std::size_t scratchBytes, const char *sourceFile, |
| int sourceLine) { |
| return BeginInternalArrayListIO<Direction::Output>( |
| descriptor, scratchArea, scratchBytes, sourceFile, sourceLine); |
| } |
| |
| Cookie IONAME(BeginInternalArrayListInput)(const Descriptor &descriptor, |
| void **scratchArea, std::size_t scratchBytes, const char *sourceFile, |
| int sourceLine) { |
| return BeginInternalArrayListIO<Direction::Input>( |
| descriptor, scratchArea, scratchBytes, sourceFile, sourceLine); |
| } |
| |
| template <Direction DIR> |
| Cookie BeginInternalArrayFormattedIO(const Descriptor &descriptor, |
| const char *format, std::size_t formatLength, void ** /*scratchArea*/, |
| std::size_t /*scratchBytes*/, const char *sourceFile, int sourceLine) { |
| Terminator oom{sourceFile, sourceLine}; |
| return &New<InternalFormattedIoStatementState<DIR>>{}( |
| oom, descriptor, format, formatLength, sourceFile, sourceLine) |
| .ioStatementState(); |
| } |
| |
| Cookie IONAME(BeginInternalArrayFormattedOutput)(const Descriptor &descriptor, |
| const char *format, std::size_t formatLength, void **scratchArea, |
| std::size_t scratchBytes, const char *sourceFile, int sourceLine) { |
| return BeginInternalArrayFormattedIO<Direction::Output>(descriptor, format, |
| formatLength, scratchArea, scratchBytes, sourceFile, sourceLine); |
| } |
| |
| Cookie IONAME(BeginInternalArrayFormattedInput)(const Descriptor &descriptor, |
| const char *format, std::size_t formatLength, void **scratchArea, |
| std::size_t scratchBytes, const char *sourceFile, int sourceLine) { |
| return BeginInternalArrayFormattedIO<Direction::Input>(descriptor, format, |
| formatLength, scratchArea, scratchBytes, sourceFile, sourceLine); |
| } |
| |
| template <Direction DIR> |
| Cookie BeginInternalFormattedIO( |
| std::conditional_t<DIR == Direction::Input, const char, char> *internal, |
| std::size_t internalLength, const char *format, std::size_t formatLength, |
| void ** /*scratchArea*/, std::size_t /*scratchBytes*/, |
| const char *sourceFile, int sourceLine) { |
| Terminator oom{sourceFile, sourceLine}; |
| return &New<InternalFormattedIoStatementState<DIR>>{}(oom, internal, |
| internalLength, format, formatLength, sourceFile, sourceLine) |
| .ioStatementState(); |
| } |
| |
| Cookie IONAME(BeginInternalFormattedOutput)(char *internal, |
| std::size_t internalLength, const char *format, std::size_t formatLength, |
| void **scratchArea, std::size_t scratchBytes, const char *sourceFile, |
| int sourceLine) { |
| Terminator oom{sourceFile, sourceLine}; |
| return BeginInternalFormattedIO<Direction::Output>(internal, internalLength, |
| format, formatLength, scratchArea, scratchBytes, sourceFile, sourceLine); |
| } |
| |
| Cookie IONAME(BeginInternalFormattedInput)(const char *internal, |
| std::size_t internalLength, const char *format, std::size_t formatLength, |
| void **scratchArea, std::size_t scratchBytes, const char *sourceFile, |
| int sourceLine) { |
| Terminator oom{sourceFile, sourceLine}; |
| return BeginInternalFormattedIO<Direction::Input>(internal, internalLength, |
| format, formatLength, scratchArea, scratchBytes, sourceFile, sourceLine); |
| } |
| |
| template <Direction DIR> |
| Cookie BeginExternalListIO( |
| ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { |
| Terminator terminator{sourceFile, sourceLine}; |
| if (unitNumber == DefaultUnit) { |
| unitNumber = DIR == Direction::Input ? 5 : 6; |
| } |
| ExternalFileUnit &unit{ |
| ExternalFileUnit::LookUpOrCrash(unitNumber, terminator)}; |
| if (unit.access == Access::Direct) { |
| terminator.Crash("List-directed I/O attempted on direct access file"); |
| return nullptr; |
| } |
| if (unit.isUnformatted) { |
| terminator.Crash("List-directed I/O attempted on unformatted file"); |
| return nullptr; |
| } |
| IoStatementState &io{unit.BeginIoStatement<ExternalListIoStatementState<DIR>>( |
| unit, sourceFile, sourceLine)}; |
| if constexpr (DIR == Direction::Input) { |
| io.AdvanceRecord(); |
| } |
| return &io; |
| } |
| |
| Cookie IONAME(BeginExternalListOutput)( |
| ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { |
| return BeginExternalListIO<Direction::Output>( |
| unitNumber, sourceFile, sourceLine); |
| } |
| |
| Cookie IONAME(BeginExternalListInput)( |
| ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { |
| return BeginExternalListIO<Direction::Input>( |
| unitNumber, sourceFile, sourceLine); |
| } |
| |
| template <Direction DIR> |
| Cookie BeginExternalFormattedIO(const char *format, std::size_t formatLength, |
| ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { |
| Terminator terminator{sourceFile, sourceLine}; |
| if (unitNumber == DefaultUnit) { |
| unitNumber = DIR == Direction::Input ? 5 : 6; |
| } |
| ExternalFileUnit &unit{ |
| ExternalFileUnit::LookUpOrCrash(unitNumber, terminator)}; |
| if (unit.isUnformatted) { |
| terminator.Crash("Formatted I/O attempted on unformatted file"); |
| return nullptr; |
| } |
| IoStatementState &io{ |
| unit.BeginIoStatement<ExternalFormattedIoStatementState<DIR>>( |
| unit, format, formatLength, sourceFile, sourceLine)}; |
| if constexpr (DIR == Direction::Input) { |
| io.AdvanceRecord(); |
| } |
| return &io; |
| } |
| |
| Cookie IONAME(BeginExternalFormattedOutput)(const char *format, |
| std::size_t formatLength, ExternalUnit unitNumber, const char *sourceFile, |
| int sourceLine) { |
| return BeginExternalFormattedIO<Direction::Output>( |
| format, formatLength, unitNumber, sourceFile, sourceLine); |
| } |
| |
| Cookie IONAME(BeginExternalFormattedInput)(const char *format, |
| std::size_t formatLength, ExternalUnit unitNumber, const char *sourceFile, |
| int sourceLine) { |
| return BeginExternalFormattedIO<Direction::Input>( |
| format, formatLength, unitNumber, sourceFile, sourceLine); |
| } |
| |
| template <Direction DIR> |
| Cookie BeginUnformattedIO( |
| ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { |
| Terminator terminator{sourceFile, sourceLine}; |
| ExternalFileUnit &file{ |
| ExternalFileUnit::LookUpOrCrash(unitNumber, terminator)}; |
| if (!file.isUnformatted) { |
| terminator.Crash("Unformatted output attempted on formatted file"); |
| } |
| IoStatementState &io{file.BeginIoStatement<UnformattedIoStatementState<DIR>>( |
| file, sourceFile, sourceLine)}; |
| if constexpr (DIR == Direction::Input) { |
| io.AdvanceRecord(); |
| } else { |
| if (file.access == Access::Sequential && !file.recordLength.has_value()) { |
| // Create space for (sub)record header to be completed by |
| // UnformattedIoStatementState<Direction::Output>::EndIoStatement() |
| io.Emit("\0\0\0\0", 4); // placeholder for record length header |
| } |
| } |
| return &io; |
| } |
| |
| Cookie IONAME(BeginUnformattedOutput)( |
| ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { |
| return BeginUnformattedIO<Direction::Output>( |
| unitNumber, sourceFile, sourceLine); |
| } |
| |
| Cookie IONAME(BeginUnformattedInput)( |
| ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { |
| return BeginUnformattedIO<Direction::Input>( |
| unitNumber, sourceFile, sourceLine); |
| } |
| |
| Cookie IONAME(BeginOpenUnit)( // OPEN(without NEWUNIT=) |
| ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { |
| bool wasExtant{false}; |
| Terminator terminator{sourceFile, sourceLine}; |
| ExternalFileUnit &unit{ |
| ExternalFileUnit::LookUpOrCreate(unitNumber, terminator, &wasExtant)}; |
| return &unit.BeginIoStatement<OpenStatementState>( |
| unit, wasExtant, sourceFile, sourceLine); |
| } |
| |
| Cookie IONAME(BeginOpenNewUnit)( // OPEN(NEWUNIT=j) |
| const char *sourceFile, int sourceLine) { |
| Terminator terminator{sourceFile, sourceLine}; |
| return IONAME(BeginOpenUnit)( |
| ExternalFileUnit::NewUnit(terminator), sourceFile, sourceLine); |
| } |
| |
| Cookie IONAME(BeginClose)( |
| ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { |
| if (ExternalFileUnit * unit{ExternalFileUnit::LookUpForClose(unitNumber)}) { |
| return &unit->BeginIoStatement<CloseStatementState>( |
| *unit, sourceFile, sourceLine); |
| } else { |
| // CLOSE(UNIT=bad unit) is just a no-op |
| Terminator oom{sourceFile, sourceLine}; |
| return &New<NoopCloseStatementState>{}(oom, sourceFile, sourceLine) |
| .ioStatementState(); |
| } |
| } |
| |
| // Control list items |
| |
| void IONAME(EnableHandlers)(Cookie cookie, bool hasIoStat, bool hasErr, |
| bool hasEnd, bool hasEor, bool hasIoMsg) { |
| IoErrorHandler &handler{cookie->GetIoErrorHandler()}; |
| if (hasIoStat) { |
| handler.HasIoStat(); |
| } |
| if (hasErr) { |
| handler.HasErrLabel(); |
| } |
| if (hasEnd) { |
| handler.HasEndLabel(); |
| } |
| if (hasEor) { |
| handler.HasEorLabel(); |
| } |
| if (hasIoMsg) { |
| handler.HasIoMsg(); |
| } |
| } |
| |
| static bool YesOrNo(const char *keyword, std::size_t length, const char *what, |
| IoErrorHandler &handler) { |
| static const char *keywords[]{"YES", "NO", nullptr}; |
| switch (IdentifyValue(keyword, length, keywords)) { |
| case 0: |
| return true; |
| case 1: |
| return false; |
| default: |
| handler.SignalError(IostatErrorInKeyword, "Invalid %s='%.*s'", what, |
| static_cast<int>(length), keyword); |
| return false; |
| } |
| } |
| |
| bool IONAME(SetAdvance)( |
| Cookie cookie, const char *keyword, std::size_t length) { |
| IoStatementState &io{*cookie}; |
| ConnectionState &connection{io.GetConnectionState()}; |
| connection.nonAdvancing = |
| !YesOrNo(keyword, length, "ADVANCE", io.GetIoErrorHandler()); |
| if (connection.nonAdvancing && connection.access == Access::Direct) { |
| io.GetIoErrorHandler().SignalError( |
| "Non-advancing I/O attempted on direct access file"); |
| } |
| return true; |
| } |
| |
| bool IONAME(SetBlank)(Cookie cookie, const char *keyword, std::size_t length) { |
| IoStatementState &io{*cookie}; |
| ConnectionState &connection{io.GetConnectionState()}; |
| static const char *keywords[]{"NULL", "ZERO", nullptr}; |
| switch (IdentifyValue(keyword, length, keywords)) { |
| case 0: |
| connection.modes.editingFlags &= ~blankZero; |
| return true; |
| case 1: |
| connection.modes.editingFlags |= blankZero; |
| return true; |
| default: |
| io.GetIoErrorHandler().SignalError(IostatErrorInKeyword, |
| "Invalid BLANK='%.*s'", static_cast<int>(length), keyword); |
| return false; |
| } |
| } |
| |
| bool IONAME(SetDecimal)( |
| Cookie cookie, const char *keyword, std::size_t length) { |
| IoStatementState &io{*cookie}; |
| ConnectionState &connection{io.GetConnectionState()}; |
| static const char *keywords[]{"COMMA", "POINT", nullptr}; |
| switch (IdentifyValue(keyword, length, keywords)) { |
| case 0: |
| connection.modes.editingFlags |= decimalComma; |
| return true; |
| case 1: |
| connection.modes.editingFlags &= ~decimalComma; |
| return true; |
| default: |
| io.GetIoErrorHandler().SignalError(IostatErrorInKeyword, |
| "Invalid DECIMAL='%.*s'", static_cast<int>(length), keyword); |
| return false; |
| } |
| } |
| |
| bool IONAME(SetDelim)(Cookie cookie, const char *keyword, std::size_t length) { |
| IoStatementState &io{*cookie}; |
| ConnectionState &connection{io.GetConnectionState()}; |
| static const char *keywords[]{"APOSTROPHE", "QUOTE", "NONE", nullptr}; |
| switch (IdentifyValue(keyword, length, keywords)) { |
| case 0: |
| connection.modes.delim = '\''; |
| return true; |
| case 1: |
| connection.modes.delim = '"'; |
| return true; |
| case 2: |
| connection.modes.delim = '\0'; |
| return true; |
| default: |
| io.GetIoErrorHandler().SignalError(IostatErrorInKeyword, |
| "Invalid DELIM='%.*s'", static_cast<int>(length), keyword); |
| return false; |
| } |
| } |
| |
| bool IONAME(SetPad)(Cookie cookie, const char *keyword, std::size_t length) { |
| IoStatementState &io{*cookie}; |
| ConnectionState &connection{io.GetConnectionState()}; |
| connection.modes.pad = |
| YesOrNo(keyword, length, "PAD", io.GetIoErrorHandler()); |
| return true; |
| } |
| |
| bool IONAME(SetPos)(Cookie cookie, std::int64_t pos) { |
| IoStatementState &io{*cookie}; |
| ConnectionState &connection{io.GetConnectionState()}; |
| if (connection.access != Access::Stream) { |
| io.GetIoErrorHandler().SignalError( |
| "REC= may not appear unless ACCESS='STREAM'"); |
| return false; |
| } |
| if (pos < 1) { |
| io.GetIoErrorHandler().SignalError( |
| "POS=%zd is invalid", static_cast<std::intmax_t>(pos)); |
| return false; |
| } |
| if (auto *unit{io.GetExternalFileUnit()}) { |
| unit->SetPosition(pos); |
| return true; |
| } |
| io.GetIoErrorHandler().Crash("SetPos() on internal unit"); |
| return false; |
| } |
| |
| bool IONAME(SetRec)(Cookie cookie, std::int64_t rec) { |
| IoStatementState &io{*cookie}; |
| ConnectionState &connection{io.GetConnectionState()}; |
| if (connection.access != Access::Direct) { |
| io.GetIoErrorHandler().SignalError( |
| "REC= may not appear unless ACCESS='DIRECT'"); |
| return false; |
| } |
| if (!connection.recordLength) { |
| io.GetIoErrorHandler().SignalError("RECL= was not specified"); |
| return false; |
| } |
| if (rec < 1) { |
| io.GetIoErrorHandler().SignalError( |
| "REC=%zd is invalid", static_cast<std::intmax_t>(rec)); |
| return false; |
| } |
| connection.currentRecordNumber = rec; |
| if (auto *unit{io.GetExternalFileUnit()}) { |
| unit->SetPosition(rec * *connection.recordLength); |
| } |
| return true; |
| } |
| |
| bool IONAME(SetRound)(Cookie cookie, const char *keyword, std::size_t length) { |
| IoStatementState &io{*cookie}; |
| ConnectionState &connection{io.GetConnectionState()}; |
| static const char *keywords[]{"UP", "DOWN", "ZERO", "NEAREST", "COMPATIBLE", |
| "PROCESSOR_DEFINED", nullptr}; |
| switch (IdentifyValue(keyword, length, keywords)) { |
| case 0: |
| connection.modes.round = decimal::RoundUp; |
| return true; |
| case 1: |
| connection.modes.round = decimal::RoundDown; |
| return true; |
| case 2: |
| connection.modes.round = decimal::RoundToZero; |
| return true; |
| case 3: |
| connection.modes.round = decimal::RoundNearest; |
| return true; |
| case 4: |
| connection.modes.round = decimal::RoundCompatible; |
| return true; |
| case 5: |
| connection.modes.round = executionEnvironment.defaultOutputRoundingMode; |
| return true; |
| default: |
| io.GetIoErrorHandler().SignalError(IostatErrorInKeyword, |
| "Invalid ROUND='%.*s'", static_cast<int>(length), keyword); |
| return false; |
| } |
| } |
| |
| bool IONAME(SetSign)(Cookie cookie, const char *keyword, std::size_t length) { |
| IoStatementState &io{*cookie}; |
| ConnectionState &connection{io.GetConnectionState()}; |
| static const char *keywords[]{"PLUS", "YES", "PROCESSOR_DEFINED", nullptr}; |
| switch (IdentifyValue(keyword, length, keywords)) { |
| case 0: |
| connection.modes.editingFlags |= signPlus; |
| return true; |
| case 1: |
| case 2: // processor default is SS |
| connection.modes.editingFlags &= ~signPlus; |
| return true; |
| default: |
| io.GetIoErrorHandler().SignalError(IostatErrorInKeyword, |
| "Invalid SIGN='%.*s'", static_cast<int>(length), keyword); |
| return false; |
| } |
| } |
| |
| bool IONAME(SetAccess)(Cookie cookie, const char *keyword, std::size_t length) { |
| IoStatementState &io{*cookie}; |
| auto *open{io.get_if<OpenStatementState>()}; |
| if (!open) { |
| io.GetIoErrorHandler().Crash( |
| "SetAccess() called when not in an OPEN statement"); |
| } |
| ConnectionState &connection{open->GetConnectionState()}; |
| Access access{connection.access}; |
| static const char *keywords[]{"SEQUENTIAL", "DIRECT", "STREAM", nullptr}; |
| switch (IdentifyValue(keyword, length, keywords)) { |
| case 0: |
| access = Access::Sequential; |
| break; |
| case 1: |
| access = Access::Direct; |
| break; |
| case 2: |
| access = Access::Stream; |
| break; |
| default: |
| open->SignalError(IostatErrorInKeyword, "Invalid ACCESS='%.*s'", |
| static_cast<int>(length), keyword); |
| } |
| if (access != connection.access) { |
| if (open->wasExtant()) { |
| open->SignalError("ACCESS= may not be changed on an open unit"); |
| } |
| connection.access = access; |
| } |
| return true; |
| } |
| |
| bool IONAME(SetAction)(Cookie cookie, const char *keyword, std::size_t length) { |
| IoStatementState &io{*cookie}; |
| auto *open{io.get_if<OpenStatementState>()}; |
| if (!open) { |
| io.GetIoErrorHandler().Crash( |
| "SetAction() called when not in an OPEN statement"); |
| } |
| bool mayRead{true}; |
| bool mayWrite{true}; |
| static const char *keywords[]{"READ", "WRITE", "READWRITE", nullptr}; |
| switch (IdentifyValue(keyword, length, keywords)) { |
| case 0: |
| mayWrite = false; |
| break; |
| case 1: |
| mayRead = false; |
| break; |
| case 2: |
| break; |
| default: |
| open->SignalError(IostatErrorInKeyword, "Invalid ACTION='%.*s'", |
| static_cast<int>(length), keyword); |
| return false; |
| } |
| if (mayRead != open->unit().mayRead() || |
| mayWrite != open->unit().mayWrite()) { |
| if (open->wasExtant()) { |
| open->SignalError("ACTION= may not be changed on an open unit"); |
| } |
| open->unit().set_mayRead(mayRead); |
| open->unit().set_mayWrite(mayWrite); |
| } |
| return true; |
| } |
| |
| bool IONAME(SetAsynchronous)( |
| Cookie cookie, const char *keyword, std::size_t length) { |
| IoStatementState &io{*cookie}; |
| auto *open{io.get_if<OpenStatementState>()}; |
| if (!open) { |
| io.GetIoErrorHandler().Crash( |
| "SetAsynchronous() called when not in an OPEN statement"); |
| } |
| static const char *keywords[]{"YES", "NO", nullptr}; |
| switch (IdentifyValue(keyword, length, keywords)) { |
| case 0: |
| open->unit().set_mayAsynchronous(true); |
| return true; |
| case 1: |
| open->unit().set_mayAsynchronous(false); |
| return true; |
| default: |
| open->SignalError(IostatErrorInKeyword, "Invalid ASYNCHRONOUS='%.*s'", |
| static_cast<int>(length), keyword); |
| return false; |
| } |
| } |
| |
| bool IONAME(SetEncoding)( |
| Cookie cookie, const char *keyword, std::size_t length) { |
| IoStatementState &io{*cookie}; |
| auto *open{io.get_if<OpenStatementState>()}; |
| if (!open) { |
| io.GetIoErrorHandler().Crash( |
| "SetEncoding() called when not in an OPEN statement"); |
| } |
| bool isUTF8{false}; |
| static const char *keywords[]{"UTF-8", "DEFAULT", nullptr}; |
| switch (IdentifyValue(keyword, length, keywords)) { |
| case 0: |
| isUTF8 = true; |
| break; |
| case 1: |
| isUTF8 = false; |
| break; |
| default: |
| open->SignalError(IostatErrorInKeyword, "Invalid ENCODING='%.*s'", |
| static_cast<int>(length), keyword); |
| } |
| if (isUTF8 != open->unit().isUTF8) { |
| if (open->wasExtant()) { |
| open->SignalError("ENCODING= may not be changed on an open unit"); |
| } |
| open->unit().isUTF8 = isUTF8; |
| } |
| return true; |
| } |
| |
| bool IONAME(SetForm)(Cookie cookie, const char *keyword, std::size_t length) { |
| IoStatementState &io{*cookie}; |
| auto *open{io.get_if<OpenStatementState>()}; |
| if (!open) { |
| io.GetIoErrorHandler().Crash( |
| "SetEncoding() called when not in an OPEN statement"); |
| } |
| bool isUnformatted{false}; |
| static const char *keywords[]{"FORMATTED", "UNFORMATTED", nullptr}; |
| switch (IdentifyValue(keyword, length, keywords)) { |
| case 0: |
| isUnformatted = false; |
| break; |
| case 1: |
| isUnformatted = true; |
| break; |
| default: |
| open->SignalError(IostatErrorInKeyword, "Invalid FORM='%.*s'", |
| static_cast<int>(length), keyword); |
| } |
| if (isUnformatted != open->unit().isUnformatted) { |
| if (open->wasExtant()) { |
| open->SignalError("FORM= may not be changed on an open unit"); |
| } |
| open->unit().isUnformatted = isUnformatted; |
| } |
| return true; |
| } |
| |
| bool IONAME(SetPosition)( |
| Cookie cookie, const char *keyword, std::size_t length) { |
| IoStatementState &io{*cookie}; |
| auto *open{io.get_if<OpenStatementState>()}; |
| if (!open) { |
| io.GetIoErrorHandler().Crash( |
| "SetPosition() called when not in an OPEN statement"); |
| } |
| static const char *positions[]{"ASIS", "REWIND", "APPEND", nullptr}; |
| switch (IdentifyValue(keyword, length, positions)) { |
| case 0: |
| open->set_position(Position::AsIs); |
| return true; |
| case 1: |
| open->set_position(Position::Rewind); |
| return true; |
| case 2: |
| open->set_position(Position::Append); |
| return true; |
| default: |
| io.GetIoErrorHandler().SignalError(IostatErrorInKeyword, |
| "Invalid POSITION='%.*s'", static_cast<int>(length), keyword); |
| } |
| return true; |
| } |
| |
| bool IONAME(SetRecl)(Cookie cookie, std::size_t n) { |
| IoStatementState &io{*cookie}; |
| auto *open{io.get_if<OpenStatementState>()}; |
| if (!open) { |
| io.GetIoErrorHandler().Crash( |
| "SetRecl() called when not in an OPEN statement"); |
| } |
| if (n <= 0) { |
| io.GetIoErrorHandler().SignalError("RECL= must be greater than zero"); |
| } |
| if (open->wasExtant() && open->unit().recordLength.has_value() && |
| *open->unit().recordLength != static_cast<std::int64_t>(n)) { |
| open->SignalError("RECL= may not be changed for an open unit"); |
| } |
| open->unit().recordLength = n; |
| return true; |
| } |
| |
| bool IONAME(SetStatus)(Cookie cookie, const char *keyword, std::size_t length) { |
| IoStatementState &io{*cookie}; |
| if (auto *open{io.get_if<OpenStatementState>()}) { |
| static const char *statuses[]{ |
| "OLD", "NEW", "SCRATCH", "REPLACE", "UNKNOWN", nullptr}; |
| switch (IdentifyValue(keyword, length, statuses)) { |
| case 0: |
| open->set_status(OpenStatus::Old); |
| return true; |
| case 1: |
| open->set_status(OpenStatus::New); |
| return true; |
| case 2: |
| open->set_status(OpenStatus::Scratch); |
| return true; |
| case 3: |
| open->set_status(OpenStatus::Replace); |
| return true; |
| case 4: |
| open->set_status(OpenStatus::Unknown); |
| return true; |
| default: |
| io.GetIoErrorHandler().SignalError(IostatErrorInKeyword, |
| "Invalid STATUS='%.*s'", static_cast<int>(length), keyword); |
| } |
| return false; |
| } |
| if (auto *close{io.get_if<CloseStatementState>()}) { |
| static const char *statuses[]{"KEEP", "DELETE", nullptr}; |
| switch (IdentifyValue(keyword, length, statuses)) { |
| case 0: |
| close->set_status(CloseStatus::Keep); |
| return true; |
| case 1: |
| close->set_status(CloseStatus::Delete); |
| return true; |
| default: |
| io.GetIoErrorHandler().SignalError(IostatErrorInKeyword, |
| "Invalid STATUS='%.*s'", static_cast<int>(length), keyword); |
| } |
| return false; |
| } |
| if (io.get_if<NoopCloseStatementState>()) { |
| return true; // don't bother validating STATUS= in a no-op CLOSE |
| } |
| io.GetIoErrorHandler().Crash( |
| "SetStatus() called when not in an OPEN or CLOSE statement"); |
| } |
| |
| bool IONAME(SetFile)( |
| Cookie cookie, const char *path, std::size_t chars, int kind) { |
| IoStatementState &io{*cookie}; |
| if (auto *open{io.get_if<OpenStatementState>()}) { |
| open->set_path(path, chars, kind); |
| return true; |
| } |
| io.GetIoErrorHandler().Crash( |
| "SetFile() called when not in an OPEN statement"); |
| return false; |
| } |
| |
| static bool SetInteger(int &x, int kind, int value) { |
| switch (kind) { |
| case 1: |
| reinterpret_cast<std::int8_t &>(x) = value; |
| return true; |
| case 2: |
| reinterpret_cast<std::int16_t &>(x) = value; |
| return true; |
| case 4: |
| x = value; |
| return true; |
| case 8: |
| reinterpret_cast<std::int64_t &>(x) = value; |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool IONAME(GetNewUnit)(Cookie cookie, int &unit, int kind) { |
| IoStatementState &io{*cookie}; |
| auto *open{io.get_if<OpenStatementState>()}; |
| if (!open) { |
| io.GetIoErrorHandler().Crash( |
| "GetNewUnit() called when not in an OPEN statement"); |
| } |
| if (!SetInteger(unit, kind, open->unit().unitNumber())) { |
| open->SignalError("GetNewUnit(): Bad INTEGER kind(%d) for result"); |
| } |
| return true; |
| } |
| |
| // Data transfers |
| |
| bool IONAME(OutputDescriptor)(Cookie cookie, const Descriptor &) { |
| IoStatementState &io{*cookie}; |
| io.GetIoErrorHandler().Crash("OutputDescriptor: not yet implemented"); // TODO |
| } |
| |
| bool IONAME(OutputUnformattedBlock)( |
| Cookie cookie, const char *x, std::size_t length) { |
| IoStatementState &io{*cookie}; |
| if (auto *unf{io.get_if<UnformattedIoStatementState<Direction::Output>>()}) { |
| return unf->Emit(x, length); |
| } |
| io.GetIoErrorHandler().Crash("OutputUnformatted() called for an I/O " |
| "statement that is not unformatted output"); |
| return false; |
| } |
| |
| bool IONAME(OutputInteger64)(Cookie cookie, std::int64_t n) { |
| IoStatementState &io{*cookie}; |
| if (!io.get_if<OutputStatementState>()) { |
| io.GetIoErrorHandler().Crash( |
| "OutputInteger64() called for a non-output I/O statement"); |
| return false; |
| } |
| if (auto edit{io.GetNextDataEdit()}) { |
| return EditIntegerOutput(io, *edit, n); |
| } |
| return false; |
| } |
| |
| bool IONAME(InputInteger)(Cookie cookie, std::int64_t &n, int kind) { |
| IoStatementState &io{*cookie}; |
| if (!io.get_if<InputStatementState>()) { |
| io.GetIoErrorHandler().Crash( |
| "InputInteger64() called for a non-input I/O statement"); |
| return false; |
| } |
| if (auto edit{io.GetNextDataEdit()}) { |
| if (edit->descriptor == DataEdit::ListDirectedNullValue) { |
| return true; |
| } |
| return EditIntegerInput(io, *edit, reinterpret_cast<void *>(&n), kind); |
| } |
| return false; |
| } |
| |
| bool IONAME(OutputReal64)(Cookie cookie, double x) { |
| IoStatementState &io{*cookie}; |
| if (!io.get_if<OutputStatementState>()) { |
| io.GetIoErrorHandler().Crash( |
| "OutputReal64() called for a non-output I/O statement"); |
| return false; |
| } |
| if (auto edit{io.GetNextDataEdit()}) { |
| return RealOutputEditing<53>{io, x}.Edit(*edit); |
| } |
| return false; |
| } |
| |
| bool IONAME(InputReal64)(Cookie cookie, double &x) { |
| IoStatementState &io{*cookie}; |
| if (!io.get_if<InputStatementState>()) { |
| io.GetIoErrorHandler().Crash( |
| "InputReal64() called for a non-input I/O statement"); |
| return false; |
| } |
| if (auto edit{io.GetNextDataEdit()}) { |
| if (edit->descriptor == DataEdit::ListDirectedNullValue) { |
| return true; |
| } |
| return EditRealInput<53>(io, *edit, reinterpret_cast<void *>(&x)); |
| } |
| return false; |
| } |
| |
| bool IONAME(OutputComplex64)(Cookie cookie, double r, double z) { |
| IoStatementState &io{*cookie}; |
| if (io.get_if<ListDirectedStatementState<Direction::Output>>()) { |
| DataEdit real, imaginary; |
| real.descriptor = DataEdit::ListDirectedRealPart; |
| imaginary.descriptor = DataEdit::ListDirectedImaginaryPart; |
| return RealOutputEditing<53>{io, r}.Edit(real) && |
| RealOutputEditing<53>{io, z}.Edit(imaginary); |
| } |
| return IONAME(OutputReal64)(cookie, r) && IONAME(OutputReal64)(cookie, z); |
| } |
| |
| bool IONAME(OutputAscii)(Cookie cookie, const char *x, std::size_t length) { |
| IoStatementState &io{*cookie}; |
| if (!io.get_if<OutputStatementState>()) { |
| io.GetIoErrorHandler().Crash( |
| "OutputAscii() called for a non-output I/O statement"); |
| return false; |
| } |
| if (auto *list{io.get_if<ListDirectedStatementState<Direction::Output>>()}) { |
| return ListDirectedDefaultCharacterOutput(io, *list, x, length); |
| } else if (auto edit{io.GetNextDataEdit()}) { |
| return EditDefaultCharacterOutput(io, *edit, x, length); |
| } else { |
| return false; |
| } |
| } |
| |
| bool IONAME(InputAscii)(Cookie cookie, char *x, std::size_t length) { |
| IoStatementState &io{*cookie}; |
| if (!io.get_if<InputStatementState>()) { |
| io.GetIoErrorHandler().Crash( |
| "InputAscii() called for a non-input I/O statement"); |
| return false; |
| } |
| if (auto edit{io.GetNextDataEdit()}) { |
| if (edit->descriptor == DataEdit::ListDirectedNullValue) { |
| return true; |
| } |
| return EditDefaultCharacterInput(io, *edit, x, length); |
| } |
| return false; |
| } |
| |
| bool IONAME(OutputLogical)(Cookie cookie, bool truth) { |
| IoStatementState &io{*cookie}; |
| if (!io.get_if<OutputStatementState>()) { |
| io.GetIoErrorHandler().Crash( |
| "OutputLogical() called for a non-output I/O statement"); |
| return false; |
| } |
| if (auto *list{io.get_if<ListDirectedStatementState<Direction::Output>>()}) { |
| return ListDirectedLogicalOutput(io, *list, truth); |
| } else if (auto edit{io.GetNextDataEdit()}) { |
| return EditLogicalOutput(io, *edit, truth); |
| } else { |
| return false; |
| } |
| } |
| |
| bool IONAME(InputLogical)(Cookie cookie, bool &truth) { |
| IoStatementState &io{*cookie}; |
| if (!io.get_if<InputStatementState>()) { |
| io.GetIoErrorHandler().Crash( |
| "InputLogical() called for a non-input I/O statement"); |
| return false; |
| } |
| if (auto edit{io.GetNextDataEdit()}) { |
| if (edit->descriptor == DataEdit::ListDirectedNullValue) { |
| return true; |
| } |
| return EditLogicalInput(io, *edit, truth); |
| } |
| return false; |
| } |
| |
| void IONAME(GetIoMsg)(Cookie cookie, char *msg, std::size_t length) { |
| IoErrorHandler &handler{cookie->GetIoErrorHandler()}; |
| if (handler.GetIoStat()) { // leave "msg" alone when no error |
| handler.GetIoMsg(msg, length); |
| } |
| } |
| |
| enum Iostat IONAME(EndIoStatement)(Cookie cookie) { |
| IoStatementState &io{*cookie}; |
| return static_cast<enum Iostat>(io.EndIoStatement()); |
| } |
| } // namespace Fortran::runtime::io |