GCC Code Coverage Report
Directory: ../src/ Exec Total Coverage
File: /home/joels/Current/lispbm/src/print.c Lines: 207 301 68.8 %
Date: 2024-12-05 14:36:58 Branches: 97 202 48.0 %

Line Branch Exec Source
1
/*
2
    Copyright 2018, 2020 - 2025      Joel Svensson    svenssonjoel@yahoo.se
3
                           2022      Benjamin Vedder
4
5
    This program is free software: you can redistribute it and/or modify
6
    it under the terms of the GNU General Public License as published by
7
    the Free Software Foundation, either version 3 of the License, or
8
    (at your option) any later version.
9
10
    This program is distributed in the hope that it will be useful,
11
    but WITHOUT ANY WARRANTY; without even the implied warranty of
12
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
    GNU General Public License for more details.
14
15
    You should have received a copy of the GNU General Public License
16
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
*/
18
19
#include <stdio.h>
20
#include <string.h>
21
#include <ctype.h>
22
#include <inttypes.h>
23
#include <lbm_types.h>
24
#include <lbm_custom_type.h>
25
26
#include "print.h"
27
#include "heap.h"
28
#include "symrepr.h"
29
#include "stack.h"
30
#include "lbm_channel.h"
31
32
#define PRINT          1
33
#define PRINT_SPACE    2
34
#define START_LIST     3
35
#define CONTINUE_LIST  4
36
#define END_LIST       5
37
#define PRINT_DOT      6
38
#define START_ARRAY    7
39
#define CONTINUE_ARRAY 8
40
#define END_ARRAY      9
41
42
static lbm_stack_t print_stack = { NULL, 0, 0};
43
static bool print_has_stack = false;
44
45
const char *failed_str = "Error: print failed\n";
46
47
48371
static int push_n(lbm_stack_t *s, lbm_uint *values, lbm_uint n) {
48
48371
  if (s->sp + n < s->size) {
49
145169
    for (lbm_uint i = 0; i < n; i ++) {
50
96798
      s->data[s->sp+i] = values[i];
51
    }
52
48371
    s->sp+=n;
53
48371
    return 1;
54
  }
55
  return 0;
56
}
57
58
1456
bool lbm_value_is_printable_string(lbm_value v, char **str) {
59
1456
  bool is_a_string = false;
60
1456
  if (lbm_is_array_r(v)) {
61
560
    lbm_array_header_t *array = (lbm_array_header_t*)lbm_car(v);
62
    // TODO: Potential null deref.
63
    //       Highly unlikely that array is a recognizable NULL though.
64
    //       If it is incorrect, it is most likely arbitrary.
65
560
    char *c_data = (char *)array->data;
66
560
    unsigned int i = 0;
67

560
    if (array->size >= 1 && c_data[0] != 0) { // nonzero length and ix 0 is not 0
68
532
      is_a_string = true;
69
1988
      for (i = 0; i < array->size; i ++) {
70
1988
	if (c_data[i] == 0) break;
71

1736
	if (!isprint((unsigned char)c_data[i]) && ((c_data[i] < 8) || c_data[i] > 13)) {
72
280
	  is_a_string = false;
73
280
	  break;
74
	}
75
      }
76
    }
77

560
    if (i != array->size-1 && c_data[i-1] != 0) is_a_string = false;
78
560
    if (is_a_string) {
79
252
      *str = (char*)array->data;
80
    }
81
  }
82
1456
  return is_a_string;
83
}
84
85
86
21672
int lbm_print_init(lbm_uint print_stack_size) {
87
88
21672
  if (print_stack_size == 0)
89
    return 0;
90
91
21672
  lbm_uint *print_stack_storage = (lbm_uint*)lbm_malloc(print_stack_size * sizeof(lbm_uint));
92
21672
  if (!print_stack_storage) return 0;
93
94
21672
  if (lbm_stack_create(&print_stack, print_stack_storage, print_stack_size)) {
95
21672
    print_has_stack = true;
96
21672
    return 1;
97
  }
98
  return 0;
99
}
100
101
#define EMIT_BUFFER_SIZE 30
102
103
#define EMIT_FAILED -1
104
#define EMIT_OK      0
105
106
127517
static int print_emit_string(lbm_char_channel_t *chan, char* str) {
107
127517
  if (str == NULL) return EMIT_FAILED;
108
182768
  while (*str != 0) {
109
134201
    int r = lbm_channel_write(chan, *str);
110
134201
    str++;
111
134201
    if (r != CHANNEL_SUCCESS) return EMIT_FAILED;
112
  }
113
48567
  return EMIT_OK;
114
}
115
116
4192
static int print_emit_char(lbm_char_channel_t *chan, char c) {
117
118
4192
  int r = lbm_channel_write(chan, c);
119
4192
  if (r != CHANNEL_SUCCESS) return EMIT_FAILED;
120
4188
  return EMIT_OK;
121
}
122
123
124
static int emit_escape(lbm_char_channel_t *chan, char c) {
125
  switch(c) {
126
  case '"': return print_emit_string(chan, "\\\"");
127
  case '\n': return print_emit_string(chan, "\\n");
128
  case '\r': return print_emit_string(chan, "\\r");
129
  case '\t': return print_emit_string(chan, "\\t");
130
  case '\\': return print_emit_string(chan, "\\\\");
131
  default:
132
    return print_emit_char(chan, c);
133
  }
134
}
135
136
static int print_emit_string_value(lbm_char_channel_t *chan, char* str) {
137
  if (str == NULL) return EMIT_FAILED;
138
  while (*str != 0) {
139
    int r = emit_escape(chan, *str++);
140
    if (r != EMIT_OK) return r;
141
  }
142
  return EMIT_OK;
143
}
144
145
44203
static int print_emit_symbol(lbm_char_channel_t *chan, lbm_value sym) {
146
44203
  char *str_ptr = (char*)lbm_get_name_by_symbol(lbm_dec_sym(sym));
147
44203
  return print_emit_string(chan, str_ptr);
148
}
149
150
1612
static int print_emit_i(lbm_char_channel_t *chan, lbm_int v) {
151
  char buf[EMIT_BUFFER_SIZE];
152
1612
  snprintf(buf, EMIT_BUFFER_SIZE, "%"PRI_INT, v);
153
1612
  return print_emit_string(chan, buf);
154
}
155
156
92
static int print_emit_u(lbm_char_channel_t *chan, lbm_uint v, bool ps) {
157
  char buf[EMIT_BUFFER_SIZE];
158
92
  snprintf(buf, EMIT_BUFFER_SIZE, "%"PRI_UINT"%s", v, ps ? "u" : "");
159
92
  return print_emit_string(chan, buf);
160
}
161
162
81186
static int print_emit_byte(lbm_char_channel_t *chan, uint8_t v, bool ps) {
163
  char buf[EMIT_BUFFER_SIZE];
164
81186
  snprintf(buf, EMIT_BUFFER_SIZE, "%u%s", v, ps ? "b" : "");
165
81186
  return print_emit_string(chan, buf);
166
}
167
168
56
static int print_emit_float(lbm_char_channel_t *chan, float v, bool ps) {
169
  char buf[EMIT_BUFFER_SIZE];
170
56
  snprintf(buf, EMIT_BUFFER_SIZE, "%"PRI_FLOAT"%s", (double)v, ps ? "f32" : "");
171
56
  return print_emit_string(chan, buf);
172
}
173
174
56
static int print_emit_double(lbm_char_channel_t *chan, double v, bool ps) {
175
  char buf[EMIT_BUFFER_SIZE];
176
56
  snprintf(buf, EMIT_BUFFER_SIZE, "%lf%s", v, ps ? "f64" : "");
177
56
  return print_emit_string(chan, buf);
178
}
179
180
56
static int print_emit_u32(lbm_char_channel_t *chan, uint32_t v, bool ps) {
181
  char buf[EMIT_BUFFER_SIZE];
182
56
  snprintf(buf,EMIT_BUFFER_SIZE, "%"PRIu32"%s", v, ps ? "u32" : "");
183
56
  return print_emit_string(chan, buf);
184
}
185
186
56
static int print_emit_i32(lbm_char_channel_t *chan, int32_t v, bool ps) {
187
  char buf[EMIT_BUFFER_SIZE];
188
56
  snprintf(buf,EMIT_BUFFER_SIZE, "%"PRId32"%s", v, ps ? "i32" : "");
189
56
  return print_emit_string(chan, buf);
190
}
191
192
56
static int print_emit_u64(lbm_char_channel_t *chan, uint64_t v, bool ps) {
193
  char buf[EMIT_BUFFER_SIZE];
194
56
  snprintf(buf,EMIT_BUFFER_SIZE, "%"PRIu64"%s", v, ps ? "u64" : "");
195
56
  return print_emit_string(chan, buf);
196
}
197
198
56
static int print_emit_i64(lbm_char_channel_t *chan, int64_t v, bool ps) {
199
  char buf[EMIT_BUFFER_SIZE];
200
56
  snprintf(buf,EMIT_BUFFER_SIZE, "%"PRId64"%s", v, ps ? "i64" : "");
201
56
  return print_emit_string(chan, buf);
202
}
203
204
48
static int print_emit_continuation(lbm_char_channel_t *chan, lbm_value v) {
205
  char buf[EMIT_BUFFER_SIZE];
206
48
  lbm_uint cont = (v & ~LBM_CONTINUATION_INTERNAL) >> LBM_ADDRESS_SHIFT;
207
48
  snprintf(buf, EMIT_BUFFER_SIZE, "CONT[" "%"PRI_UINT"]", cont);
208
48
  return print_emit_string(chan, buf);
209
}
210
211
static int print_emit_custom(lbm_char_channel_t *chan, lbm_value v) {
212
  lbm_uint *custom = (lbm_uint*)lbm_car(v);
213
  int r; // NULL checks works against SYM_NIL.
214
  if (custom && custom[CUSTOM_TYPE_DESCRIPTOR]) {
215
    r = print_emit_string(chan, (char*)custom[CUSTOM_TYPE_DESCRIPTOR]);
216
  } else {
217
    r = print_emit_string(chan, "INVALID_CUSTOM_TYPE");
218
  }
219
  return r;
220
}
221
222
static int print_emit_defrag_mem(lbm_char_channel_t *chan, lbm_value v) {
223
  (void) v;
224
  return print_emit_string(chan, "DM");
225
}
226
227
12
static int print_emit_channel(lbm_char_channel_t *chan, lbm_value v) {
228
  (void) v;
229
12
  return print_emit_string(chan, "~CHANNEL~");
230
}
231
232
168
static int print_emit_array_data(lbm_char_channel_t *chan, lbm_array_header_t *array) {
233
234
168
  int r = print_emit_char(chan, '[');
235
236
168
  if (r == EMIT_OK) {
237
238
81298
    for (unsigned int i = 0; i < array->size; i ++) {
239
240
81130
      char *c_data = (char*)array->data;
241
81130
      r = print_emit_byte(chan, (uint8_t)c_data[i], false);
242
243

81130
      if (r == EMIT_OK && i != array->size - 1) {
244
2044
        r = print_emit_char(chan, ' ');
245
      }
246
    }
247
248
168
    if (r != EMIT_OK) return r;
249
140
    return print_emit_char(chan, ']');
250
  }
251
  return r;
252
}
253
254
168
static int print_emit_bytearray(lbm_char_channel_t *chan, lbm_value v) {
255
168
  int r = 0;
256
  char *str;
257
168
  if (lbm_is_array_r(v)) {
258
168
    if (lbm_value_is_printable_string(v, &str)) {
259
      r = print_emit_char(chan, '"');
260
      if (r == EMIT_OK) {
261
        r = print_emit_string_value(chan, str);
262
        if (r == EMIT_OK) {
263
          r = print_emit_char(chan, '"');
264
        }
265
      }
266
    } else {
267
168
      lbm_array_header_t *array = (lbm_array_header_t*)lbm_car(v);
268
168
      r=  print_emit_array_data(chan, array);
269
    }
270
  } else {
271
    r = print_emit_string(chan, "[INVALID_ARRAY]");
272
  }
273
168
  return r;
274
}
275
276
277
45483
static int lbm_print_internal(lbm_char_channel_t *chan, lbm_value v) {
278
279
45483
  lbm_stack_clear(&print_stack);
280
45483
  lbm_value start_print[2] = {v, PRINT};
281
45483
  push_n(&print_stack, start_print, 2);
282
45483
  bool chan_full = false;
283
  lbm_value curr;
284
  lbm_uint instr;
285
45483
  int r = EMIT_FAILED;
286
287

139769
  while (!lbm_stack_is_empty(&print_stack) && !chan_full) {
288
48807
    lbm_pop(&print_stack, &instr);
289


48807
    switch (instr) {
290
    case START_ARRAY: {
291
      lbm_pop(&print_stack, &curr);
292
      int res = 1;
293
      r = print_emit_char(chan, '[');
294
      lbm_array_header_t *arr = (lbm_array_header_t*)lbm_car(curr);
295
      lbm_uint size = arr->size / sizeof(lbm_value);
296
      lbm_value *arrdata = (lbm_value*)arr->data;
297
      if (size >= 1) {
298
        lbm_value continuation[5] =
299
          {1,  // next index
300
           (lbm_uint) arr,
301
           CONTINUE_ARRAY,
302
           arrdata[0], // first elt
303
           PRINT};
304
        res = res && push_n(&print_stack, continuation, 5);
305
      } else {
306
        res = res && lbm_push(&print_stack, END_LIST);
307
      }
308
      if (!res) {
309
        return EMIT_FAILED;
310
      }
311
      break;
312
    }
313
    case CONTINUE_ARRAY: {
314
      lbm_uint arr_ptr;
315
      lbm_array_header_t *arr;
316
      lbm_uint ix;
317
      int res = 1;
318
      lbm_pop_2(&print_stack, &arr_ptr, &ix);
319
      arr = (lbm_array_header_t *)arr_ptr;
320
      lbm_value *arrdata = (lbm_value*)arr->data;
321
      if (ix < (arr->size / sizeof(lbm_value))) {
322
        r = print_emit_char(chan, ' ');
323
        lbm_value continuation[5] =
324
          {ix + 1,
325
           (lbm_uint) arr,
326
           CONTINUE_ARRAY,
327
           arrdata[ix],
328
           PRINT};
329
        res = res && push_n(&print_stack, continuation, 5);
330
      } else {
331
        res = res && lbm_push(&print_stack, END_ARRAY);
332
      }
333
      if (!res) {
334
        return EMIT_FAILED;
335
      }
336
      break;
337
    }
338
    case END_ARRAY: {
339
      r = print_emit_char(chan, ']');
340
      break;
341
    }
342
412
    case START_LIST: {
343
412
      lbm_pop(&print_stack, &curr);
344
412
      r = print_emit_char(chan, '(');
345
412
      if (r != EMIT_OK) return r;
346
412
      lbm_value car_val = lbm_car(curr);
347
412
      lbm_value cdr_val = lbm_cdr(curr);
348
412
      int res = 1;
349

488
      if (lbm_type_of(cdr_val) == LBM_TYPE_CONS ||
350
76
          lbm_type_of(cdr_val) == (LBM_TYPE_CONS | LBM_PTR_TO_CONSTANT_BIT)) {
351
336
        lbm_value cont[2] = {cdr_val, CONTINUE_LIST};
352

336
        res = res && push_n(&print_stack, cont, 2);
353

76
      } else if (lbm_type_of(cdr_val) == LBM_TYPE_SYMBOL &&
354
                 cdr_val == ENC_SYM_NIL) {
355

48
        res = res && lbm_push(&print_stack, END_LIST);
356
      } else {
357
28
        lbm_value cont[4] = {END_LIST, cdr_val, PRINT, PRINT_DOT};
358

28
        res = res && push_n(&print_stack, cont, 4);
359
      }
360
412
      lbm_value cont[2] = {car_val, PRINT};
361

412
      res = res && push_n(&print_stack, cont,2);
362
412
      if (!res) {
363
        return EMIT_FAILED;
364
      }
365
412
      break;
366
    }
367
1020
    case CONTINUE_LIST: {
368
1020
      int res = 1;
369
1020
      lbm_pop(&print_stack, &curr);
370
371
1020
      if (lbm_type_of(curr) == LBM_TYPE_SYMBOL &&
372
          curr == ENC_SYM_NIL) {
373
1016
        break;
374
      }
375
376
1020
      lbm_value car_val = lbm_car(curr);
377
1020
      lbm_value cdr_val = lbm_cdr(curr);
378
379
1020
      r = print_emit_char(chan, ' ');
380
1020
      if (r != EMIT_OK) {
381
4
        return r;
382
      }
383

1348
      if (lbm_type_of(cdr_val) == LBM_TYPE_CONS ||
384
332
          lbm_type_of(cdr_val) == (LBM_TYPE_CONS | LBM_PTR_TO_CONSTANT_BIT)) {
385
684
        lbm_value cont[2] = {cdr_val, CONTINUE_LIST};
386

684
        res = res && push_n(&print_stack, cont, 2);
387

332
      } else if (lbm_type_of(cdr_val) == LBM_TYPE_SYMBOL &&
388
                  cdr_val == ENC_SYM_NIL) {
389

332
        res = res && lbm_push(&print_stack, END_LIST);
390
      } else {
391
        lbm_value cont[4] = {END_LIST, cdr_val, PRINT, PRINT_DOT};
392
        res = res && push_n(&print_stack, cont, 4);
393
      }
394
1016
      lbm_value cont[2] = {car_val, PRINT};
395

1016
      res = res && push_n(&print_stack, cont, 2);
396
1016
      if (!res) {
397
        return EMIT_FAILED;
398
      }
399
1016
      break;
400
    }
401
408
    case END_LIST:
402
408
      r = print_emit_char(chan, ')');
403
408
      if (r != EMIT_OK) return r;
404
408
      break;
405
    case PRINT_SPACE:
406
      r = print_emit_char(chan, ' ');
407
      if (r != EMIT_OK) return r;
408
      break;
409
28
    case PRINT_DOT:
410
28
      r = print_emit_string(chan, " . ");
411
28
      if (r != EMIT_OK) return r;
412
28
      break;
413
46939
    case PRINT:
414
46939
      lbm_pop(&print_stack, &curr);
415
416
46939
      lbm_type t = lbm_type_of(curr);
417
46939
      if (lbm_is_ptr(curr))
418
976
          t = t & LBM_PTR_TO_CONSTANT_MASK; // print constants normally
419
420
      switch(t) {
421
412
      case LBM_TYPE_CONS: {
422
412
        lbm_value cont[2] = {curr, START_LIST};
423
412
        int res = push_n(&print_stack, cont, 2);
424
412
        if (!res) {
425
          print_emit_string(chan," ...");
426
          return EMIT_OK;
427
        }
428
412
        break;
429
      }
430
44203
      case LBM_TYPE_SYMBOL:
431
44203
        r = print_emit_symbol(chan, curr);
432
44203
        break;
433
1612
      case LBM_TYPE_I:
434
1612
        r = print_emit_i(chan, lbm_dec_i(curr));
435
1612
        break;
436
92
      case LBM_TYPE_U:
437
92
        r = print_emit_u(chan, lbm_dec_u(curr), true);
438
92
        break;
439
56
      case LBM_TYPE_CHAR:
440
56
        r = print_emit_byte(chan, (uint8_t)lbm_dec_char(curr), true);
441
56
        break;
442
56
      case LBM_TYPE_FLOAT:
443
56
        r = print_emit_float(chan, lbm_dec_float(curr), true);
444
56
        break;
445
56
      case LBM_TYPE_DOUBLE:
446
56
        r = print_emit_double(chan, lbm_dec_double(curr), true);
447
56
        break;
448
56
      case LBM_TYPE_U32:
449
56
        r = print_emit_u32(chan, lbm_dec_u32(curr), true);
450
56
        break;
451
56
      case LBM_TYPE_I32:
452
56
        r = print_emit_i32(chan, lbm_dec_i32(curr), true);
453
56
        break;
454
56
      case LBM_TYPE_U64:
455
56
        r = print_emit_u64(chan, lbm_dec_u64(curr), true);
456
56
        break;
457
56
      case LBM_TYPE_I64:
458
56
        r = print_emit_i64(chan, lbm_dec_i64(curr), true);
459
56
        break;
460
48
      case LBM_CONTINUATION_INTERNAL_TYPE:
461
48
        r = print_emit_continuation(chan, curr);
462
48
        break;
463
      case LBM_TYPE_CUSTOM:
464
        r = print_emit_custom(chan, curr);
465
        break;
466
12
      case LBM_TYPE_CHANNEL:
467
12
        r = print_emit_channel(chan, curr);
468
12
        break;
469
168
      case LBM_TYPE_ARRAY:
470
168
        r = print_emit_bytearray(chan, curr);
471
168
        break;
472
      case LBM_TYPE_DEFRAG_MEM:
473
	r = print_emit_defrag_mem(chan, curr);
474
	break;
475
      case LBM_TYPE_LISPARRAY: {
476
        lbm_value cont[2] = {curr, START_ARRAY};
477
        int res = push_n(&print_stack, cont, 2);
478
        if (!res) {
479
          print_emit_string(chan, " ...");
480
          return EMIT_OK;
481
        }
482
        break;
483
      }
484
      default:
485
        return EMIT_FAILED;
486
      }
487
94286
    }
488
  }
489
45479
  return r;
490
}
491
492
45483
int lbm_print_value(char *buf, unsigned int len, lbm_value v) {
493
494
  lbm_string_channel_state_t st;
495
  lbm_char_channel_t chan;
496
497
45483
  memset(buf, 0, len);
498
45483
  lbm_create_string_char_channel_size(&st, &chan, buf, len);
499
45483
  if (lbm_print_internal(&chan,v) == EMIT_OK)
500
45451
    return 1;
501
32
  return 0;
502
}