[flang] begin processing format strings
authorpeter klausler <pklausler@nvidia.com>
Thu, 9 Jan 2020 16:10:57 +0000 (08:10 -0800)
committerpeter klausler <pklausler@nvidia.com>
Wed, 15 Jan 2020 19:27:55 +0000 (11:27 -0800)
Move RoundingMode to Fortran.h

Always skip blanks outside character literals & Hollerith

Templatize

optimize repeat counts somewhat

Fix license punctuation, remove patch

Original-commit: flang-compiler/f18@4a0d39b0398974ade4367a5a96e11a90e853c18c
Reviewed-on: https://github.com/flang-compiler/f18/pull/927

17 files changed:
flang/lib/common/Fortran.h
flang/lib/evaluate/common.h
flang/lib/evaluate/fold-integer.cc
flang/lib/evaluate/fold-real.cc
flang/lib/evaluate/host.cc
flang/lib/evaluate/real.cc
flang/lib/evaluate/real.h
flang/lib/evaluate/rounding-bits.h
flang/runtime/CMakeLists.txt
flang/runtime/format.cc [new file with mode: 0644]
flang/runtime/format.h [new file with mode: 0644]
flang/runtime/stop.cc
flang/test/CMakeLists.txt
flang/test/evaluate/fp-testing.cc
flang/test/evaluate/fp-testing.h
flang/test/runtime/CMakeLists.txt [new file with mode: 0644]
flang/test/runtime/format.cc [new file with mode: 0644]

index 9681472..c4f3677 100644 (file)
@@ -56,6 +56,16 @@ ENUM_CLASS(IoSpecKind, Access, Action, Advance, Asynchronous, Blank, Decimal,
     Dispose,  // nonstandard
 )
 
+// Floating-point rounding modes; these are packed into a byte to save
+// room in the runtime's format processing context structure.
+enum class RoundingMode : std::uint8_t {
+  TiesToEven,  // ROUND=NEAREST, RN - default IEEE rounding
+  ToZero,  // ROUND=ZERO, RZ - truncation
+  Down,  // ROUND=DOWN, RD
+  Up,  // ROUND=UP, RU
+  TiesAwayFromZero,  // ROUND=COMPATIBLE, RC - ties round away from zero
+};
+
 // Fortran arrays may have up to 15 dimensions (See Fortran 2018 section 5.4.6).
 static constexpr int maxRank{15};
 }
index 7a01cec..e716d21 100644 (file)
@@ -114,10 +114,8 @@ template<typename A> struct ValueWithRealFlags {
   RealFlags flags{};
 };
 
-ENUM_CLASS(RoundingMode, TiesToEven, ToZero, Down, Up, TiesAwayFromZero)
-
 struct Rounding {
-  RoundingMode mode{RoundingMode::TiesToEven};
+  common::RoundingMode mode{common::RoundingMode::TiesToEven};
   // When set, emulate status flag behavior peculiar to x86
   // (viz., fail to set the Underflow flag when an inexact product of a
   // multiplication is rounded up to a normal number from a subnormal
index 8e7a895..aa696dd 100644 (file)
@@ -157,10 +157,10 @@ Expr<Type<TypeCategory::Integer, KIND>> FoldIntrinsicFunction(
   } else if (name == "ceiling" || name == "floor" || name == "nint") {
     if (const auto *cx{UnwrapExpr<Expr<SomeReal>>(args[0])}) {
       // NINT rounds ties away from zero, not to even
-      RoundingMode mode{name == "ceiling"
-              ? RoundingMode::Up
-              : name == "floor" ? RoundingMode::Down
-                                : RoundingMode::TiesAwayFromZero};
+      common::RoundingMode mode{name == "ceiling"
+              ? common::RoundingMode::Up
+              : name == "floor" ? common::RoundingMode::Down
+                                : common::RoundingMode::TiesAwayFromZero};
       return std::visit(
           [&](const auto &kx) {
             using TR = ResultType<decltype(kx)>;
index 74eaf62..05b719e 100644 (file)
@@ -89,8 +89,9 @@ Expr<Type<TypeCategory::Real, KIND>> FoldIntrinsicFunction(
         context, std::move(funcRef), &Scalar<ComplexT>::AIMAG);
   } else if (name == "aint" || name == "anint") {
     // ANINT rounds ties away from zero, not to even
-    RoundingMode mode{
-        name == "aint" ? RoundingMode::ToZero : RoundingMode::TiesAwayFromZero};
+    common::RoundingMode mode{name == "aint"
+            ? common::RoundingMode::ToZero
+            : common::RoundingMode::TiesAwayFromZero};
     return FoldElementalIntrinsic<T, T>(context, std::move(funcRef),
         ScalarFunc<T, T>([&name, &context, mode](
                              const Scalar<T> &x) -> Scalar<T> {
index 82537f2..718bc7a 100644 (file)
@@ -76,11 +76,11 @@ void HostFloatingPointEnvironment::SetUpHostFloatingPointEnvironment(
     return;
   }
   switch (context.rounding().mode) {
-  case RoundingMode::TiesToEven: fesetround(FE_TONEAREST); break;
-  case RoundingMode::ToZero: fesetround(FE_TOWARDZERO); break;
-  case RoundingMode::Up: fesetround(FE_UPWARD); break;
-  case RoundingMode::Down: fesetround(FE_DOWNWARD); break;
-  case RoundingMode::TiesAwayFromZero:
+  case common::RoundingMode::TiesToEven: fesetround(FE_TONEAREST); break;
+  case common::RoundingMode::ToZero: fesetround(FE_TOWARDZERO); break;
+  case common::RoundingMode::Up: fesetround(FE_UPWARD); break;
+  case common::RoundingMode::Down: fesetround(FE_DOWNWARD); break;
+  case common::RoundingMode::TiesAwayFromZero:
     fesetround(FE_TONEAREST);
     context.messages().Say(
         "TiesAwayFromZero rounding mode is not available when folding constants"
index 4c3f4a6..44b7cbe 100644 (file)
@@ -97,7 +97,7 @@ ValueWithRealFlags<Real<W, P, IM>> Real<W, P, IM>::Add(
     }
     if (order == Ordering::Equal) {
       // x + (-x) -> +0.0 unless rounding is directed downwards
-      if (rounding.mode == RoundingMode::Down) {
+      if (rounding.mode == common::RoundingMode::Down) {
         result.value.word_ = result.value.word_.IBSET(bits - 1);  // -0.0
       }
       return result;
@@ -263,7 +263,7 @@ ValueWithRealFlags<Real<W, P, IM>> Real<W, P, IM>::Divide(
 
 template<typename W, int P, bool IM>
 ValueWithRealFlags<Real<W, P, IM>> Real<W, P, IM>::ToWholeNumber(
-    RoundingMode mode) const {
+    common::RoundingMode mode) const {
   ValueWithRealFlags<Real> result{*this};
   if (IsNotANumber()) {
     result.flags.set(RealFlag::InvalidArgument);
@@ -280,7 +280,7 @@ ValueWithRealFlags<Real<W, P, IM>> Real<W, P, IM>::ToWholeNumber(
       result.flags.reset(RealFlag::Inexact);  // result *is* exact
       // Return (ival-adjust) with original sign in case we've generated a zero.
       result.value =
-          result.value.Subtract(adjust, Rounding{RoundingMode::ToZero})
+          result.value.Subtract(adjust, Rounding{common::RoundingMode::ToZero})
               .value.SIGN(*this);
     }
   }
@@ -307,10 +307,10 @@ RealFlags Real<W, P, IM>::Normalize(bool negative, int exponent,
   }
   if (exponent >= maxExponent) {
     // Infinity or overflow
-    if (rounding.mode == RoundingMode::TiesToEven ||
-        rounding.mode == RoundingMode::TiesAwayFromZero ||
-        (rounding.mode == RoundingMode::Up && !negative) ||
-        (rounding.mode == RoundingMode::Down && negative)) {
+    if (rounding.mode == common::RoundingMode::TiesToEven ||
+        rounding.mode == common::RoundingMode::TiesAwayFromZero ||
+        (rounding.mode == common::RoundingMode::Up && !negative) ||
+        (rounding.mode == common::RoundingMode::Down && negative)) {
       word_ = Word{maxExponent}.SHIFTL(significandBits);  // Inf
     } else {
       // directed rounding: round to largest finite value rather than infinity
@@ -376,8 +376,8 @@ RealFlags Real<W, P, IM>::Round(
     if (rounding.x86CompatibleBehavior && Exponent() != 0 && multiply &&
         bits.sticky() &&
         (bits.guard() ||
-            (rounding.mode != RoundingMode::Up &&
-                rounding.mode != RoundingMode::Down))) {
+            (rounding.mode != common::RoundingMode::Up &&
+                rounding.mode != common::RoundingMode::Down))) {
       // x86 edge case in which Underflow fails to signal when a subnormal
       // inexact multiplication product rounds to a normal result when
       // the guard bit is set or we're not using directed rounding
@@ -397,13 +397,14 @@ void Real<W, P, IM>::NormalizeAndRound(ValueWithRealFlags<Real> &result,
   result.flags |= result.value.Round(rounding, roundingBits, multiply);
 }
 
-inline enum decimal::FortranRounding MapRoundingMode(RoundingMode rounding) {
+inline enum decimal::FortranRounding MapRoundingMode(
+    common::RoundingMode rounding) {
   switch (rounding) {
-  case RoundingMode::TiesToEven: break;
-  case RoundingMode::ToZero: return decimal::RoundToZero;
-  case RoundingMode::Down: return decimal::RoundDown;
-  case RoundingMode::Up: return decimal::RoundUp;
-  case RoundingMode::TiesAwayFromZero: return decimal::RoundCompatible;
+  case common::RoundingMode::TiesToEven: break;
+  case common::RoundingMode::ToZero: return decimal::RoundToZero;
+  case common::RoundingMode::Down: return decimal::RoundDown;
+  case common::RoundingMode::Up: return decimal::RoundUp;
+  case common::RoundingMode::TiesAwayFromZero: return decimal::RoundCompatible;
   }
   return decimal::RoundNearest;  // dodge gcc warning about lack of result
 }
index 2e0086c..92bb7d1 100644 (file)
@@ -207,12 +207,12 @@ public:
 
   // Conversion to integer in the same real format (AINT(), ANINT())
   ValueWithRealFlags<Real> ToWholeNumber(
-      RoundingMode = RoundingMode::ToZero) const;
+      common::RoundingMode = common::RoundingMode::ToZero) const;
 
   // Conversion to an integer (INT(), NINT(), FLOOR(), CEILING())
   template<typename INT>
   constexpr ValueWithRealFlags<INT> ToInteger(
-      RoundingMode mode = RoundingMode::ToZero) const {
+      common::RoundingMode mode = common::RoundingMode::ToZero) const {
     ValueWithRealFlags<INT> result;
     if (IsNotANumber()) {
       result.flags.set(RealFlag::InvalidArgument);
index fc029b6..07db7ce 100644 (file)
@@ -78,13 +78,13 @@ public:
       Rounding rounding, bool isNegative, bool isOdd) const {
     bool round{false};  // to dodge bogus g++ warning about missing return
     switch (rounding.mode) {
-    case RoundingMode::TiesToEven:
+    case common::RoundingMode::TiesToEven:
       round = guard_ && (round_ | sticky_ | isOdd);
       break;
-    case RoundingMode::ToZero: break;
-    case RoundingMode::Down: round = isNegative && !empty(); break;
-    case RoundingMode::Up: round = !isNegative && !empty(); break;
-    case RoundingMode::TiesAwayFromZero: round = guard_; break;
+    case common::RoundingMode::ToZero: break;
+    case common::RoundingMode::Down: round = isNegative && !empty(); break;
+    case common::RoundingMode::Up: round = !isNegative && !empty(); break;
+    case common::RoundingMode::TiesAwayFromZero: round = guard_; break;
     }
     return round;
   }
index 99713cc..523c7aa 100644 (file)
@@ -10,6 +10,7 @@ add_library(FortranRuntime
   ISO_Fortran_binding.cc
   derived-type.cc
   descriptor.cc
+  format.cc
   main.cc
   stop.cc
   terminator.cc
diff --git a/flang/runtime/format.cc b/flang/runtime/format.cc
new file mode 100644 (file)
index 0000000..b7a175d
--- /dev/null
@@ -0,0 +1,365 @@
+//===-- runtime/format.cc ---------------------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "format.h"
+#include "../lib/common/format.h"
+#include "../lib/decimal/decimal.h"
+#include <limits>
+
+namespace Fortran::runtime {
+
+template<typename CHAR>
+FormatControl<CHAR>::FormatControl(FormatContext &context, const CHAR *format,
+    std::size_t formatLength, const MutableModes &modes, int maxHeight)
+  : context_{context}, modes_{modes}, maxHeight_{static_cast<std::uint8_t>(
+                                          maxHeight)},
+    format_{format}, formatLength_{static_cast<int>(formatLength)} {
+  // The additional two items are for the whole string and a
+  // repeated non-parenthesized edit descriptor.
+  if (maxHeight > std::numeric_limits<std::int8_t>::max()) {
+    context_.terminator.Crash(
+        "internal Fortran runtime error: maxHeight %d", maxHeight);
+  }
+  stack_[0].start = offset_;
+  stack_[0].remaining = Iteration::unlimited;  // 13.4(8)
+}
+
+template<typename CHAR>
+int FormatControl<CHAR>::GetMaxParenthesisNesting(
+    Terminator &terminator, const CHAR *format, std::size_t formatLength) {
+  using Validator = common::FormatValidator<CHAR>;
+  typename Validator::Reporter reporter{
+      [&](const common::FormatMessage &message) {
+        terminator.Crash(message.text, message.arg);
+        return false;  // crashes on error above
+      }};
+  Validator validator{format, formatLength, reporter};
+  validator.Check();
+  return validator.maxNesting();
+}
+
+static void HandleCharacterLiteral(
+    FormatContext &context, const char *str, std::size_t chars) {
+  if (context.handleCharacterLiteral1) {
+    context.handleCharacterLiteral1(str, chars);
+  }
+}
+
+static void HandleCharacterLiteral(
+    FormatContext &context, const char16_t *str, std::size_t chars) {
+  if (context.handleCharacterLiteral2) {
+    context.handleCharacterLiteral2(str, chars);
+  }
+}
+
+static void HandleCharacterLiteral(
+    FormatContext &context, const char32_t *str, std::size_t chars) {
+  if (context.handleCharacterLiteral4) {
+    context.handleCharacterLiteral4(str, chars);
+  }
+}
+
+template<typename CHAR> int FormatControl<CHAR>::GetIntField(CHAR firstCh) {
+  CHAR ch{firstCh ? firstCh : PeekNext()};
+  if (ch < '0' || ch > '9') {
+    context_.terminator.Crash(
+        "Invalid FORMAT: integer expected at '%c'", static_cast<char>(ch));
+  }
+  int result{0};
+  while (ch >= '0' && ch <= '9') {
+    if (result >
+        std::numeric_limits<int>::max() / 10 - (static_cast<int>(ch) - '0')) {
+      context_.terminator.Crash("FORMAT integer field out of range");
+    }
+    result = 10 * result + ch - '0';
+    if (firstCh) {
+      firstCh = '\0';
+    } else {
+      ++offset_;
+    }
+    ch = PeekNext();
+  }
+  return result;
+}
+
+static void HandleControl(MutableModes &modes, std::uint16_t &scale,
+    FormatContext &context, char ch, char next, int n) {
+  switch (ch) {
+  case 'B':
+    if (next == 'Z') {
+      modes.editingFlags |= blankZero;
+      return;
+    }
+    if (next == 'N') {
+      modes.editingFlags &= ~blankZero;
+      return;
+    }
+    break;
+  case 'D':
+    if (next == 'C') {
+      modes.editingFlags |= decimalComma;
+      return;
+    }
+    if (next == 'P') {
+      modes.editingFlags &= ~decimalComma;
+      return;
+    }
+    break;
+  case 'P':
+    if (!next) {
+      scale = n;  // kP - decimal scaling by 10**k (TODO)
+      return;
+    }
+    break;
+  case 'R':
+    switch (next) {
+    case 'N': modes.roundingMode = common::RoundingMode::TiesToEven; return;
+    case 'Z': modes.roundingMode = common::RoundingMode::ToZero; return;
+    case 'U': modes.roundingMode = common::RoundingMode::Up; return;
+    case 'D': modes.roundingMode = common::RoundingMode::Down; return;
+    case 'C':
+      modes.roundingMode = common::RoundingMode::TiesAwayFromZero;
+      return;
+    default: break;
+    }
+    break;
+  case 'X':
+    if (!next) {
+      if (context.handleRelativePosition) {
+        context.handleRelativePosition(n);
+      }
+      return;
+    }
+    break;
+  case 'S':
+    if (next == 'P') {
+      modes.editingFlags |= signPlus;
+      return;
+    }
+    if (!next || next == 'S') {
+      modes.editingFlags &= ~signPlus;
+      return;
+    }
+    break;
+  case 'T': {
+    if (!next) {  // Tn
+      if (context.handleAbsolutePosition) {
+        context.handleAbsolutePosition(n);
+      }
+      return;
+    }
+    if (next == 'L' || next == 'R') {  // TLn & TRn
+      if (context.handleRelativePosition) {
+        context.handleRelativePosition(next == 'L' ? -n : n);
+      }
+      return;
+    }
+  } break;
+  default: break;
+  }
+  if (next) {
+    context.terminator.Crash(
+        "Unknown '%c%c' edit descriptor in FORMAT", ch, next);
+  } else {
+    context.terminator.Crash("Unknown '%c' edit descriptor in FORMAT", ch);
+  }
+}
+
+// Locates the next data edit descriptor in the format.
+// Handles all repetition counts and control edit descriptors.
+// Generally assumes that the format string has survived the common
+// format validator gauntlet.
+template<typename CHAR> int FormatControl<CHAR>::CueUpNextDataEdit(bool stop) {
+  int unlimitedLoopCheck{-1};
+  while (true) {
+    std::optional<int> repeat;
+    bool unlimited{false};
+    CHAR ch{Capitalize(GetNextChar())};
+    while (ch == ',' || ch == ':') {
+      // Skip commas, and don't complain if they're missing; the format
+      // validator does that.
+      if (stop && ch == ':') {
+        return 0;
+      }
+      ch = Capitalize(GetNextChar());
+    }
+    if (ch >= '0' && ch <= '9') {  // repeat count
+      repeat = GetIntField(ch);
+      ch = GetNextChar();
+    } else if (ch == '*') {
+      unlimited = true;
+      ch = GetNextChar();
+      if (ch != '(') {
+        context_.terminator.Crash(
+            "Invalid FORMAT: '*' may appear only before '('");
+      }
+    }
+    if (ch == '(') {
+      if (height_ >= maxHeight_) {
+        context_.terminator.Crash(
+            "FORMAT stack overflow: too many nested parentheses");
+      }
+      stack_[height_].start = offset_ - 1;  // the '('
+      if (unlimited || height_ == 0) {
+        stack_[height_].remaining = Iteration::unlimited;
+        unlimitedLoopCheck = offset_ - 1;
+      } else if (repeat) {
+        if (*repeat <= 0) {
+          *repeat = 1;  // error recovery
+        }
+        stack_[height_].remaining = *repeat - 1;
+      } else {
+        stack_[height_].remaining = 0;
+      }
+      ++height_;
+    } else if (height_ == 0) {
+      context_.terminator.Crash("FORMAT lacks initial '('");
+    } else if (ch == ')') {
+      if (height_ == 1 && stop) {
+        return 0;  // end of FORMAT and no data items remain
+      }
+      if (stack_[height_ - 1].remaining == Iteration::unlimited) {
+        offset_ = stack_[height_ - 1].start + 1;
+        if (offset_ == unlimitedLoopCheck) {
+          context_.terminator.Crash(
+              "Unlimited repetition in FORMAT lacks data edit descriptors");
+        }
+      } else if (stack_[height_ - 1].remaining-- > 0) {
+        offset_ = stack_[height_ - 1].start + 1;
+      } else {
+        --height_;
+      }
+    } else if (ch == '\'' || ch == '"') {
+      // Quoted 'character literal'
+      CHAR quote{ch};
+      auto start{offset_};
+      while (offset_ < formatLength_ && format_[offset_] != quote) {
+        ++offset_;
+      }
+      if (offset_ >= formatLength_) {
+        context_.terminator.Crash(
+            "FORMAT missing closing quote on character literal");
+      }
+      ++offset_;
+      std::size_t chars{
+          static_cast<std::size_t>(&format_[offset_] - &format_[start])};
+      if (PeekNext() == quote) {
+        // subtle: handle doubled quote character in a literal by including
+        // the first in the output, then treating the second as the start
+        // of another character literal.
+      } else {
+        --chars;
+      }
+      HandleCharacterLiteral(context_, format_ + start, chars);
+    } else if (ch == 'H') {
+      // 9HHOLLERITH
+      if (!repeat || *repeat < 1 || offset_ + *repeat > formatLength_) {
+        context_.terminator.Crash("Invalid width on Hollerith in FORMAT");
+      }
+      HandleCharacterLiteral(
+          context_, format_ + offset_, static_cast<std::size_t>(*repeat));
+      offset_ += *repeat;
+    } else if (ch >= 'A' && ch <= 'Z') {
+      int start{offset_ - 1};
+      CHAR next{Capitalize(PeekNext())};
+      if (next < 'A' || next > 'Z') {
+        next = '\0';
+      }
+      if (ch == 'E' ||
+          (!next &&
+              (ch == 'A' || ch == 'I' || ch == 'B' || ch == 'O' || ch == 'Z' ||
+                  ch == 'F' || ch == 'D' || ch == 'G'))) {
+        // Data edit descriptor found
+        offset_ = start;
+        return repeat ? *repeat : 1;
+      } else {
+        // Control edit descriptor
+        if (ch == 'T') {  // Tn, TLn, TRn
+          repeat = GetIntField();
+        }
+        HandleControl(modes_, scale_, context_, static_cast<char>(ch),
+            static_cast<char>(next), repeat ? *repeat : 1);
+      }
+    } else if (ch == '/') {
+      if (context_.handleSlash) {
+        context_.handleSlash();
+      }
+    } else {
+      context_.terminator.Crash(
+          "Invalid character '%c' in FORMAT", static_cast<char>(ch));
+    }
+  }
+}
+
+template<typename CHAR>
+void FormatControl<CHAR>::GetNext(DataEdit &edit, int maxRepeat) {
+
+  // TODO: DT editing
+
+  // Return the next data edit descriptor
+  int repeat{CueUpNextDataEdit()};
+  auto start{offset_};
+  edit.descriptor = static_cast<char>(Capitalize(GetNextChar()));
+  if (edit.descriptor == 'E') {
+    edit.variation = static_cast<char>(Capitalize(PeekNext()));
+    if (edit.variation >= 'A' && edit.variation <= 'Z') {
+      ++offset_;
+    } else {
+      edit.variation = '\0';
+    }
+  } else {
+    edit.variation = '\0';
+  }
+
+  edit.width = GetIntField();
+  edit.modes = modes_;
+  if (PeekNext() == '.') {
+    ++offset_;
+    edit.digits = GetIntField();
+    CHAR ch{PeekNext()};
+    if (ch == 'e' || ch == 'E' || ch == 'd' || ch == 'D') {
+      ++offset_;
+      edit.expoDigits = GetIntField();
+    } else {
+      edit.expoDigits.reset();
+    }
+  } else {
+    edit.digits.reset();
+    edit.expoDigits.reset();
+  }
+
+  // Handle repeated nonparenthesized edit descriptors
+  if (repeat > 1) {
+    stack_[height_].start = start;  // after repeat count
+    stack_[height_].remaining = repeat;  // full count
+    ++height_;
+  }
+  edit.repeat = 1;
+  if (height_ > 1) {
+    int start{stack_[height_ - 1].start};
+    if (format_[start] != '(') {
+      if (stack_[height_ - 1].remaining > maxRepeat) {
+        edit.repeat = maxRepeat;
+        stack_[height_ - 1].remaining -= maxRepeat;
+        offset_ = start;  // repeat same edit descriptor next time
+      } else {
+        edit.repeat = stack_[height_ - 1].remaining;
+        --height_;
+      }
+    }
+  }
+}
+
+template<typename CHAR> void FormatControl<CHAR>::FinishOutput() {
+  CueUpNextDataEdit(true /* stop at colon or end of FORMAT */);
+}
+
+template class FormatControl<char>;
+template class FormatControl<char16_t>;
+template class FormatControl<char32_t>;
+}
diff --git a/flang/runtime/format.h b/flang/runtime/format.h
new file mode 100644 (file)
index 0000000..94025f6
--- /dev/null
@@ -0,0 +1,137 @@
+//===-- runtime/format.h ----------------------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+// FORMAT string processing
+
+#ifndef FORTRAN_RUNTIME_FORMAT_H_
+#define FORTRAN_RUNTIME_FORMAT_H_
+
+#include "terminator.h"
+#include "../lib/common/Fortran.h"
+#include <cinttypes>
+#include <optional>
+
+namespace Fortran::runtime {
+
+enum EditingFlags {
+  blankZero = 1,  // BLANK=ZERO or BZ edit
+  decimalComma = 2,  // DECIMAL=COMMA or DC edit
+  signPlus = 4,  // SIGN=PLUS or SP edit
+};
+
+struct MutableModes {
+  std::uint8_t editingFlags{0};  // BN, DP, SS
+  common::RoundingMode roundingMode{common::RoundingMode::TiesToEven};  // RN
+};
+
+// A single edit descriptor extracted from a FORMAT
+struct DataEdit {
+  char descriptor;  // capitalized: one of A, I, B, O, Z, F, E(N/S/X), D, G
+  char variation{'\0'};  // N, S, or X for EN, ES, EX
+  int width;  // the 'w' field
+  std::optional<int> digits;  // the 'm' or 'd' field
+  std::optional<int> expoDigits;  // 'Ee' field
+  MutableModes modes;
+  int repeat{1};
+};
+
+struct FormatContext {
+  Terminator &terminator;
+  void (*handleCharacterLiteral1)(const char *, std::size_t){nullptr};
+  void (*handleCharacterLiteral2)(const char16_t *, std::size_t){nullptr};
+  void (*handleCharacterLiteral4)(const char32_t *, std::size_t){nullptr};
+  void (*handleSlash)(){nullptr};
+  void (*handleAbsolutePosition)(int){nullptr};  // Tn
+  void (*handleRelativePosition)(int){nullptr};  // nX, TRn, TLn (negated)
+};
+
+// Generates a sequence of DataEdits from a FORMAT statement or
+// default-CHARACTER string.  Driven by I/O item list processing.
+// Errors are fatal.  See clause 13.4 in Fortran 2018 for background.
+template<typename CHAR = char> class FormatControl {
+public:
+  FormatControl(FormatContext &, const CHAR *format, std::size_t formatLength,
+      const MutableModes &initialModes, int maxHeight = maxMaxHeight);
+
+  // Determines the max parenthesis nesting level by scanning and validating
+  // the FORMAT string.
+  static int GetMaxParenthesisNesting(
+      Terminator &, const CHAR *format, std::size_t formatLength);
+
+  // For attempting to allocate in a user-supplied stack area
+  static std::size_t GetNeededSize(int maxHeight) {
+    return sizeof(FormatControl) -
+        sizeof(Iteration) * (maxMaxHeight - maxHeight);
+  }
+
+  // Extracts the next data edit descriptor, handling control edit descriptors
+  // along the way.
+  void GetNext(DataEdit &, int maxRepeat = 1);
+
+  // Emit any remaining character literals after the last data item.
+  void FinishOutput();
+
+private:
+  static constexpr std::uint8_t maxMaxHeight{100};
+
+  struct Iteration {
+    static constexpr int unlimited{-1};
+    int start{0};  // offset in format_ of '(' or a repeated edit descriptor
+    int remaining{0};  // while >0, decrement and iterate
+  };
+
+  void SkipBlanks() {
+    while (offset_ < formatLength_ && format_[offset_] == ' ') {
+      ++offset_;
+    }
+  }
+  CHAR PeekNext() {
+    SkipBlanks();
+    return offset_ < formatLength_ ? format_[offset_] : '\0';
+  }
+  CHAR GetNextChar() {
+    SkipBlanks();
+    if (offset_ >= formatLength_) {
+      context_.terminator.Crash("FORMAT missing at least one ')'");
+    }
+    return format_[offset_++];
+  }
+  int GetIntField(CHAR firstCh = '\0');
+
+  // Advances through the FORMAT until the next data edit
+  // descriptor has been found; handles control edit descriptors
+  // along the way.  Returns the repeat count that appeared
+  // before the descriptor (defaulting to 1) and leaves offset_
+  // pointing to the data edit.
+  int CueUpNextDataEdit(bool stop = false);
+
+  static constexpr CHAR Capitalize(CHAR ch) {
+    return ch >= 'a' && ch <= 'z' ? ch + 'A' - 'a' : ch;
+  }
+
+  // Data members are arranged and typed so as to reduce size.
+  // This structure may be allocated in stack space loaned by the
+  // user program for internal I/O.
+  FormatContext &context_;
+  MutableModes modes_;
+  std::uint16_t scale_{0};  // kP
+  const std::uint8_t maxHeight_{maxMaxHeight};
+  std::uint8_t height_{0};
+  const CHAR *format_;
+  int formatLength_;
+  int offset_{0};  // next item is at format_[offset_]
+
+  // must be last, may be incomplete
+  Iteration stack_[maxMaxHeight];
+};
+
+extern template class FormatControl<char>;
+extern template class FormatControl<char16_t>;
+extern template class FormatControl<char32_t>;
+}
+#endif  // FORTRAN_RUNTIME_FORMAT_H_
index 5cd96ef..a1f421a 100644 (file)
@@ -55,8 +55,8 @@ static void DescribeIEEESignaledExceptions() {
 [[noreturn]] void RTNAME(StopStatementText)(
     const char *code, bool isErrorStop, bool quiet) {
   if (!quiet) {
-    std::fprintf(stderr, "Fortran %s: %s\n",
-        isErrorStop ? "ERROR STOP" : "STOP", code);
+    std::fprintf(
+        stderr, "Fortran %s: %s\n", isErrorStop ? "ERROR STOP" : "STOP", code);
     DescribeIEEESignaledExceptions();
   }
   std::exit(EXIT_FAILURE);
index da06c83..9013757 100644 (file)
@@ -8,4 +8,5 @@
 
 add_subdirectory(decimal)
 add_subdirectory(evaluate)
+add_subdirectory(runtime)
 add_subdirectory(semantics)
index 98a84de..a1893a8 100644 (file)
@@ -3,6 +3,7 @@
 #include <cstdlib>
 #include <cstring>
 
+using Fortran::common::RoundingMode;
 using Fortran::evaluate::RealFlag;
 
 ScopedHostFloatingPointEnvironment::ScopedHostFloatingPointEnvironment(
index ed2efc5..acb2d68 100644 (file)
@@ -4,9 +4,9 @@
 #include "../../lib/evaluate/common.h"
 #include <fenv.h>
 
+using Fortran::common::RoundingMode;
 using Fortran::evaluate::RealFlags;
 using Fortran::evaluate::Rounding;
-using Fortran::evaluate::RoundingMode;
 
 class ScopedHostFloatingPointEnvironment {
 public:
diff --git a/flang/test/runtime/CMakeLists.txt b/flang/test/runtime/CMakeLists.txt
new file mode 100644 (file)
index 0000000..a9ef7c4
--- /dev/null
@@ -0,0 +1,21 @@
+#===-- test/runtime/CMakeLists.txt -----------------------------------------===#
+#
+# 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
+#
+#------------------------------------------------------------------------------#
+
+if(CMAKE_COMPILER_IS_GNUCXX OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang"))
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexceptions")
+endif()
+
+add_executable(format-test
+  format.cc
+)
+
+target_link_libraries(format-test
+  FortranRuntime
+)
+
+add_test(Format format-test)
diff --git a/flang/test/runtime/format.cc b/flang/test/runtime/format.cc
new file mode 100644 (file)
index 0000000..95d44f1
--- /dev/null
@@ -0,0 +1,114 @@
+// Test basic FORMAT string traversal
+#include "../runtime/format.h"
+#include <cstdarg>
+#include <cstring>
+#include <iostream>
+#include <list>
+#include <string>
+
+using namespace Fortran::runtime;
+using namespace std::literals::string_literals;
+
+static int failures{0};
+using Results = std::list<std::string>;
+static Results results;
+
+static void handleCharacterLiteral(const char *s, std::size_t len) {
+  std::string str{s, len};
+  results.push_back("'"s + str + '\'');
+}
+
+static void handleSlash() { results.emplace_back("/"); }
+
+static void handleAbsolutePosition(int n) {
+  results.push_back("T"s + std::to_string(n));
+}
+
+static void handleRelativePosition(int n) {
+  if (n < 0) {
+    results.push_back("TL"s + std::to_string(-n));
+  } else {
+    results.push_back(std::to_string(n) + 'X');
+  }
+}
+
+static void Report(const DataEdit &edit) {
+  std::string str{edit.descriptor};
+  if (edit.repeat != 1) {
+    str = std::to_string(edit.repeat) + '*' + str;
+  }
+  if (edit.variation) {
+    str += edit.variation;
+  }
+  str += std::to_string(edit.width);
+  if (edit.digits) {
+    str += "."s + std::to_string(*edit.digits);
+  }
+  if (edit.expoDigits) {
+    str += "E"s + std::to_string(*edit.expoDigits);
+  }
+  // modes?
+  results.push_back(str);
+}
+
+// Override the Crash() in the runtime library
+void Terminator::Crash(const char *message, ...) {
+  std::va_list ap;
+  va_start(ap, message);
+  char buffer[1000];
+  std::vsnprintf(buffer, sizeof buffer, message, ap);
+  va_end(ap);
+  throw std::string{buffer};
+}
+
+static void Check(Results &expect) {
+  if (expect != results) {
+    std::cerr << "expected:";
+    for (const std::string &s : expect) {
+      std::cerr << ' ' << s;
+    }
+    std::cerr << "\ngot:";
+    for (const std::string &s : results) {
+      std::cerr << ' ' << s;
+    }
+    std::cerr << '\n';
+    ++failures;
+  }
+  expect.clear();
+  results.clear();
+}
+
+static void Test(FormatContext &context, int n, const char *format,
+    Results &&expect, int repeat = 1) {
+  MutableModes modes;
+  FormatControl control{context, format, std::strlen(format), modes};
+  try {
+    for (int j{0}; j < n; ++j) {
+      DataEdit edit;
+      control.GetNext(edit, repeat);
+      Report(edit);
+    }
+    control.FinishOutput();
+  } catch (const std::string &crash) {
+    results.push_back("Crash:"s + crash);
+  }
+  Check(expect);
+}
+
+int main() {
+  Terminator terminator{"source", 1};
+  FormatContext context{terminator, &handleCharacterLiteral, nullptr, nullptr,
+      &handleSlash, &handleAbsolutePosition, &handleRelativePosition};
+  Test(context, 1, "('PI=',F9.7)", Results{"'PI='", "F9.7"});
+  Test(context, 1, "(3HPI=F9.7)", Results{"'PI='", "F9.7"});
+  Test(context, 1, "(3HPI=/F9.7)", Results{"'PI='", "/", "F9.7"});
+  Test(context, 2, "('PI=',F9.7)", Results{"'PI='", "F9.7", "'PI='", "F9.7"});
+  Test(context, 2, "(2('PI=',F9.7),'done')",
+      Results{"'PI='", "F9.7", "'PI='", "F9.7", "'done'"});
+  Test(context, 2, "(3('PI=',F9.7,:),'tooFar')",
+      Results{"'PI='", "F9.7", "'PI='", "F9.7"});
+  Test(context, 2, "(*('PI=',F9.7,:),'tooFar')",
+      Results{"'PI='", "F9.7", "'PI='", "F9.7"});
+  Test(context, 1, "(3F9.7)", Results{"2*F9.7"}, 2);
+  return failures > 0;
+}