cobcd.py 118 KB
Newer Older
1
#######################################################################
rdubner's avatar
rdubner committed
2
# Copyright (c) 2019-2020 COBOLworx
3
4
5
6
7
8
9
10
11
12
13
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#    * Redistributions of source code must retain the above copyright
#      notice, this list of conditions and the following disclaimer.
#    * Redistributions in binary form must reproduce the above copyright
#      notice, this list of conditions and the following disclaimer in
#      the documentation and/or other materials provided with the
#      distribution.
rdubner's avatar
rdubner committed
14
#    * Neither the name of the COBOLworx nor the names of its
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#      contributors may be used to endorse or promote products derived
#      from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
29
30
#   This module implements the gdb extension commands cprint and cwatch
#EndOfCopyright########################################################
31

32
33
34
35
36
37
from __future__ import print_function

import re
import sys
import os
import traceback
38
import datetime
39
40
import subprocess

41

42
#   This module implements the gdb extension commands cprint and cwatch
43
#
44
#   This script is read by the COBST program and incorporated into a
45
#   C module that gets compiled and linked into the final executable
46
47
48
49
50
51
#
#   Decoding a COBOL variable will require the following information,
#   taken from ...\gnucobol-3.0-rc1\libcob\common.h
#
#   This structure will have an f_ symbol pointing to it.
#
52
#       typedef struct __cob_field
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#           {
#           size_t                  size;       /* Field size */
#           unsigned char           *data;      /* Pointer to field data */
#           const cob_field_attr    *attr;      /* Pointer to attribute */
#           } cob_field;
#
#   cob_field.attr points to this structure, which will have an 'a_' symbol
#
#       typedef struct __cob_field_attr
#           {
#           unsigned short          type;       /* Field type */
#           unsigned short          digits;     /* Digit count */
#           signed short            scale;      /* Field scale */
#           unsigned short          flags;      /* Field flags */
#           const cob_pic_symbol    *pic;       /* Pointer to picture string */
#           } cob_field_attr;
#
#   cob_field_attr.pic, if present, will have a 'p_' symbol pointing to
#
#       typedef struct __cob_pic_symbol
#           {
#           char    symbol;
#           int     times_repeated;
#           } cob_pic_symbol;

#
#   /* Field types */
#
COB_TYPE_UNKNOWN             =  0x00    # 0
COB_TYPE_GROUP               =  0x01    # 1
COB_TYPE_BOOLEAN             =  0x02    # 2
COB_TYPE_NUMERIC             =  0x10    # 16
COB_TYPE_NUMERIC_DISPLAY     =  0x10    # 16
COB_TYPE_NUMERIC_BINARY      =  0x11    # 17
COB_TYPE_NUMERIC_PACKED      =  0x12    # 18
COB_TYPE_NUMERIC_FLOAT       =  0x13    # 19
COB_TYPE_NUMERIC_DOUBLE      =  0x14    # 20
COB_TYPE_NUMERIC_L_DOUBLE    =  0x15    # 21
COB_TYPE_NUMERIC_FP_DEC64    =  0x16    # 22
COB_TYPE_NUMERIC_FP_DEC128   =  0x17    # 23
COB_TYPE_NUMERIC_FP_BIN32    =  0x18    # 24
COB_TYPE_NUMERIC_FP_BIN64    =  0x19    # 25
COB_TYPE_NUMERIC_FP_BIN128   =  0x1A    # 26
COB_TYPE_NUMERIC_COMP5       =  0x1B    # 27
COB_TYPE_ALNUM               =  0x20    # 32
COB_TYPE_ALPHANUMERIC        =  0x21    # 33
COB_TYPE_ALPHANUMERIC_ALL    =  0x22    # 34
COB_TYPE_ALPHANUMERIC_EDITED =  0x23    # 35
COB_TYPE_NUMERIC_EDITED      =  0x24    # 36
COB_TYPE_NATIONAL            =  0x40    # 48
COB_TYPE_NATIONAL_EDITED     =  0x41    # 49

#
#   /* Field flags */
#
COB_FLAG_HAVE_SIGN      = (1 << 0)   # /* 0x0001 */
COB_FLAG_SIGN_SEPARATE  = (1 << 1)   # /* 0x0002 */
COB_FLAG_SIGN_LEADING   = (1 << 2)   # /* 0x0004 */
COB_FLAG_BLANK_ZERO     = (1 << 3)   # /* 0x0008 */
COB_FLAG_JUSTIFIED      = (1 << 4)   # /* 0x0010 */
COB_FLAG_BINARY_SWAP    = (1 << 5)   # /* 0x0020 */
COB_FLAG_REAL_BINARY    = (1 << 6)   # /* 0x0040 */
COB_FLAG_IS_POINTER     = (1 << 7)   # /* 0x0080 */
COB_FLAG_NO_SIGN_NIBBLE = (1 << 8)   # /* 0x0100 */
COB_FLAG_IS_FP          = (1 << 9)   # /* 0x0200 */
COB_FLAG_REAL_SIGN      = (1 << 10)  # /* 0x0400 */
COB_FLAG_BINARY_TRUNC   = (1 << 11)  # /* 0x0800 */
COB_FLAG_CONSTANT       = (1 << 12)  # /* 0x1000 */

122
123
124
125
126
127
128
129
def NoNulls(s) :
    ss = ""
    for ch in s:
        if ord(ch) < 32 or ord(ch) >= 128 :
            ch = '.'
        ss += ch
    return ss

130
131
132
def LeftRight(s,splitchar) :
    """Splits what is presumably a numeric string with a possible
    splitting character into left and right pieces"""
rdubner's avatar
rdubner committed
133
134
135
136
137
138
    left = ""
    right = ""
    i = 0
    while i < len(s) :
        ch = s[i]
        i += 1
139
        if ch == splitchar :
rdubner's avatar
rdubner committed
140
141
142
143
144
145
146
147
            break
        left += ch
    while i < len(s) :
        ch = s[i]
        i += 1
        right += ch
    return left, right

148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
def Picture(s) :
    """ Expands a 9(3)V9(2) to 999V999 """
    r = ""
    lch = ''
    ch = ''
    i = 0
    ncount = 0
    while i < len(s) :
        ch = s[i].upper()
        i += 1
        if ch == '(' :
            ncount = 0
            while i < len(s) :
                ch = s[i]
                i += 1
                if ch == ')' :
                    break;
                ncount *= 10
                ncount += ord(ch) - ord('0')
            ncount -= 1
            while ncount > 0 :
                ncount -= 1
                r += lch
        else :
            lch = ch
            r += ch
    # To avoid certain types of twitching on the part of your poor beleagured
    # programmer, replace the digrams 'CR' and 'DB' with 'c' and 'd'.  Note
    # that CR or DB, if present, are the final characters of the string.

    n = r.find("DB")
    if n > -1 :
        r = r[:n] + 'd'
    n = r.find("CR")
    if n > -1 :
        r = r[:n] + 'c'

    return r

187
188
189
190
191
192
193
194
195
def MachineInterfaceBody(s) :
    ### convert display_body for machine interface:
    body = s
    body_length = 0
    is_quoted = False

    # Strip off leading and trailing square brackets, which is what sometimes
    # get before some variable types (e.g., file records) are initialized
    #if len(body) >=2 and body[0] == '[' :
196
    #    body = body[1:-1]
197
198
199
200

    # Strip off leading and trailing double-quotes
    if len(body) >=2 and body[0] == '"' :
        is_quoted = True
201
        body = body[1:-1]
202
203
204
205
206
207
208
209
210
211
212
213
214

    # Whatever is left is now the actual number of characters
    body_length = len(body)

    # Prefix double-quotes and backslashes with backslashes:
    temp = ""
    for ch in body :
        if ch == '"'  or ch == '\\':
            temp += '\\'
        temp += ch

    return body , body_length

215
216
217
218
219
220
221
222
223
224
225
class VarTrieNode() :
    def __init__(self):
        self.parent = None
        self.children = {}
        self.piece_of_name = ""
        self.count = 0
        self.payload_index = None

    def AddCanonicalName(self,name) :
        pass

rdubner's avatar
rdubner committed
226
227
228
229
230
def BlankIsZero(v) :
    if len(v) == 0 :
        v = "0"
    return int(v)

231
def GetAddressOf(variable) :
232
233
234
235
236
237
238
239
240
241
242
243
244
    if 'gdb' in sys.modules :
        try :
            command = "info address " + variable
            address_string = gdb.execute(command,False,True)

            nfound = address_string.find(" 0x")
            if nfound == -1 :
                print("No match in GetAddressOf()", command)
                return None
            else :
                address_string = address_string[nfound+3:]
                nfound = address_string.find(' ')
                address_string = address_string[0:nfound]
245
                address = int(address_string,16)
246
247
248
249
250
251
252
253
                return address
        except gdb.error:
            print("gdb.execute failed:",command)
            return None
    else :
        # We are running off-line
        return None

254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
class VarTrie() :
    def __init__(self):
        self.storage_list = []
        self.storage_trie = VarTrieNode()
        self.program_ids = set()

    def AddToTrie(self,trie,name_tokens,payload_index) :
        our_node = trie

        for name in name_tokens :
            if name not in our_node.children :
                new_child = VarTrieNode()
                new_child.piece_of_name = name
                new_child.parent = our_node
                our_node.children[name] = new_child
            our_node = our_node.children[name]

        # We're all the way at the end:
        our_node.payload_index = payload_index
        our_node.count = 1

        # Propogate the count back up to the root:
        while our_node.parent :
            our_node.parent.count += our_node.count
            our_node = our_node.parent

        # and we are done

rdubner's avatar
rdubner committed
282
    def LoadFromLine(self,variable_line) :
rdubner's avatar
rdubner committed
283
        if len(variable_line) >= 1 and variable_line[0] == "D" :
rdubner's avatar
rdubner committed
284
285
286
            indexSection=1
            indexLevel=2
            indexName=3
287
288
289
290
291
292
293
294
295
296
297
298
            indexField=4
            indexBase=5
            indexOffset=6
            indexListType=7
            indexListSize=8
            indexPicture=9
            indexUsage=10
            indexOccurs=11
            indexAttrType=12
            indexAttrDigits=13
            indexAttrScale=14
            indexAttrFlags=15
rdubner's avatar
rdubner committed
299
300
301
302
303
304

            tokens = variable_line.split("|")
            name_tokens =  tokens[indexName].split("/")
            if len(name_tokens) >= 1 :
                payload = CobolVariable()

305
                payload.Program    = name_tokens[-1]
rdubner's avatar
rdubner committed
306
                payload.Section    = tokens[indexSection]
307
                payload.Name       = tokens[indexName]
rdubner's avatar
rdubner committed
308
                payload.Level      = int(BlankIsZero(tokens[indexLevel]))
rdubner's avatar
rdubner committed
309
                payload.Base       = tokens[indexBase]
310
                payload.Field      = tokens[indexField]
rdubner's avatar
rdubner committed
311
                payload.Offset     = int(BlankIsZero(tokens[indexOffset]))
rdubner's avatar
rdubner committed
312
                payload.ListType   = tokens[indexListType]
rdubner's avatar
rdubner committed
313
                payload.ListSize   = int(BlankIsZero(tokens[indexListSize]))
rdubner's avatar
rdubner committed
314
315
                payload.Picture    = tokens[indexPicture]
                payload.Usage      = tokens[indexUsage]
rdubner's avatar
rdubner committed
316
317
318
319
320
321
                payload.Occurs     = int(BlankIsZero(tokens[indexOccurs]))
                payload.FieldSize  = int(BlankIsZero(tokens[indexListSize]))
                payload.AttrType   = int(BlankIsZero(tokens[indexAttrType]))
                payload.AttrDigits = int(BlankIsZero(tokens[indexAttrDigits]))
                payload.AttrScale  = int(BlankIsZero(tokens[indexAttrScale]))
                payload.AttrFlags  = int(BlankIsZero(tokens[indexAttrFlags]))
rdubner's avatar
rdubner committed
322
323
324
325
326
327
328

                if payload.Section == "IN" :
                    payload.Section = "INPUT-OUTPUT"
                elif payload.Section == "WO" :
                    payload.Section = "WORKING-STORAGE"
                elif payload.Section == "LO" :
                    payload.Section = "LOCAL-STORAGE"
rdubner's avatar
rdubner committed
329
                elif payload.Section == "LI" :
rdubner's avatar
rdubner committed
330
331
332
333
334
335
336
337
338
339
340
341
342
                    payload.Section = "LINKAGE"

                if payload.ListType == "A" :
                    payload.ListType == "ALPHANUMERIC"
                if payload.ListType == "N" :
                    payload.ListType == "NUMERIC"
                if payload.ListType == "G" :
                    payload.ListType == "GROUP"
                if payload.ListType == "F" :
                    payload.ListType == "FILE"

                # If the COBOL source code doesn't refer to a variable,
                # then we don't have much information about it.  But
343
                # we can look at the payload.ListType and at least
rdubner's avatar
rdubner committed
344
345
346
                # identify ALPHANUMERICS:

                if payload.ListType in ("ALPHABETIC","ALPHANUMERIC") :
347
                    payload.AttrType = COB_TYPE_ALPHANUMERIC ;
rdubner's avatar
rdubner committed
348
349
350
351
352
353
354
355
356
357
358
359
360

                # Save the location of this payload in the _storage
                # list.  It's ordered, so we can make use of that when
                # displaying the contents of grouped variables.  We also
                # only keep the payloads themselves in the
                # storage list, and otherwise deal in indexes into that
                # list.
                payload.payload_index = len(self.storage_list)
                self.storage_list.append(payload)
                self.AddToTrie(self.storage_trie,
                                name_tokens,
                                payload.payload_index)
                self.program_ids.add(payload.Program)
361
362
363
364
365
366
367
368
369
370
371
372
373

    def TokenizeCanonicalName(self,desired) :
        # We allow "A of B in C", where [of|in] is not case sensitive
        # We also allow A.B.C, A/B/C, and "A B C"

        # turn IN and OF into spaces
        pattern = re.compile(r"^(.*) (in|In|iN|IN|of|oF|Of|OF) (.*)$")
        while True :
            match = re.search(pattern,desired)
            if not match :
                break
            else :
                desired = match.group(1) + " " + match.group(3)
374

375
376
377
378
379
380
        desired = desired.replace("."," ")
        desired = desired.replace("/"," ")
        retval = desired.split()
        return retval

    def NameMatcher(self,
381
382
383
384
                    boys,
                    boy_index,
                    trie,
                    paths) :
385
        # Nomenclature:  We have a list of lost_boys: A/B/C
386
387
        # We have a forest, where some of the trees are labeled.
        # The labels might be
388
389
390
391
392
393
394
        # A.B.C (Just one valid path)
        # X.A.B.C.Y (Just one valid path)
        # A.B.C.X.Y.Z (Just one valid path)
        # A.X.B.Y.C.Z (Just one path)
        # A.B.C.X and A.B.C.Y (two valid paths)
        # A.B.X.C and A.B.Y.C (two valid paths)
        # And so on.
395
        #
396
397
398
399
400
401
402
403
404
405
        # We want to wander through the forest, creating a list of paths that
        # leave off each boy at his own tree.
        #
        # In classic recursive-entry style, this routine took a lot of thinking
        # but ends up with very little code that is swamped by explanatory
        # comments.
        #
        # The intent is for this to be unimportant to the user.  It should
        # just work *right*.  If the user asks "show me FRED" and there is
        # only one possibility, then the user gets that.  If there are two
406
        # possibilities, then the user gets shown those possibilities with a
407
408
409
410
411
        # simple option for selecting one.
        #
        if boy_index < len(boys) :
            # we still have at least one boy to leave off at his tree
            boy = boys[boy_index]
rdubner's avatar
rdubner committed
412
413
            #print("DEBUG boy",boy)
            #print("DEBUG trie.children.keys",trie.children.keys())
414
415
416
417
418
419
420
421
            # Do a case-insensitive match:
            bFound = False;
            for child in trie.children :
                if boy.upper() == child.upper() :
                    bFound = True
                    boy = child
                    break
            if bFound :
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
                # We have found this boy's tree.  Advance both the boy
                # and the trie.
                next_trie = trie.children[boy]
                boy_index += 1
                # Continue along that path.
                self.NameMatcher(boys,boy_index,next_trie,paths)
                # and terminate.
                return
            else :
                # We can't leave that boy at this node.  So, we have to
                # check for that boy on all possible paths from here:
                child_tries = list(trie.children.values())
                for next_trie in child_tries :
                    self.NameMatcher(boys,boy_index,next_trie,paths)
                # and terminate.
                return
        else :
439
            # we have dropped off the whole string of boys.  Now it's a
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
            # question of finding every possible path to the edge of the
            # forest from that point.
            if len(trie.children) > 0 :
                # We have not yet reached the edge of the forest:
                child_tries = list(trie.children.values())
                for next_trie in child_tries :
                    self.NameMatcher(boys,boy_index,next_trie,paths)
                # and terminate.
                return
            else :
                # This node has no children, which means we have reached the
                # edge of the forest.

                # Log it:
                paths.append(trie.payload_index)
                #
                # and terminate
                return
458

rdubner's avatar
rdubner committed
459
460
461
462
463
464
    def GetAllPossibilities(self) :
        # Typically in response to a 'print *'
        paths = []
        for index in range(len(self.storage_list)) :
            paths.append(index)
        return paths
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497

    def GetListOfPossibilities(self,desired) :
        if isinstance(desired,list) :
            # We were handed a bunch of pieces.  Let's
            # glue them back together into a single space-separated string:
            desired = " ".join(desired)

        # Go handle our various "A of B" possibilities:
        name_tokens = self.TokenizeCanonicalName(desired)

        # Wander through the forest, looking for possible paths:
        paths = []

        if len(name_tokens) == 0 :
            # if there's nothing, there is nothing
            return paths

        self.NameMatcher(name_tokens,0,self.storage_trie,paths)

        if len(paths) == 1 :
            index = paths[0]

        if len(paths) == 0 :
            # A strict attempt at matching what we were given has come up
            # with bupkis.  Let's start again with the very first element (if
            # there was more than one) and give him some options:

            fragment = desired.split()[0]

            pattern1 = re.compile("^"  + fragment)
            pattern2 = re.compile(".+" + fragment)
            pattern3 = re.compile("^"  + fragment,re.IGNORECASE)
            pattern4 = re.compile(".+" + fragment,re.IGNORECASE)
498

499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
            set1 = set()  # same case, from the beginning
            set2 = set()  # case-insensitive, from the beginning
            set3 = set()  # same case, anywhere
            set4 = set()  # case-insensitive, anywhere
            for index in range(len(self.storage_list)) :
                if re.search(pattern1,self.storage_list[index].Name) :
                    set1.add(index)
                if re.search(pattern2,self.storage_list[index].Name) :
                    set2.add(index)
                if re.search(pattern3,self.storage_list[index].Name) :
                    set3.add(index)
                if re.search(pattern4,self.storage_list[index].Name) :
                    set4.add(index)

            paths = list(set1)
            for index in set2 :
                if index not in paths :
                    paths.append(index)
            for index in set3 :
                if index not in paths :
                    paths.append(index)
            for index in set4 :
                if index not in paths :
                    paths.append(index)

        return paths

526
527
528
529
530
531
532
533
534
535
536
    def GetListOfExactPossibilities(self,token) :
        # We are going to return variables that contain token in its
        # exact, case-insensitive, form

        # Wander through the forest, looking for possible paths:
        paths = []
        self.NameMatcher([token],0,self.storage_trie,paths)

        return paths


537
538
539
540
541
542
543
class LineList() :
    """List of starting lines of ProgramNames"""

    # The point of this list is to be able to figure out, when a trap occurs,
    # which COBOL routine the trap occurred in.  We can't just look at the
    # information from the gdb "frame" command, because the .c code doesn't
    # use the exact same name. It's similar, but it's transmogrified.  But
544
    # we can know the line number where the trap occurred, and scanning
545
546
547
548
549
    # the .tab file lets us convert line numbers to the original COBOL
    # program-id

    def __init__(self) :
        self.line_list = [] # This will be a list of (int,str) tuples
550
        self.EndOfTheWorld = 10000000
551

552
    def Insert(self,line_number , program_id ) :
553
554
        self.line_list.append( (line_number,program_id) )

555
    def Find(self,line_number ) :
556
        # returns the program-id that covers line_number
557

558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
        if len(self.line_list) == 0 :
            return ""

        if line_number < self.line_list[0][0] or line_number >= self.line_list[-1][0] :
            return ""

        left = 0
        right = len(self.line_list)

        while True :
            middle = (right+left)//2
            if self.line_list[middle][0] <= line_number \
                    and line_number < self.line_list[middle+1][0]:
                break
            if self.line_list[middle][0] > line_number :
                right = middle
            else :
                left = middle

        return self.line_list[middle][1]

rdubner's avatar
rdubner committed
579
580
581
582
    def LoadFromLine(self,variable_line) :
        if len(variable_line) > 0 and variable_line[0] == 'P' :
            tokens = variable_line.split("|")
            if len(tokens) >= 3 :
583
                program_id = tokens[2]
rdubner's avatar
rdubner committed
584
585
                line_number = int(tokens[1])
                self.Insert(line_number,program_id)
586
587
588
589
590

class CobolVariable() :
    """Manages the field, attributes, flags, and pointers of a GnuCOBOL COBOL variable"""
    def __init__ (self) :
        self.index      = None
591
592
593
594
595
596
597
598
599
600
601
602
        self.Program    = ""
        self.Section    = ""
        self.Name       = ""
        self.Level      = -1
        self.Base       = ""
        self.Field      = ""
        self.Offset     = 0
        self.ListType   = ""
        self.ListSize   = 0
        self.Picture    = ""
        self.Usage      = ""
        self.Occurs     = 0
603
        self.FieldSize  = 0
604
        self.AttrType   = 0
605
606
        self.AttrDigits = 0
        self.AttrScale  = 0
607
        self.AttrFlags  = 0
608

609
        self.location   = ""            # This is Base + Offset as reported by GDB
rdubner's avatar
rdubner committed
610
        self.data       = []            # list of ListSize bytes
611

612
613
614
615
        self.decimal_point = '.'        # Eventually we are going to have to
        self.comma_separator = ','      # handle DECIMAL-POINT IS COMMA
        self.currency_symbol = '$'

616
617
618
619
620
621
        # Here is the string information decoded from the attributes and
        # the data we get from gdb:
        self.not_in_context = False
        self.display_name = ""
        self.display_body = ""
        self.display_pic  = ""
622
623
624
625
626
627
628
629
630
631

        # This section is used for "StringToxxx processing
        self.rvalue = ""           # Original user string, before cleaning
        self.cleaned_rvalue = ""   # This will have exactly AttrDigits, zero-filled
        #                          # as necessary to the left and right
        self.is_negative = False   # True if there was a minus sign
        self.expanded_picture = "" # "9(4)" becomes "9999" here
        self.ldigits = -1          # this integer is AttrDigits - AttrScale
        self.rdigits = -1          # this integer is AttrScale...
        #                          # ...but see self.CleanAndScale
632

633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
    def CleanTokens(self,execute_result) :
        # (This was created during initial development.  During refactoring
        #  to use cobst data rather than collecting field and attribute
        #  information via gdb.execute, it became superfluous.  I am leaving
        #  it here in case it becomes needed again.  RJD 2019-07-24 v0.1)

        # cgdb returns wildly different results from dbg.execute("print ...")
        # than the same function call in straight gdb .  This routine normalizes
        # the two, returning a list that always has the data we care about
        # right after the keyword

        # Because, as per Finagle, the perversity of the universe *does*
        # tend towards a maximum, and because users are agents of chaos,
        # I am going to strip out anything between two double-quotes,
        # because otherwise some user *will* create a string that will
        # confuse the living crap out of this routine!

        left = execute_result.find('"')
        right = execute_result.rfind('"')
        if left != -1 and right != -1 :
            execute_result = execute_result[:left] + execute_result[right+1:]

        # Turn extraneous characters into spaces, to mollify split()
        execute_result = execute_result.replace("'",' ')
        execute_result = execute_result.replace("{",' ')
        execute_result = execute_result.replace("}",' ')
        execute_result = execute_result.replace(",",' ')
        execute_result = execute_result.replace("<",' ')
        execute_result = execute_result.replace(">",' ')

        ex_tokens = execute_result.split()

        # Get rid of superfluous tokens. cgdb has a bunch whose initial
        # characters are \x1a characters; we don't want them.  Nor do we
        # want 0x55555555, although we will pass through 0x0 (because that's
        # what we see for a picture pointer when there is no picture)
        tokens = []
        for xtoken in ex_tokens :
            if      ((ord(xtoken[0]) > 32
                    and xtoken != '='
                    and xtoken != '*'
                    and xtoken != '-'
                    and xtoken[0] != '0')
                    or xtoken == "0"
                    or xtoken == "0x0") :
                tokens.append(xtoken)
        return tokens
rdubner's avatar
rdubner committed
680

681
682
    def CleanAndScale(self,rside) :
        """ Returns False if the number could not be CleanedAndScaled """
683
        # rside is the raw data from the user.
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
        self.rvalue = rside

        # Expand 9(6)V9(5) to 999999V99999
        self.expanded_picture = Picture(self.Picture)

        pcount = 0
        for ch in self.expanded_picture :
            if ch == 'P' :
                pcount += 1

        # If there is a minus sign, flag this value as negative, and flush
        # out everything to the left of the minus sign
        nhyphen = rside.find('-')
        if nhyphen > -1 :
            self.is_negative = True
            rside = rside[nhyphen+1:]
        else :
            self.is_negative = False

        # A negative number without the HAVE_SIGN is not valid
        if self.is_negative and (self.AttrFlags & COB_FLAG_HAVE_SIGN) == 0 :
            print("Error: Minus sign not allowed on unsigned variable")
            return False

        # Let's flush out everything but numeric stuff, so that 1,000 becomes
        # 1000 and things like 1953/02/02 become 19530202
        self.cleaned_rvalue = ""
        for ch in rside :
            if "0123456789-.".find(ch) > -1 :
                self.cleaned_rvalue += ch

        # Figure out the initial left/right digit count
        self.rdigits = self.AttrScale
        self.ldigits = self.AttrDigits - self.rdigits

719
        # We need to handle the possibility of
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
        # scaling 'P' characters in the PICTURE clause.

        # Canonical examples:
        # PICTURE 9PPP yields DIGITS 4, SCALE -3, and an input of
        # 3000 gets converted to self.data 3

        # PICTURE PPP9 yields DIGITS 4, SCALE 4, and an input of
        # 0.0003 gets converted to self.data 3

        if pcount > 0 :
            # Sigh.  Well, we have to do it.  Let's count on GnuCOBOL having
            # figured out whether this is a left-side or right-side scaling

            lstr, rstr = LeftRight(rside,self.decimal_point)

            if self.rdigits < 0 :
                self.ldigits = self.AttrDigits

                # This is a 9PPP style picture.  Whether implied or explicit,
                # the decimal point will be at the right edge of the number

                # There is, therefore, no right string
                self.rdigits = 0
743
                rstr = ""
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803

                # We lop off the rightmost -ldigits from lstr
                for i in range(pcount) :
                    self.ldigits -= 1
                    lstr = '0' + lstr
                    lstr = lstr[0:-1];

            elif self.rdigits > 0 :
                # This is a PPP9 style picture

                # There is, therefore, no left string
                self.ldigits = 0
                lstr = ""
                # And we lop off the leftmost digits of rstr
                self.rdigits = self.AttrScale
                for i in range(pcount) :
                    self.rdigits -= 1
                    rstr = rstr[1:]
                    rstr = rstr + '0'

            # Put the number back together for further processing:
            self.cleaned_rvalue = lstr + self.decimal_point + rstr

        # Let's turn cleaned_rvalue into a string of exactly AttrDigits
        # characters, zero-filled to the left and right, as necessary

        lstr, rstr = LeftRight(self.cleaned_rvalue,self.decimal_point)

        # Trim off leading zeroes
        while len(lstr) > 0 and lstr[0] == '0' :
            lstr = lstr[1:]

        # Trim off any trailing zeroes:
        while len(rstr)>0 and rstr[len(rstr)-1] == '0' :
            rstr = rstr[:len(rstr)-1]

        # If there are too many ldigits flag this as an error
        if len(lstr) > self.ldigits :
            print("Error: Magnitude too large")
            return False

        # If there are too many rdigits, just grab the leftmost
        if len(rstr) > self.rdigits :
            print("Warning: Least significant digits truncated")
            rstr = rstr[:self.rdigits]

        # Make sure there are exactly ldigits:
        while len(lstr) < self.ldigits :
            lstr = '0' + lstr

        # Make sure there are exactly rdigits:
        while len(rstr) < self.rdigits :
            rstr = rstr + '0'

        self.cleaned_rvalue = lstr + rstr
        if int(self.cleaned_rvalue) == 0 :
            self.is_negative = False

        return True

804
    def SetToRside(self, line_list , rside) :
805
        #print( "Inside SetToRside", rside)
rdubner's avatar
rdubner committed
806

rdubner's avatar
rdubner committed
807
808
        GotData = True

809
        if self.AttrType == COB_TYPE_NUMERIC_DISPLAY :
rdubner's avatar
rdubner committed
810
            self.StringToNumericDisplay(rside)
rdubner's avatar
rdubner committed
811
        elif self.AttrType == COB_TYPE_NUMERIC_BINARY :
rdubner's avatar
rdubner committed
812
            self.StringToNumericBinary(rside)
rdubner's avatar
rdubner committed
813
        elif self.AttrType == COB_TYPE_NUMERIC_PACKED :
rdubner's avatar
rdubner committed
814
            self.StringToPackedDecimal(rside)
rdubner's avatar
rdubner committed
815
        elif self.AttrType == COB_TYPE_NUMERIC_FLOAT :
rdubner's avatar
rdubner committed
816
            self.StringToIeeeBinary(rside)
rdubner's avatar
rdubner committed
817
        elif self.AttrType == COB_TYPE_NUMERIC_DOUBLE :
rdubner's avatar
rdubner committed
818
            self.StringToIeeeBinary(rside)
rdubner's avatar
rdubner committed
819
        elif self.AttrType == COB_TYPE_NUMERIC_FP_DEC64 :
rdubner's avatar
rdubner committed
820
821
            GotData = False
            print("Need to implement: COB_TYPE_NUMERIC_FP_DEC64")
rdubner's avatar
rdubner committed
822
        elif self.AttrType == COB_TYPE_NUMERIC_FP_DEC128 :
rdubner's avatar
rdubner committed
823
824
            GotData = False
            print("Need to implement: COB_TYPE_NUMERIC_FP_DEC128")
rdubner's avatar
rdubner committed
825
        elif self.AttrType == COB_TYPE_NUMERIC_FP_BIN32 :
rdubner's avatar
rdubner committed
826
827
            GotData = False
            print("Need to implement: COB_TYPE_NUMERIC_FP_BIN32")
rdubner's avatar
rdubner committed
828
        elif self.AttrType == COB_TYPE_NUMERIC_FP_BIN64 :
rdubner's avatar
rdubner committed
829
830
            GotData = False
            print("Need to implement: COB_TYPE_NUMERIC_FP_BIN64")
rdubner's avatar
rdubner committed
831
        elif self.AttrType == COB_TYPE_NUMERIC_FP_BIN128 :
rdubner's avatar
rdubner committed
832
833
            GotData = False
            print("Need to implement: COB_TYPE_NUMERIC_FP_BIN128")
rdubner's avatar
rdubner committed
834
        elif self.AttrType == COB_TYPE_ALPHANUMERIC :
rdubner's avatar
rdubner committed
835
            self.StringToAlphanumeric(rside)
rdubner's avatar
rdubner committed
836
        elif self.AttrType == COB_TYPE_NUMERIC_EDITED :
rdubner's avatar
rdubner committed
837
            self.StringToNumericEdited(rside)
rdubner's avatar
rdubner committed
838
        else :
rdubner's avatar
rdubner committed
839
840
841
            GotData = False
            print("Probably a POINTER type.  Not yet implemented")

842
843
844
        if GotData and len(self.data) > 0:
            #print("We've GotData: self.data")
            #print(' '.join(format(x, '02x') for x in self.data))
rdubner's avatar
rdubner committed
845

846
            # We've got self.ListSize bytes in self.data
rdubner's avatar
rdubner committed
847
            # Put them into memory
rdubner's avatar
rdubner committed
848

rdubner's avatar
rdubner committed
849
            the_address = self.GetTheAddress(line_list)
rdubner's avatar
rdubner committed
850

rdubner's avatar
rdubner committed
851
852
853
854
855
856
857
            if the_address :
                i = 0
                while i < len(self.data) :
                    command = "set {char}" + hex(the_address+i) + "=" + hex(self.data[i])
                    if 'gdb' in sys.modules :
                         gdb.execute(command,False,True)
                    i += 1
rdubner's avatar
rdubner committed
858

rdubner's avatar
rdubner committed
859
860
861
862
863
864
    def GetTheAddress(self,line_list) :
        if self.Section in ("WORKING-STORAGE",
                            "INPUT-OUTPUT",
                            "LINKAGE") :
            # We can find this variable's address at base_symbol + offset:
            command = "info address " + self.Base
rdubner's avatar
rdubner committed
865

rdubner's avatar
rdubner committed
866
867
868
869
870
871
872
873
874
875
876
877
            if 'gdb' in sys.modules :
                try :
                    #print(command)
                    address_string = gdb.execute(command,False,True)

                    match = re.search("address 0x([0-9a-f]+)\.",address_string)
                    if not match :
                        print("No match")
                    else :
                        address = int(match.group(1),16)
                        address += self.Offset
                        return address
rdubner's avatar
rdubner committed
878

rdubner's avatar
rdubner committed
879
                except gdb.error:
880
                    # This exception is thrown when you ask for a LINKAGE
rdubner's avatar
rdubner committed
881
882
883
884
885
886
887
888
889
890
                    # variable and it's not available because you aren't
                    # trapped inside the referring routine.
                    self.not_in_context = True
            else :
                # We are running off-line
                return None

        elif self.Section == "LOCAL-STORAGE" :
            if 'gdb' in sys.modules :
                try :
891
                    # Okay.
rdubner's avatar
rdubner committed
892
893
894
895
896
                    #
                    # We are looking for something in LOCAL-STORAGE.  Local
                    # storage is kept in a calloc-ed memory location named
                    # cob_local_ptr.  The complication is that if we have
                    # several program-ids that each have their own cob_local_ptr
897
                    # we have to make sure we know which one we are dealing
rdubner's avatar
rdubner committed
898
                    # with.
899

rdubner's avatar
rdubner committed
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
                    # Pick up the decimal line number where we are trapped
                    line_number = -1
                    frame_string = gdb.execute("frame",False,True)
                    # Look for a number after a colon
                    nfound = frame_string.find(':')
                    if nfound > -1 :
                        frame_string = frame_string[nfound+1:]
                        frame_string = frame_string.split()
                        try :
                            line_number = int(frame_string[0])
                        except :
                            line_number = -1

                    #print("Trapped at ",line_number)
                    # We know we are trapped at line_number

                    # Look up the program-id of that line_number
                    program_id = line_list.Find(line_number)
                    #print("in :", program_id)
919

rdubner's avatar
rdubner committed
920
921
922
923
924
925
926
927
928
929
930
931
                    # Okay again.
                    #
                    # program-id is where the program-under-test is
                    # trapped.  Let's see if that program-id matches
                    # the final place of the desired variable:
                    #
                    #print( self.Name )
                    split_name = self.Name.split('/')
                    #print("Match? :",split_name[-1],program_id)
                    if split_name[-1] == program_id :
                        # Everything seems to match up, so we can try
                        # to get this variable's data:
932

rdubner's avatar
rdubner committed
933
934
                        command = "info address cob_local_ptr"
                        address_string = gdb.execute(command,False,True)
rdubner's avatar
rdubner committed
935
936
                        #print(command)
                        #print(address_string)
rdubner's avatar
rdubner committed
937
938
939
940
941
942
                        address_string = gdb.execute(command,False,True)

                        match = re.search("address 0x([0-9a-f]+)\.",address_string)
                        if not match :
                            print("No match")
                        else :
rdubner's avatar
rdubner committed
943
                            #print(match.group(1))
rdubner's avatar
rdubner committed
944
945
946
947
948
949
950
951
952
953
                            address = int(match.group(1),16)
                            address += self.Offset
                            return address
                    else :
                        return None

                except gdb.error:
                    return None
        else :
            return None
954

955
    def FetchVariableData(self, line_list , show_failure ) :
956
        data_string = None
957
        self.location = ""
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
        self.data = []
        self.display_body = ""
        self.display_pic = ""
        self.display_name = ""
        self.not_in_context = False

        if self.Section in ("WORKING-STORAGE",
                            "INPUT-OUTPUT",
                            "LINKAGE") :
            # We can find this variable's contents by asking for the bytes
            # at base_symbol + offset:
            command = "x/" + str(self.ListSize) + "xb " + self.Base
            if self.Offset > 0 :
                command += "+"
                command += str(self.Offset)

            if 'gdb' in sys.modules :
                try :
                    data_string = gdb.execute(command,False,True)
                except gdb.error:
978
                    # This exception is thrown when you ask for a LINKAGE
979
980
981
982
                    # variable and it's not available because you aren't
                    # trapped inside the referring routine.
                    self.not_in_context = True
            else :
rdubner's avatar
rdubner committed
983
                # We are running off-line, so simulate a return of just zeroes
984
985
986
987
988
989
990
                data_string = ""
                for i in range(self.ListSize) :
                    data_string += "0x00 "
                # print("Simulated", command, "returns", self.ListSize, "zeroes")
        elif self.Section == "LOCAL-STORAGE" :
            if 'gdb' in sys.modules :
                try :
991
                    # Okay.
992
993
994
995
996
                    #
                    # We are looking for something in LOCAL-STORAGE.  Local
                    # storage is kept in a calloc-ed memory location named
                    # cob_local_ptr.  The complication is that if we have
                    # several program-ids that each have their own cob_local_ptr
997
                    # we have to make sure we know which one we are dealing
998
                    # with.
999

1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
                    # Pick up the decimal line number where we are trapped
                    line_number = -1
                    frame_string = gdb.execute("frame",False,True)
                    # Look for a number after a colon
                    nfound = frame_string.find(':')
                    if nfound > -1 :
                        frame_string = frame_string[nfound+1:]
                        frame_string = frame_string.split()
                        try :
                            line_number = int(frame_string[0])
                        except :
                            line_number = -1

                    #print("Trapped at ",line_number)
                    # We know we are trapped at line_number

                    # Look up the program-id of that line_number
                    program_id = line_list.Find(line_number)
                    #print("in :", program_id)
1019

1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
                    # Okay again.
                    #
                    # program-id is where the program-under-test is
                    # trapped.  Let's see if that program-id matches
                    # the final place of the desired variable:
                    #
                    #print( self.Name )
                    split_name = self.Name.split('/')
                    #print("Match? :",split_name[-1],program_id)
                    if split_name[-1] == program_id :
                        # Everything seems to match up, so we can try
                        # to get this variable's data:
1032
1033

                        command = "x/" + str(self.ListSize) + "xb "
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
                        command += "cob_local_ptr"
                        if self.Offset > 0 :
                            command += "+"
                            command += str(self.Offset)

                        data_string = gdb.execute(command,False,True)
                        #print(data_string)
                    else :
                        if show_failure :
                            print("Not in context ( we are stopped in", program_id,")")
                        self.not_in_context = True

                except gdb.error:
                    self.not_in_context = True
            else :
                # print("Can't do LOCAL-STORAGE in debug mode")
                data_string = ""
                for i in range(self.ListSize) :
                    data_string += "0x00 "
        else :
            print("We don't know how to handle",self.Section)
            return

        if data_string != None :
            bytes = data_string.split()
1059
1060
1061
1062
1063
1064
1065

            if len(bytes) > 0 :
                self.location = bytes[0]
                n = self.location.find(':')
                if n > -1 :
                    self.location = self.location[0:n]

1066
1067
            for byte in bytes :
                if len(byte) != 4 :
1068
                    # We are ignoring anything that's not a single-byte 0x..
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
                    # hex value
                    continue
                self.data.append(int(byte,0))

        # Our caller thinks we have data, so we're going to be asked to
        # display it.  Might as well get a jump on that.
        self.display_name = self.NameToString();
        if self.not_in_context :
            self.display_body = "Not in context"
        else :
            self.display_body,self.display_pic = self.DataToString()

    def NameToString(self) :
        storage_type = ""
        if self.Section == "INPUT-OUTPUT" :
            storage_type = "I/O"
        elif self.Section == "WORKING-STORAGE" :
            storage_type = "W-S"
        elif self.Section == "LOCAL-STORAGE" :
            storage_type = "L-S"
        elif self.Section == "LINKAGE" :
            storage_type = "LNK"
1091

1092
1093
1094
1095
1096
1097
1098
        s = "{1:02} {2} [{0}]".format(storage_type,self.Level,self.Name)
        return s

    def ShowFields(self) :
        print("Name:       " , self.display_name    )
        print("Base:       " , self.Base            )
        print("Offset:     " , self.Offset          )
1099
        #print("Attr:       " , self.Attr            )
1100
        print("ListType:   " , self.ListType        )
1101

1102
        print("AttrType:   " , self.AttrType ,end="")
1103
        print(" (",hex(self.AttrType),") ", sep='', end="")
1104
1105
        if self.AttrType == 0 :
            print(" (Unknown)", end="")
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
        elif self.AttrType == COB_TYPE_UNKNOWN             :
            print("UNKNOWN            ", end="")
        elif self.AttrType == COB_TYPE_GROUP               :
            print("GROUP              ", end="")
        elif self.AttrType == COB_TYPE_BOOLEAN             :
            print("BOOLEAN            ", end="")
        #elif self.AttrType == COB_TYPE_NUMERIC             :   # NUMERIC and NUMERIC_DISPLAY are the same
        #    print("NUMERIC            ", end="")
        elif self.AttrType == COB_TYPE_NUMERIC_DISPLAY     :
            print("NUMERIC_DISPLAY    ", end="")
        elif self.AttrType == COB_TYPE_NUMERIC_BINARY      :
            print("NUMERIC_BINARY     ", end="")
        elif self.AttrType == COB_TYPE_NUMERIC_PACKED      :
            print("NUMERIC_PACKED     ", end="")
        elif self.AttrType == COB_TYPE_NUMERIC_FLOAT       :
            print("NUMERIC_FLOAT      ", end="")
        elif self.AttrType == COB_TYPE_NUMERIC_DOUBLE      :
            print("NUMERIC_DOUBLE     ", end="")
        elif self.AttrType == COB_TYPE_NUMERIC_L_DOUBLE    :
            print("NUMERIC_L_DOUBLE   ", end="")
        elif self.AttrType == COB_TYPE_NUMERIC_FP_DEC64    :
            print("NUMERIC_FP_DEC64   ", end="")
        elif self.AttrType == COB_TYPE_NUMERIC_FP_DEC128   :
            print("NUMERIC_FP_DEC128  ", end="")
        elif self.AttrType == COB_TYPE_NUMERIC_FP_BIN32    :
            print("NUMERIC_FP_BIN32   ", end="")
        elif self.AttrType == COB_TYPE_NUMERIC_FP_BIN64    :
            print("NUMERIC_FP_BIN64   ", end="")
        elif self.AttrType == COB_TYPE_NUMERIC_FP_BIN128   :
            print("NUMERIC_FP_BIN128  ", end="")
        elif self.AttrType == COB_TYPE_NUMERIC_COMP5       :
            print("NUMERIC_COMP5      ", end="")
        elif self.AttrType == COB_TYPE_ALNUM               :
            print("ALNUM              ", end="")
        elif self.AttrType == COB_TYPE_ALPHANUMERIC        :
            print("ALPHANUMERIC       ", end="")
        elif self.AttrType == COB_TYPE_ALPHANUMERIC_ALL    :
            print("ALPHANUMERIC_ALL   ", end="")
        elif self.AttrType == COB_TYPE_ALPHANUMERIC_EDITED :
            print("ALPHANUMERIC_EDITED", end="")
        elif self.AttrType == COB_TYPE_NUMERIC_EDITED      :
            print("NUMERIC_EDITED     ", end="")
        elif self.AttrType == COB_TYPE_NATIONAL            :
            print("NATIONAL           ", end="")
        elif self.AttrType == COB_TYPE_NATIONAL_EDITED     :
            print("NATIONAL_EDITED    ", end="")
        print()

1154
1155
        print("AttrDigits: " , self.AttrDigits      )
        print("AttrScale:  " , self.AttrScale       )
1156
1157

        print("AttrFlags:  " , hex(self.AttrFlags)  , end = '')
1158
        if (self.AttrFlags & COB_FLAG_HAVE_SIGN     ) != 0 :
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
            print(" HAVE_SIGN", end = '')
        if (self.AttrFlags & COB_FLAG_SIGN_SEPARATE ) != 0 :
            print(" SIGN_SEPARATE", end = '')
        if (self.AttrFlags & COB_FLAG_SIGN_LEADING  ) != 0 :
            print(" SIGN_LEADING", end = '')
        if (self.AttrFlags & COB_FLAG_BLANK_ZERO    ) != 0 :
            print(" BLANK_ZERO", end = '')
        if (self.AttrFlags & COB_FLAG_JUSTIFIED     ) != 0 :
            print(" FLAG_JUSTIFIED", end = '')
        if (self.AttrFlags & COB_FLAG_BINARY_SWAP   ) != 0 :
            print(" BINARY_SWAP", end = '')
        if (self.AttrFlags & COB_FLAG_REAL_BINARY   ) != 0 :
            print(" REAL_BINARY", end = '')
        if (self.AttrFlags & COB_FLAG_IS_POINTER    ) != 0 :
            print(" IS_POINTER", end = '')
        if (self.AttrFlags & COB_FLAG_NO_SIGN_NIBBLE) != 0 :
            print(" NO_SIGN_NIBBLE", end = '')
        if (self.AttrFlags & COB_FLAG_IS_FP         ) != 0 :
            print(" IS_FP", end = '')
        if (self.AttrFlags & COB_FLAG_REAL_SIGN     ) != 0 :
            print(" REAL_SIGN", end = '')
        if (self.AttrFlags & COB_FLAG_BINARY_TRUNC  ) != 0 :
            print(" BINARY_TRUNC", end = '')
        if (self.AttrFlags & COB_FLAG_CONSTANT      ) != 0 :
            print(" CONSTANT ", end = '')
        print()

1186
1187
1188
1189
        print("Picture:    " , self.Picture         )
        print("Usage:      " , self.Usage           )
        print("Size:       " , self.ListSize        )
        print("pic_data: (HEX)   [" , end= " ")
1190
1191
1192
1193
1194
1195
1196
1197

        ## At this point, if BINARY_SWAP is on, our data are reversed.
        ## We want to unreverse them for this display:
        output_bytes = self.data
        if (self.AttrFlags & COB_FLAG_BINARY_SWAP) != 0 :
            output_bytes.reverse()

        for byte in output_bytes :
1198
1199
1200
1201
1202
1203
            hexout = hex(byte)[-2:]
            if hexout[0:1] == 'x' :
                hexout = '0' + hexout[-1:]
            print(hexout,end=" ")
        print("]")
        print("pic_data: (ASCII) \"", end = "")
1204
        for byte in output_bytes :
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
            if 32 <= byte < 128 :   # valid ASCII characters
                print(chr(byte),end="")
            else :
                print('.',end="")
        print('"')

    def IeeeBinaryToString(self) :
        bytes = self.data
        length = len(bytes)
        if length == 10 :
            return self.X86_extended_precision_from_bytes(bytes)

        exponent_bits = 0
        fraction_bits = 0
        bias = 0
        valid_digits = 0

        if length == 2 :
            # binary16
            exponent_bits = 5
            fraction_bits = 10
            bias = 15
            valid_digits = 4
        elif length == 4 :
            # binary32
            exponent_bits = 8
            fraction_bits = 23
            bias = 127
            valid_digits = 8
        elif length == 8 :
            # binary64
            exponent_bits = 11
            fraction_bits = 52
            bias = 1023
            valid_digits = 16
        elif length == 16 :
            # binary128
            exponent_bits = 15
            fraction_bits = 112
            bias = 16383
            valid_digits = 34
        elif length == 32 :
            # binary256
            exponent_bits = 19
            fraction_bits = 236
            bias = 262143
            valid_digits = 72
        else :
            message = "Don't know how to handle IEEE 754 floating point with a length of "
            message += str(length)
            message += " bytes"
            raise Exception (message)

        type = "  [ IEEE binary"
        type += str(length*8)
        type += " ]"

        exponent_mask = ((1<<exponent_bits)-1)
        unit_point = 1<<fraction_bits
        ten_point = unit_point * 10
        fraction_mask = unit_point-1

        # combine the bytes into a single value
        value = 1   # This is a guard bit; it ensure that, for example,
                    # a bunch of bytes of zeros have at least the number
                    # of bits we're going to need.

        for i in range(length-1,-1,-1) :
            value <<= 8
            value += bytes[i]

        # extract the various IEEE 754 fields:
        sign = (value >> exponent_bits + fraction_bits) & 1
        exponent = (value >> fraction_bits) & exponent_mask
        fraction = value & fraction_mask

        # start creating the output string:

        body = ""
        pic = ""

        if sign == 1 :
            body += '-'

        if ((exponent+1) & exponent_mask) == 0 :
            # exponent is the maximum possible value
            if fraction == 0 :
                body += "Inf"
                body += type
                return body , pic
            body = "NaN"
            body += type
            return body , pic

        if exponent == 0 :
            if fraction == 0 :
                body += "0.0"
                body += type
                return body , pic
            else :
                # faction is != 0, so this is a denormal number
                # fraction stays the same, with the implied binary
                # point fraction_bits to the left of the whole value
                # exponent becomes -N, where N is -(bias-1)
                exponent = -(bias-1)
                # now, adjust fraction to look like a regular number:
                while (fraction & (1<<fraction_bits)) == 0 :
                    fraction <<= 1
                    exponent -= 1
        else :
            # At this point, the exponent is in the range of 1 - exponent_mask-1,
            # meaning that this is an ordinary floating point number.
            # subtract bias from the exponent, as per the specification
            exponent -= bias
            # and add in the implied "1" bit to the left edge of the fraction
            fraction += unit_point

        # Now it gets interesting.  We have an exponent, which multiplies fraction
        # by a power of 2^exponent.  And we have fraction, which currently has
        # an implied binary point between bits fraction_bits+1 and fraction_bits
        #
        # our rather daunting goal is to convert this to 1.234E10
        #

        # fraction is a value ranging from 1.0000 to 1.999999, and
        #
        # Take advantage of python's unlimited integer capability, and find the
        # decimal equivalent of the unit place of the fraction:
        decimal_exponent = 0
        while exponent != 0 :
            if exponent < 0 :
                # We want to increase the exponent, which means we want to divide
                # the fraction by two.  To avoid losing bits, we're going to
                # do that by multiplying it by 5, and dividing it by ten via
                # the decimal exponent:
                exponent += 1;  # increase exponent towards zero
                fraction *= 5;  # multiply by five
                decimal_exponent -= 1; # divide by ten
            else :
                exponent -= 1;
                fraction <<= 1;

            # Multiply or divide by ten to normalize fraction to be
            # between unit_point <= fraction < ten_point

            while fraction > ten_point :
                fraction //= 10
                decimal_exponent += 1;
            while fraction < unit_point :
                fraction *= 10
                decimal_exponent -= 1;

        # when you pop out here, you have a big number
        # whose left-most decimal digit is 1-9 (and is the
        # units place) and the correct decimal_exponent
        # We can now peel of the digits, largest to smallest:

        digit_count = 1
        digit = fraction // unit_point;
        fraction -= digit * unit_point
        fraction *= 10
        body += chr( ord('0') + digit)
        body += '.'
        while fraction != 0 :
            digit = fraction // unit_point;
            fraction -= digit * unit_point
            fraction *= 10
            body += chr( ord('0') + digit)
            digit_count += 1
            if digit_count >= valid_digits :
                break;

        while body[len(body)-1] == '0' :
            body = body[:len(body)-1]

        if body[len(body)-1] == '.' :
            body += '0';

        body += 'e'
        body += str(decimal_exponent)
        body += type
        return body , pic

rdubner's avatar
rdubner committed
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
    def StringToIeeeBinary(self,rside) :
        self.data = bytearray(self.ListSize)
        length = self.ListSize

        if length == 2 :
            # binary16
            exponent_bits = 5
            fraction_bits = 10
            bias = 15
            valid_digits = 4
        elif length == 4 :
            # binary32
            exponent_bits = 8
            fraction_bits = 23
            bias = 127
            valid_digits = 8
        elif length == 8 :
            # binary64
            exponent_bits = 11
            fraction_bits = 52
            bias = 1023
            valid_digits = 16
        elif length == 16 :
            # binary128
            exponent_bits = 15
            fraction_bits = 112
            bias = 16383
            valid_digits = 34
        elif length == 32 :
            # binary256
            exponent_bits = 19
            fraction_bits = 236
            bias = 262143
            valid_digits = 72
        else :
            message = "Don't know how to handle IEEE 754 floating point with a length of "
            message += str(length)
            message += " bytes"
            raise Exception (message)

        rside = rside.lower()

        if rside == "inf" or rside == "+inf" :
            # Positive infinity
            sign = 0
            exponent = (1<<exponent_bits)-1
            significand = 0
        elif rside == "-inf" :
            # Negative infinity
            sign = 1
            exponent = (1<<exponent_bits)-1
            significand = 0
        elif rside == "nan" or rside == "snan" :
            # signalling NaN
            sign = 0
            exponent = (1<<exponent_bits)-1
            significand = 1
        elif rside == "qnan" :
            # quiet NaN
            sign = 0
            exponent = (1<<exponent_bits)-1
            significand = (1<<(fraction_bits-1)) + 1
        else :
            try :
                fpnumber = float(rside)
            except :
                print("Ill-formed floating point input")
                fpnumber = 0

            if fpnumber == 0 :
                sign = 0
                exponent = 0
                significand = 0
            else :
                # We have run out of dodges; we have to actually encode
                # the floating point number
                if fpnumber < 0 :
                    sign = 1
                    fpnumber = -fpnumber
                else:
                    sign = 0

                astring = "{0:24.18e}".format(fpnumber)
1471

rdubner's avatar
rdubner committed
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
                eloc = astring.find("e")
                point10 = -1 # Just to the right of the leftmost digit
                lside = astring[0]
                lside += astring[2:eloc]

                exp10 = int(astring[eloc+1:])

                while exp10 < 0 :
                    point10 += 1
                    if point10 > 0 :
                        lside = '0' + lside
                    exp10 += 1

                while exp10 > 0 :
                    lside = lside + '0'
                    lside = lside
                    point10 -= 1
                    exp10 -= 1

                if point10 < 0 :
                    intpart = lside[0:-point10]
                    fracpart = lside[-point10:]
                else :
                    intpart = ""
                    fracpart = lside

                while len(fracpart) > 0 :
                    if  fracpart[-1:] != '0' : # Just the last char
                        break
                    fracpart = fracpart[0:-1] # All but last char

                # Convert the integer part to a string of bytes:
                intba = bytearray(intpart.encode("utf8"))
                for i in range(len(intba)) :
                    intba[i] -= 0x30    # "0"

                # Convert the integer part to a string of bits:
                intbits = ""
                while len(intba) > 0 :
                    # divide intba by two
                    borrow = 0
                    for i in range(len(intba)) :
                        newborrow = intba[i] & 0x01
                        intba[i] = (borrow*10 + intba[i]) // 2
                        borrow = newborrow
                    # Push the lowest order bit onto the bitstring
                    if borrow ==  1 :
                        intbits += "1"
                    else :
                        intbits += "0"
                    if intba[0] == 0x00 :
                        intba = intba[1:]

                # Reverse the order of intbits:
                intbits = intbits[::-1]

                # Convert the fractional part to a string of bytes:
                fracba = bytearray(fracpart.encode("utf8"))
                for i in range(len(fracba)) :
                    fracba[i] -= 0x30    # "0"

                # Convert the fractional part to a string of bits.  We
                # we will create one more than we can possibly need

                fracbits = ""
                while len(fracbits) < fraction_bits+1+1 :
                    # Multiply fracbits by two:
                    carry = 0
                    for i in range(len(fracba)-1,-1,-1) :
                        fracba[i] = fracba[i]*2 + carry
                        carry = fracba[i] // 10
                        fracba[i] = fracba[i] % 10
                    # Pick up the bit that means >= 0.5
                    if carry == 1 :
                        fracbits += '1'
                    else :
                        fracbits += '0'

                sigbits = intbits + fracbits
                binary_point = len(intbits)

                # Round to the actual number of places we'll need
                carry = sigbits[fraction_bits+1]
                sigbits = sigbits[0:fraction_bits+1]

                i = len(sigbits)-1
                while(i >= 0) :
                    if carry == '0' :
                        break;
                    if sigbits[i] == '0' :
                        sigbits = sigbits[:i] + '1' + sigbits[i+1:]
                        break;
                    sigbits = sigbits[:i] + '0' + sigbits[i+1:]
                    carry = 1
                    i -= 1

                if carry == 1 :
                    # Well, this is improbable.  Somebody must've entered
                    # 0.999999999999999999999999999999999, but in a way
                    # that snuck past the Python floating point input
                    # conversion.
                    #
                    # What a jerk
                    #
                    sigbits = '1' + sigbits
                    sigbits = sigbits[0:fraction_bits+1]
                    binary_point += 1
1579

rdubner's avatar
rdubner committed
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
                # If the number is >= 1, it's already normalized.

                # Otherwise, strip off leading zeroes while adjusting the
                # binary point:
                while sigbits[0] == '0' :
                    sigbits = sigbits[1:] + '0'
                    binary_point -= 1;

                # calculate the power_of_two from the binary_point position
                power_of_two = binary_point - 1

                # Convert the power_of_two using the bias:

                exponent = power_of_two + bias

                if exponent <= 0 :
                    # I am not going to mess with sub-normal numbers.  This
                    # is, therefore, just too damned small.  Make it zero
                    sign = 0
                    exponent = 0
                    significand = 0
                elif exponent >= (1<<exponent_bits)-1 :
1602
                    # The number is too big for this IEEE format.
rdubner's avatar
rdubner committed
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
                    # Make it infinite
                    exponent = (1<<exponent_bits)-1
                    significand = 0
                else :
                    # We have the sign and exponent.  Build the signicand:
                    significand = 0
                    # Trim off the implicit high-order bit:
                    sigbits = sigbits[1:]
                    for ch in sigbits :
                        significand <<= 1
                        if ch == '1' :
                            significand += 1;

        # We can now build the self.data bytearray:

        mask = 0x80
        offset = 0;
        if sign == 1:
            self.data[offset] |= mask
        mask >>= 1
        if mask == 0 :
            mask = 0x80
            offset += 1

        for i in range(exponent_bits) :
            if exponent & 1<<(exponent_bits-1-i) != 0 :
                self.data[offset] |= mask
            mask >>= 1
            if mask == 0 :
                mask = 0x80
                offset += 1

        for i in range(fraction_bits) :
            if significand & 1<<(fraction_bits-1-i) != 0 :
                self.data[offset] |= mask
            mask >>= 1
            if mask == 0 :
                mask = 0x80
                offset += 1
        self.data.reverse()

1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
    def FpNormalize(self,coefficient,exponent) :
        # convert anything to 1.234E5
        if coefficient == 0 :
            exponent = 0

        str_coefficient = str(coefficient)
        # make sure it's at least two characters
        if len(str_coefficient) == 1 :
            str_coefficient += '0'
            exponent -= 1
        places = len(str_coefficient) - 1   # One digit to the left of the
                                            # decimal point; places to the right
        exponent += places
        # insert the decimal point:
        str_coefficient = str_coefficient[0:1] + '.' + str_coefficient[1:]

        # strip off trailing zeroes
        while str_coefficient[-1:] == '0' :
            str_coefficient = str_coefficient[:len(str_coefficient)-1]

        # when left with "3." make it "3.0"
        if len(str_coefficient) == 2 :
            str_coefficient += '0'

        return str_coefficient,exponent

    def GnuCobolDecimal128ToString(self) :
        # This decodes the GnuCOBOL version of the decimal64 format.  This is
        # based on a misreading of the actual IEEE format:
        # See https://en.wikipedia.org/wiki/Decimal64_floating-point_format
        # Either that, or they were using an early draft version.  Anyway,
        # follow along to see how it works:

        # Convert the bytes of data to a single 128-bit value:
        our_bytes = self.data
        if self.AttrFlags & COB_FLAG_BINARY_SWAP :
            our_bytes.reverse()
        value = 1   # This is a guard bit.  Without it, we wouldn't get a
                    # 128-bit zero, for example
        for i in range(self.ListSize-1,-1, -1) :
            value <<= 8
            value += our_bytes[i]

        if self.ListSize == 16 :
            # decimal128
            # The format is 1 S, 4 combination,  14 exponent, 110 coefficient
            type = "decimal64"
            coefficient_bits1 = 111  # Top 4 are 100x
            coefficient_bits2 = 113  # Top 4 are 0xxx
            exponent_loc1 = 128 - 1 - 2 - 14
            exponent_loc2 = 128 - 1 - 0 - 14
            exponent_bits = 14
            bias = 6176

        sign = (value >> (128-1)) & 1
        combination = (value >> (128-5)) & 0xF;

        coefficient1 = value & ((1<<coefficient_bits1)-1)
        coefficient2 = value & ((1<<coefficient_bits2)-1)
        exponent1 = (value >> exponent_loc1) & ((1<<exponent_bits)-1)
        exponent2 = (value >> exponent_loc2) & ((1<<exponent_bits)-1)

        body = ""
        pic = ""
        if combination == 0b1111 : # It's NaN/Inf
            body = "NaN/Inf"
        else :
            # It's a number
            if sign == 1 :
                body += '-'
            if combination >= 0b1100 :
                # Mode 1:
                exponent = exponent1
                coefficient = coefficient1 + (1<<113)
            else :
                exponent = exponent2
                coefficient = coefficient2

            exponent -= bias
            str_coefficient, exponent = self.FpNormalize(coefficient,exponent)
            body += str_coefficient

            body += 'e'
            body += str(exponent)

            pic += "  [ GnuCOBOL "
            pic += type
            pic += " ]"
        return body,pic


    def GnuCobolDecimal64ToString(self) :
        # This decodes the GnuCOBOL version of the decimal64 format.  This is
        # based on a misreading of the actual IEEE format:
        # See https://en.wikipedia.org/wiki/Decimal64_floating-point_format
        # Either that, or they were using an early draft version.  Anyway,
        # follow along to see how it works:

        # Convert the bytes of data to a single 64-bit value:
        our_bytes = self.data
        if self.AttrFlags & COB_FLAG_BINARY_SWAP :
            our_bytes.reverse()
        value = 1   # This is a guard bit.  Without it, we wouldn't get a
                    # 64-bit zero, for example
        for i in range(self.ListSize-1,-1, -1) :
            value <<= 8
            value += our_bytes[i]

        if self.ListSize == 8 :
            # decimal64
            # The format is 1 S, 4 combination, 8 exponent, 50 coefficient
            type = "decimal64"
            coefficient_bits1 = 51  # Top 4 are 100x
            coefficient_bits2 = 53  # Top 4 are 0xxx
            exponent_loc1 = 64 - 1 - 2 - 10
            exponent_loc2 = 64 - 1 - 0 - 10
            exponent_bits = 10
            bias = 398

        sign = (value >> (64-1)) & 1
        combination = (value >> (64-5)) & 0xF;

        coefficient1 = value & ((1<<coefficient_bits1)-1)
        coefficient2 = value & ((1<<coefficient_bits2)-1)
        exponent1 = (value >> exponent_loc1) & ((1<<exponent_bits)-1)
        exponent2 = (value >> exponent_loc2) & ((1<<exponent_bits)-1)

        body = ""
        pic = ""
        if combination == 0b1111 : # It's NaN/Inf
            body = "NaN/Inf"
        else :
            # It's a number
            if sign == 1 :
                body += '-'
            if combination >= 0b1100 :
                # Mode 1:
                exponent = exponent1
                coefficient = coefficient1 + (1<<53)
            else :
                exponent = exponent2
                coefficient = coefficient2

            exponent -= bias
            str_coefficient, exponent = self.FpNormalize(coefficient,exponent)
            body += str_coefficient

            body += 'e'
            body += str(exponent)

            pic += "[ GnuCOBOL "
            pic += type
            pic += " ]"
        return body, pic

    def IeeeDecimal64ToString(self) :
        # NOTE: This code is more-or-less untested.  This is legitimate code
        # for IEEE decimal64.  As of this writing, version 3.0- of GnuCOBOL
        # has a flawed implementation.  It is internally consistent, but it
        # isn't IEEE.  See GnuCobolDecimal64ToString

        # This is the IEEE decimal64/128 format
        # See https://en.wikipedia.org/wiki/Decimal64_floating-point_format
        # GnuCOBOL uses the Binary Integer Decimal option, rather than
        # the Densely Packed Decimal option.

        # Convert the bytes of data to a single 64-bit value:
        our_bytes = self.data
        if self.AttrFlags & COB_FLAG_BINARY_SWAP :
            our_bytes.reverse()
        value = 1   # This is a guard bit.  Without it, we wouldn't get a
                    # 64-bit zero, for example
        for i in range(self.ListSize-1,-1, -1) :
            value <<= 8
            value += our_bytes[i]

        if self.ListSize == 8 :
            # decimal64
            # The format is 1 S, 5 combination, 8 exponent, 50 coefficient
            type = "binary64"
            coefficient_bits = 50
            exponent_bits = 8
            combination_bits = 5
            bias = 398

        coefficient = value & ((1<<coefficient_bits)-1)
        exponent = (value >> coefficient_bits) & ((1<<exponent_bits)-1)
        combination = (value >> (coefficient_bits+exponent_bits)) \
                              & ((1<<combination_bits)-1)
        sign = (value >> (coefficient_bits+exponent_bits+combination_bits)) & 1

        print( hex(value) )
        print( bin(combination) )
        print( hex(exponent) )
        print( hex(coefficient) )

        body = ""
        pic = ""
        if combination == 0b11111 : # It's NaN
            if (exponent & (1<<(exponent_bits-1))) != 0 :
                body += 's'
            else :
                body += 'q'
            body += "NaN"
        elif combination == 0b11110 : # It's Infinity
            if sign == 1 :
                body += '-'
            else :
                body += '+'
            body += "Inf"
        else :
            # It's a number
            if sign == 1 :
                body += '-'
            if combination >= 0b11000 :
                ms_exponent = (combination>>1)&0x03
                ms_coefficient = 8 + (combination&1)
            else :
                ms_exponent = (combination>>3)&0x03
                ms_coefficient = combination & 0x07

            exponent = (ms_exponent<<exponent_bits) + exponent
            exponent -= bias
            #coefficient = (ms_coefficient<<coefficient_bits) + coefficient
            body += str(coefficient)
            body += 'e'
            body += str(exponent)

            pics += "[ IEEE "
            pics += type
            pics += " ]"
        return body, pic

    def PackedDecimalToString(self) :
        body = ""
        pic = ""
rdubner's avatar
rdubner committed
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894

        pcount = 0
        digit_count = 0

        for ch in Picture(self.Picture) :
            if ch == '9' :
                digit_count += 1
            if ch == 'P' :
                pcount += 1

        # The sign nybble: 0x0C = positive
        #                  0x0D = negative
        #                  0x0F = unsigned

        has_sign_nybble = (self.AttrFlags & COB_FLAG_NO_SIGN_NIBBLE) == 0
1895
        signed = (self.AttrFlags & COB_FLAG_HAVE_SIGN) != 0
rdubner's avatar
rdubner committed
1896
1897
1898
1899
1900
1901

        negative = False
        if has_sign_nybble :
            sign_nybble = self.data[self.ListSize-1] & 0x0F
            if sign_nybble in (0xf, 0xa, 0xc, 0xe) : # one learns not to ask silly questions
                negative = False
1902
            else :
rdubner's avatar
rdubner committed
1903
                negative = True
1904
1905

        nybble = 0