Browse Source

lang: string concatenation + content-copy assignment; LEN of literals

- '+': SymTab.IsStrType; SimExpr OpAdd string branch (string+string,
  or string+CHAR via DeclCharStr); QbeGen.StrCat -> shim m2strcat
  (static concat buffer; consume before concatenating again).
- String assignment is now a content copy (QbeGen.StrAssign ->
  m2strassign): up to cap chars (cap content slots + terminator),
  truncated, NUL-terminated. Replaces the count-checked CopyArray that
  trapped on mismatch; t_trap_count/t_trap_emptystr removed and
  replaced by t_strtrunc/t_strempty.
- LEN/LENGTH fold for string literals (content length); arrays
  unchanged (capacity).
- expect_run/expect_trap now link shim.c.
- tests: t_strcat (63) -> 128/128. Fixpoint OK (2,080,607 bytes).
- docs updated.
Eric Streit 1 tuần trước cách đây
mục cha
commit
259f5aca80

+ 6 - 5
compiler/run_tests.sh

@@ -2,6 +2,7 @@
 # Step-1 regression: M2 -> gen_ssa/*.ssa -> qbe -> cc -> exit code.
 # Usage: ./run_tests.sh   (from the compiler directory)
 pass=0; fail=0
+SHIM="../runtime/syslib/shim.c"
 
 modof() {
   sed -n 's/^MODULE \([A-Za-z][A-Za-z0-9]*\).*/\1/p' "tests/$1" | head -n 1
@@ -20,7 +21,7 @@ expect_run() {
   if ! qbe -o "gen_ssa/$mod.s" "gen_ssa/$mod.ssa" 2> "gen_ssa/$mod.qbeerr"; then
     fail=$((fail+1)); echo "FAIL(run): $name qbe:"; cat "gen_ssa/$mod.qbeerr"; return
   fi
-  if ! cc "gen_ssa/$mod.s" -o "gen_ssa/$mod" 2> "gen_ssa/$mod.ccerr"; then
+  if ! cc "gen_ssa/$mod.s" "$SHIM" -o "gen_ssa/$mod" 2> "gen_ssa/$mod.ccerr"; then
     fail=$((fail+1)); echo "FAIL(run): $name cc:"; cat "gen_ssa/$mod.ccerr"; return
   fi
   timeout 10 "gen_ssa/$mod" > /dev/null 2>&1; got=$?
@@ -94,6 +95,7 @@ expect_run t_ptrnest.mod 10
 expect_fail t_opaqueptr.mod ""
 expect_run t_enumdecl.mod 0
 expect_run t_enum.mod 15
+expect_run t_strcat.mod 63
 expect_run t_proc.mod 0
 expect_run t_forward.mod 0
 expect_run t_call.mod 125
@@ -147,7 +149,7 @@ expect_trap() {
   if ! qbe -o "gen_ssa/$mod.s" "gen_ssa/$mod.ssa" 2> "gen_ssa/$mod.qbeerr"; then
     fail=$((fail+1)); echo "FAIL(trap): $name qbe"; return
   fi
-  if ! cc "gen_ssa/$mod.s" -o "gen_ssa/$mod" 2> "gen_ssa/$mod.ccerr"; then
+  if ! cc "gen_ssa/$mod.s" "$SHIM" -o "gen_ssa/$mod" 2> "gen_ssa/$mod.ccerr"; then
     fail=$((fail+1)); echo "FAIL(trap): $name cc"; return
   fi
   timeout 10 "gen_ssa/$mod" > /dev/null 2>&1; got=$?
@@ -159,9 +161,9 @@ expect_trap() {
 }
 
 expect_trap t_trap_index.mod
-expect_trap t_trap_count.mod
+expect_run t_strtrunc.mod 42
 expect_trap t_trap_setspan.mod
-expect_trap t_trap_emptystr.mod
+expect_run t_strempty.mod 42
 expect_trap t_trap_chr8.mod
 expect_fail t_defunit.mod ""
 expect_fail t_implunit.mod ""
@@ -169,7 +171,6 @@ expect_run t_nested.mod 42
 
 # --- separate compilation (step 4.3): $1 prog module, $2 exit code,
 #     then the session's source files (program last by convention) ---
-SHIM="../runtime/syslib/shim.c"
 
 # Build a session image; echoes 0 on success, non-zero on failure.
 build_session() {

+ 68 - 6
compiler/src/M2.atg

@@ -908,8 +908,14 @@ PRODUCTIONS
                                               OR (dk = SymTab.KindField) THEN
                                              IF SymTab.ClassOf(dt) =
                                                 SymTab.ClArray THEN
-                                               QbeGen.CopyArray(qd, qe,
-                                                 dt)
+                                               IF SymTab.StrCompat(dt,
+                                                    et) THEN
+                                                 QbeGen.StrAssign(qd,
+                                                   qe)
+                                               ELSE
+                                                 QbeGen.CopyArray(qd,
+                                                   qe, dt)
+                                               END
                                              ELSIF SymTab.ClassOf(dt) =
                                                  SymTab.ClSet THEN
                                                QbeGen.CopySet(qd, qe,
@@ -936,7 +942,13 @@ PRODUCTIONS
                                              END
                                            ELSIF SymTab.ClassOf(dt) =
                                                  SymTab.ClArray THEN
-                                             QbeGen.CopyArray(qd, qe, dt)
+                                             IF SymTab.StrCompat(dt,
+                                                  et) THEN
+                                               QbeGen.StrAssign(qd, qe)
+                                             ELSE
+                                               QbeGen.CopyArray(qd, qe,
+                                                 dt)
+                                             END
                                            ELSIF SymTab.ClassOf(dt) =
                                                  SymTab.ClSet THEN
                                              QbeGen.CopySet(qd, qe,
@@ -1788,6 +1800,25 @@ PRODUCTIONS
                QbeGen.CopyOp(qr, q)
              ELSE QbeGen.DelayFlush; QbeGen.CopyOp("0", q)
              END
+           ELSIF (op = SymTab.OpAdd)
+             AND (SymTab.StrCompat(t, t2)
+               OR (SymTab.IsStrType(t)
+                 AND (SymTab.ClassOf(t2) = SymTab.ClChar))
+               OR ((SymTab.ClassOf(t) = SymTab.ClChar)
+                 AND SymTab.IsStrType(t2))) THEN
+             (* string concatenation; a CHAR operand becomes a
+                1-character string literal *)
+             IF SymTab.StrCompat(t, t2) THEN
+               QbeGen.StrCat(q, q2, qt)
+             ELSIF SymTab.IsStrType(t) THEN
+               QbeGen.DeclCharStr(q2, qs);
+               QbeGen.StrCat(q, qs, qt)
+             ELSE
+               QbeGen.DeclCharStr(q, qs);
+               QbeGen.StrCat(qs, q2, qt)
+             END;
+             t := SymTab.NewStr();
+             QbeGen.CopyOp(qt, q)
            ELSIF (t # SymTab.InvalidType) AND (t2 # SymTab.InvalidType)
  AND (SymTab.ClassOf(t) = SymTab.ClSet)
  AND (SymTab.ClassOf(t2) = SymTab.ClSet) THEN
@@ -1974,7 +2005,7 @@ PRODUCTIONS
                                              vt: SymTab.TypeIndex;
                                              c1, c2: INTEGER;
                                              called, isHigh, sfx, isCh,
-                                             isU, uok: BOOLEAN;
+                                             isU, uok, isStr: BOOLEAN;
                                              ucp: INTEGER; .)
     = integer                           (. LexString(s);
                                            QbeGen.NormInt(s, q);
@@ -2085,8 +2116,39 @@ PRODUCTIONS
                                            END; .)
     | ( "HIGH"                          (. isHigh := TRUE; .)
       | ( "LEN" | "LENGTH" )            (. isHigh := FALSE; .) )
-      "(" Design<dt, dk, qd, qn, sfx> ")"
-                                        (. IF dt = SymTab.InvalidType THEN
+      "(" ( Design<dt, dk, qd, qn, sfx> (. isStr := FALSE; .)
+          | string                       (. LexString(s);
+                                            isStr := TRUE;
+                                            IF SymTab.StrLen(s) = 3 THEN
+                                              dt := SymTab.CharType();
+                                              QbeGen.IntStr(QbeGen.CharVal(s),
+                                                qd)
+                                            ELSE
+                                              dt := SymTab.NewStr();
+                                              QbeGen.DeclStr(s, qd);
+                                              QbeGen.NoteAddr(qd, qd)
+                                            END;
+                                            dk := -1;
+                                            qn[0] := CHR(0); .) )
+      ")"
+                                        (. IF isStr THEN
+                                             (* fold: content length at
+                                                compile time *)
+                                             IF SymTab.StrLen(s) = 3 THEN
+                                               c1 := 1
+                                             ELSE
+                                               c1 :=
+                                                 SymTab.StrLen(s) - 2
+                                             END;
+                                             IF isHigh THEN
+                                               DEC(c1)
+                                             END;
+                                             QbeGen.IntStr(c1, qr);
+                                             t := SymTab.IntType();
+                                             QbeGen.CopyOp(qr, q)
+                                           ELSIF dt = SymTab.InvalidType THEN
+                                             t := SymTab.InvalidType;
+                                             QbeGen.CopyOp("0", q)
                                            ELSIF SymTab.ClassOf(dt) #
                                                  SymTab.ClArray THEN
                                              SemError(217);

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 1486 - 1424
compiler/src/M2.lst


+ 9 - 0
compiler/src/QbeGen.def

@@ -349,6 +349,15 @@ PROCEDURE SetBinOp (sel: INTEGER; l: ARRAY OF CHAR; r: ARRAY OF CHAR;
 PROCEDURE StrEq (op: INTEGER; l, r: ARRAY OF CHAR; VAR q: QVal);
 (* String equality (`=`/`OpNeq`) via the shim; result BOOLEAN. *)
 
+PROCEDURE StrLen (s: ARRAY OF CHAR; VAR q: QVal);
+(* q := the string length (characters before the NUL), INTEGER. *)
+
+PROCEDURE StrAssign (dst, src: ARRAY OF CHAR);
+(* Content copy of a string into a CHAR array (NUL-terminated). *)
+
+PROCEDURE StrCat (a, b: ARRAY OF CHAR; VAR q: QVal);
+(* q := a + b in the shim's concat buffer (a descriptor address). *)
+
 PROCEDURE CmpSet (op: INTEGER; l: ARRAY OF CHAR; r: ARRAY OF CHAR;
                   lw, rw: CARDINAL; VAR q: QVal);
 (* =/# over min words plus zero-checks on the larger side's extras;

+ 21 - 0
compiler/src/QbeGen.mod

@@ -1602,6 +1602,27 @@ PROCEDURE StrEq (op: INTEGER; l, r: ARRAY OF CHAR; VAR q: QVal);
     END
   END StrEq;
 
+PROCEDURE StrLen (s: ARRAY OF CHAR; VAR q: QVal);
+(* q := the number of characters before the NUL (INTEGER). *)
+  BEGIN
+    NewTemp(q);
+    W("  "); W(q); W(" =w call $m2strlen(l "); W(s); WL(")")
+  END StrLen;
+
+PROCEDURE StrAssign (dst, src: ARRAY OF CHAR);
+(* Copy the string src into the CHAR array at dst (content copy,
+   truncated to the destination capacity, NUL-terminated). *)
+  BEGIN
+    W("  call $m2strassign(l "); W(dst); W(", l "); W(src); WL(")")
+  END StrAssign;
+
+PROCEDURE StrCat (a, b: ARRAY OF CHAR; VAR q: QVal);
+(* q := a + b (a descriptor in the shim's concat buffer). *)
+  BEGIN
+    NewTemp(q);
+    W("  "); W(q); W(" =l call $m2strcat(l "); W(a); W(", l "); W(b); WL(")")
+  END StrCat;
+
 (* ---------------- arrays: length-prefixed layout ---------------- *)
 (* Indexes and counts are LONGCARD (l) in emitted code; immediates
    pass through, w-temps widen via extsw. Traps call $abort + hlt

+ 3 - 0
compiler/src/SymTab.def

@@ -466,6 +466,9 @@ PROCEDURE BoolCheck (t: TypeIndex): BOOLEAN;
 PROCEDURE RelCheck (l, r: TypeIndex; op: INTEGER): BOOLEAN;
 (* = # <> < <= > >= IN (213/222 chosen by caller via op). *)
 
+PROCEDURE IsStrType (t: TypeIndex): BOOLEAN;
+(* A string literal (ClStr) or a 1-D ARRAY OF CHAR. *)
+
 PROCEDURE StrCompat (l, r: TypeIndex): BOOLEAN;
 (* Both operands are string-compatible (ClStr / 1-D ARRAY OF CHAR):
    `=`/`#` compare content via the shim. *)

+ 23 - 0
compiler/tests/t_strcat.mod

@@ -0,0 +1,23 @@
+MODULE TStrCat;
+(* String concatenation `+` (arrays and literals, including a single
+   CHAR operand) and string length. Exit 63. *)
+VAR ExitCode : INTEGER;
+VAR a : ARRAY [0 .. 31] OF CHAR;
+VAR b, c : ARRAY [0 .. 7] OF CHAR;
+BEGIN
+  b[0] := "a"; b[1] := "b"; b[2] := CHR(0);
+  c[0] := "c"; c[1] := "d"; c[2] := CHR(0);
+
+  a := b + c;                     (* array + array -> "abcd" *)
+  IF a = "abcd" THEN ExitCode := ExitCode + 1 ELSE ExitCode := 100 END;
+
+  a := "xy" + "z";                (* literal + CHAR -> "xyz" *)
+  IF a = "xyz" THEN ExitCode := ExitCode + 2 ELSE ExitCode := 100 END;
+
+  a := "hello";                   (* string assignment (was a trap) *)
+  IF a = "hello" THEN ExitCode := ExitCode + 4 ELSE ExitCode := 100 END;
+
+  IF LEN(a) = 32 THEN ExitCode := ExitCode + 8 ELSE ExitCode := 100 END;
+  IF LEN("hello") = 5 THEN ExitCode := ExitCode + 16 ELSE ExitCode := 100 END;
+  IF LENGTH("abc") = 3 THEN ExitCode := ExitCode + 32 ELSE ExitCode := 100 END
+END TStrCat.

+ 10 - 0
compiler/tests/t_strempty.mod

@@ -0,0 +1,10 @@
+MODULE TStrEmpty;
+(* Empty string assignment: the destination becomes empty (NUL at 0),
+   even when it previously held text. Exit 42. *)
+VAR ExitCode : INTEGER;
+VAR s : ARRAY [0 .. 2] OF CHAR;
+BEGIN
+  s[0] := "x"; s[1] := "y"; s[2] := CHR(0);
+  s := "";
+  IF (s[0] = CHR(0)) AND (s = "") THEN ExitCode := 42 ELSE ExitCode := 1 END
+END TStrEmpty.

+ 9 - 0
compiler/tests/t_strtrunc.mod

@@ -0,0 +1,9 @@
+MODULE TStrTrunc;
+(* String assignment copies content into a CHAR array, truncated to the
+   destination capacity and NUL-terminated (no count trap). Exit 42. *)
+VAR ExitCode : INTEGER;
+VAR s : ARRAY [0 .. 2] OF CHAR;
+BEGIN
+  s := "abcd";                    (* capacity 3 -> "abc" *)
+  IF s = "abc" THEN ExitCode := 42 ELSE ExitCode := 1 END
+END TStrTrunc.

+ 0 - 7
compiler/tests/t_trap_count.mod

@@ -1,7 +0,0 @@
-MODULE TTrapCount;
-VAR s : ARRAY [0..2] OF CHAR;
-VAR ExitCode : INTEGER;
-BEGIN
-  s := "abcd";
-  ExitCode := 0
-END TTrapCount.

+ 0 - 7
compiler/tests/t_trap_emptystr.mod

@@ -1,7 +0,0 @@
-MODULE TTrapEmptyStr;
-VAR s : ARRAY [0..2] OF CHAR;
-VAR ExitCode : INTEGER;
-BEGIN
-  s := "";
-  ExitCode := 0
-END TTrapEmptyStr.

+ 9 - 6
docs/features.md

@@ -1,4 +1,4 @@
-# m2compiler-V3 — feature status (at `v3-programargs-iochan`, 126/126 green)
+# m2compiler-V3 — feature status (at `v3-strcat`, 128/128 green)
 
 Pipeline: Coco/R `M2.atg` (1748 lines, 73 productions) → `gm2`-built
 `M2` → QBE `.ssa` → `qbe` → `cc` → run. `SymTab.mod` 1623 lines,
@@ -42,16 +42,19 @@ Legend: ✅ done · 🔄 partial · ⏸ not started / deferred.
   `WHILE`, `REPEAT/UNTIL`, `LOOP/EXIT`, `FOR/TO/BY` (static sign),
   `WITH` (single/multi/nested), `RETURN` (232-checked),
   `NEW`/`DISPOSE` (builtins), bare and parenthesized calls.
-- ✅ `HIGH`; ⏸ `LEN`, `GOTO`/labels.
+- ✅ `HIGH`/`LEN`; ⏸ `GOTO`/labels.
 
 ## Expressions
 - ✅ Full precedence, `+ - * / DIV MOD`, comparisons, eager
   `AND`/`OR`/`NOT`, `IN`, set ops (`+ - * /` = union/diff/inter/symdiff),
   pointer `=`/`#`, int→real conversion, `^` deref, indexing, field
   access, qualified `L.x`/`L.P`.
+- ✅ String `=`/`#` (content), string `+` (concatenation, incl. a
+  `CHAR` operand), string assignment (content copy, truncated to the
+  array capacity, NUL-terminated), `LEN`/`LENGTH` of arrays and string
+  literals.
 - ✅ `NIL` comparisons/assignment.
-- ✅ String `=`/`#` (content comparison via the shim). `VAL`/`ADR`/
-  `TSIZE`/`SIZE` work. ⏸ String `+` (concat), `LEN`, result suffixes
+- ✅ `VAL`/`ADR`/`TSIZE`/`SIZE`/`HIGH`/`LEN` work. ⏸ Result suffixes
   (`F()^`).
 
 ## Procedures & modules
@@ -88,7 +91,7 @@ Legend: ✅ done · 🔄 partial · ⏸ not started / deferred.
 ## Not started
 - ⏸ Clarion `CLASS` lowering (declared + checked only); Unicode
   `UString` whole-string assignment; strict `231` def/impl signature
-  checks; string `+`; `IOChan` beyond the standard streams. (ISO
+  checks; `IOChan` beyond the standard streams; variant records. (ISO
   `ConvResults` enumerations are now expressible.) The TopSpeed
   legacy grammar (`TopSpeed-V3-M2.atg`) is a separate sidecar, not
   merged.
@@ -97,6 +100,6 @@ Legend: ✅ done · 🔄 partial · ⏸ not started / deferred.
 byte-identical compiler image; `stdlib/` + `runtime/syslib` are in
 use. See `docs/summary_selfhosting.md`.)
 
-Next: string concatenation, strict `231` def/impl signature checks,
+Next: strict `231` def/impl signature checks, variant records,
 `IOChan` over files. Roadmap: `docs/plan.md`,
 `docs/blaise-phases-map.md`.

+ 72 - 0
docs/summary_strcat.md

@@ -0,0 +1,72 @@
+# Step: string concatenation + string assignment/LEN polish
+
+Tag `v3-strcat`. Suite **128/128**; fixpoint **OK**
+(`bootstrap/fixpoint.sh`, image **2,080,607 bytes**).
+
+## String concatenation `+`
+
+- `SymTab.IsStrType(t)` (exported): a string literal (`ClStr`) or a
+  1-D `ARRAY OF CHAR`.
+- The `SimExpr` action gained an `OpAdd` string branch before the
+  arithmetic path: `a + b` where both are string-compatible, or where
+  one is a string and the other a `CHAR` (the char becomes a
+  one-character string literal via `QbeGen.DeclCharStr`). The result
+  type is `NewStr()` (a string literal value).
+- `QbeGen.StrCat` emits `%t =l call $m2strcat(l a, l b)`; the shim
+  concatenates into a static buffer and returns its descriptor
+  address. **The value is valid until the next concatenation**, so it
+  must be consumed (assigned/compared/passed) before another one.
+- `shim.c`: `m2strcat`.
+
+## String assignment is now a content copy
+
+Previously `a := "…"` did a whole-array *copy* with a runtime count
+check, so any length mismatch **trapped** (the old
+`t_trap_count`/`t_trap_emptystr` tests). It now copies content:
+
+- `QbeGen.StrAssign` emits `call $m2strassign(l dst, l src)`.
+- `shim.c`: `m2strassign` copies up to `cap` characters (a CHAR array
+  has `cap` content slots plus a reserved terminator at index `cap`),
+  truncates to fit, and NUL-terminates.
+- `a := "abcd"` into `ARRAY [0..2] OF CHAR` now yields `"abc"`
+  (truncated); `a := ""` clears the destination.
+- Assignment also works when the RHS is a `+` concatenation (result
+  type `ClStr`).
+
+## `LEN` / `LENGTH` on string literals
+
+`LEN("hello")` / `LENGTH("abc")` now fold to the content length at
+compile time; the non-literal forms were already supported (`LEN` of
+an array is its capacity, `LEN` of an open array reads the count).
+
+## Tests
+
+- `t_strcat.mod` (exit 63): `array + array`, `literal + CHAR`,
+  string assignment, `LEN(array)`/`LEN("hello")`/`LENGTH("abc")`.
+- `t_strtrunc.mod` (exit 42): `"abcd"` into capacity 3 → `"abc"`.
+- `t_strempty.mod` (exit 42): `""` clears the destination.
+- `t_trap_count.mod` / `t_trap_emptystr.mod` **removed** (obsolete).
+- `expect_run`/`expect_trap` now link `shim.c` (string assignment calls
+  into it).
+
+## Notes / caveats
+
+- `"x" + "y"` is accepted through the CHAR rule (single-character
+  literals are `CHAR`, as in classic Modula-2, so they are not
+  `ClStr`).
+- The concat buffer is a single static 8 KiB; nested/interleaved
+  concatenations must be consumed in order. This is sufficient for
+  ordinary expressions (`("a" + "b") + "c"` is fine: each result is
+  consumed by the next `+` immediately).
+- `LEN` of an array remains the capacity, not the current
+  NUL-terminated length (use the shim-backed `Strings.Length`/`LEN`
+  of the *string* for that).
+
+## Files
+
+`compiler/src/M2.atg` (string `+`, string-assign branches, `LEN`
+literal), `compiler/src/QbeGen.def`/`.mod` (`StrCat`/`StrAssign`/
+`StrLen`), `compiler/src/SymTab.def` (`IsStrType`),
+`runtime/syslib/shim.c` (`m2strcat`/`m2strassign`/`m2strlen`),
+`compiler/run_tests.sh`, `compiler/tests/{t_strcat,t_strtrunc,
+t_strempty}.mod`, `docs/features.md`.

+ 43 - 0
runtime/syslib/shim.c

@@ -280,6 +280,49 @@ long m2streq(long *a, long *b)
     return (strcmp((char *)(a + 1), (char *)(b + 1)) == 0) ? 1 : 0;
 }
 
+/* Number of characters before the NUL. */
+long m2strlen(long *s)
+{
+    return (long)strlen((char *)(s + 1));
+}
+
+/* Copy the content of src into the CHAR array at dst (capacity
+   dst[0]); truncated to fit and NUL-terminated.  A CHAR array has
+   `cap` content slots plus a reserved terminator slot at index `cap`,
+   so up to `cap` characters fit.  Returns the length copied. */
+long m2strassign(long *dst, long *src)
+{
+    char *d = (char *)(dst + 1);
+    const char *s = (const char *)(src + 1);
+    long cap = dst[0];
+    size_t n;
+    if (cap < 0) return 0;
+    n = strlen(s);
+    if (n > (size_t)cap) n = (size_t)cap;
+    memmove(d, s, n);
+    d[n] = 0;
+    return (long)n;
+}
+
+/* Concatenate two string descriptors into a static buffer; returns the
+   result descriptor address. The value is valid until the next
+   m2strcat call (so copy it before concatenating again). */
+long m2strcat(long *a, long *b)
+{
+    static struct { long count; char data[8192]; } buf;
+    const char *sa = (const char *)(a + 1);
+    const char *sb = (const char *)(b + 1);
+    size_t na = strlen(sa), nb = strlen(sb);
+    if (na > sizeof buf.data - 1) na = sizeof buf.data - 1;
+    if (nb > sizeof buf.data - 1 - na)
+        nb = sizeof buf.data - 1 - na;
+    memmove(buf.data, sa, na);
+    memmove(buf.data + na, sb, nb);
+    buf.data[na + nb] = 0;
+    buf.count = (long)(na + nb);
+    return (long)&buf;
+}
+
 /* ---------------- reals (Conversions / RealIO) ---------------- */
 
 /* Format a REAL into a descriptor string (6 significant digits,

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác