GCC Code Coverage Report
Directory: ../src/ Exec Total Coverage
File: /home/joels/Current/lispbm/src/print.c Lines: 274 301 91.0 %
Date: 2024-12-26 17:59:19 Branches: 127 202 62.9 %

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
555110
static int push_n(lbm_stack_t *s, lbm_uint *values, lbm_uint n) {
48
555110
  if (s->sp + n < s->size) {
49
1673282
    for (lbm_uint i = 0; i < n; i ++) {
50
1118172
      s->data[s->sp+i] = values[i];
51
    }
52
555110
    s->sp+=n;
53
555110
    return 1;
54
  }
55
  return 0;
56
}
57
58
5320
bool lbm_value_is_printable_string(lbm_value v, char **str) {
59
5320
  bool is_a_string = false;
60
5320
  if (lbm_is_array_r(v)) {
61
4424
    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
4424
    char *c_data = (char *)array->data;
66
4424
    if (array->size >= 1) { // nonzero length
67
3976
      unsigned int i = 0;
68
3976
      is_a_string = true;
69
7196
      for (i = 0; i < array->size; i ++) {
70
7196
	if (c_data[i] == 0) break;
71

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

3976
      if (i > 0 && i != array->size-1 && c_data[i-1] != 0) is_a_string = false;
77
3976
      if (is_a_string) {
78
2464
        *str = (char*)array->data;
79
      }
80
    }
81
  }
82
5320
  return is_a_string;
83
}
84
85
86
21756
int lbm_print_init(lbm_uint print_stack_size) {
87
88
21756
  if (print_stack_size == 0)
89
    return 0;
90
91
21756
  lbm_uint *print_stack_storage = (lbm_uint*)lbm_malloc(print_stack_size * sizeof(lbm_uint));
92
21756
  if (!print_stack_storage) return 0;
93
94
21756
  if (lbm_stack_create(&print_stack, print_stack_storage, print_stack_size)) {
95
21756
    print_has_stack = true;
96
21756
    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
407722
static int print_emit_string(lbm_char_channel_t *chan, char* str) {
107
407722
  if (str == NULL) return EMIT_FAILED;
108
2090740
  while (*str != 0) {
109
1683092
    int r = lbm_channel_write(chan, *str);
110
1683092
    str++;
111
1683092
    if (r != CHANNEL_SUCCESS) return EMIT_FAILED;
112
  }
113
407648
  return EMIT_OK;
114
}
115
116
180704
static int print_emit_char(lbm_char_channel_t *chan, char c) {
117
118
180704
  int r = lbm_channel_write(chan, c);
119
180704
  if (r != CHANNEL_SUCCESS) return EMIT_FAILED;
120
180616
  return EMIT_OK;
121
}
122
123
124
1596
static int emit_escape(lbm_char_channel_t *chan, char c) {
125

1596
  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
1596
  default:
132
1596
    return print_emit_char(chan, c);
133
  }
134
}
135
136
2212
static int print_emit_string_value(lbm_char_channel_t *chan, char* str) {
137
2212
  if (str == NULL) return EMIT_FAILED;
138
3808
  while (*str != 0) {
139
1596
    int r = emit_escape(chan, *str++);
140
1596
    if (r != EMIT_OK) return r;
141
  }
142
2212
  return EMIT_OK;
143
}
144
145
262305
static int print_emit_symbol(lbm_char_channel_t *chan, lbm_value sym) {
146
262305
  char *str_ptr = (char*)lbm_get_name_by_symbol(lbm_dec_sym(sym));
147
262305
  return print_emit_string(chan, str_ptr);
148
}
149
150
41749
static int print_emit_i(lbm_char_channel_t *chan, lbm_int v) {
151
  char buf[EMIT_BUFFER_SIZE];
152
41749
  snprintf(buf, EMIT_BUFFER_SIZE, "%"PRI_INT, v);
153
41749
  return print_emit_string(chan, buf);
154
}
155
156
21624
static int print_emit_u(lbm_char_channel_t *chan, lbm_uint v, bool ps) {
157
  char buf[EMIT_BUFFER_SIZE];
158
21624
  snprintf(buf, EMIT_BUFFER_SIZE, "%"PRI_UINT"%s", v, ps ? "u" : "");
159
21624
  return print_emit_string(chan, buf);
160
}
161
162
13048
static int print_emit_byte(lbm_char_channel_t *chan, uint8_t v, bool ps) {
163
  char buf[EMIT_BUFFER_SIZE];
164
13048
  snprintf(buf, EMIT_BUFFER_SIZE, "%u%s", v, ps ? "b" : "");
165
13048
  return print_emit_string(chan, buf);
166
}
167
168
420
static int print_emit_float(lbm_char_channel_t *chan, float v, bool ps) {
169
  char buf[EMIT_BUFFER_SIZE];
170
420
  snprintf(buf, EMIT_BUFFER_SIZE, "%"PRI_FLOAT"%s", (double)v, ps ? "f32" : "");
171
420
  return print_emit_string(chan, buf);
172
}
173
174
336
static int print_emit_double(lbm_char_channel_t *chan, double v, bool ps) {
175
  char buf[EMIT_BUFFER_SIZE];
176
336
  snprintf(buf, EMIT_BUFFER_SIZE, "%lf%s", v, ps ? "f64" : "");
177
336
  return print_emit_string(chan, buf);
178
}
179
180
112
static int print_emit_u32(lbm_char_channel_t *chan, uint32_t v, bool ps) {
181
  char buf[EMIT_BUFFER_SIZE];
182
112
  snprintf(buf,EMIT_BUFFER_SIZE, "%"PRIu32"%s", v, ps ? "u32" : "");
183
112
  return print_emit_string(chan, buf);
184
}
185
186
112
static int print_emit_i32(lbm_char_channel_t *chan, int32_t v, bool ps) {
187
  char buf[EMIT_BUFFER_SIZE];
188
112
  snprintf(buf,EMIT_BUFFER_SIZE, "%"PRId32"%s", v, ps ? "i32" : "");
189
112
  return print_emit_string(chan, buf);
190
}
191
192
112
static int print_emit_u64(lbm_char_channel_t *chan, uint64_t v, bool ps) {
193
  char buf[EMIT_BUFFER_SIZE];
194
112
  snprintf(buf,EMIT_BUFFER_SIZE, "%"PRIu64"%s", v, ps ? "u64" : "");
195
112
  return print_emit_string(chan, buf);
196
}
197
198
112
static int print_emit_i64(lbm_char_channel_t *chan, int64_t v, bool ps) {
199
  char buf[EMIT_BUFFER_SIZE];
200
112
  snprintf(buf,EMIT_BUFFER_SIZE, "%"PRId64"%s", v, ps ? "i64" : "");
201
112
  return print_emit_string(chan, buf);
202
}
203
204
51428
static int print_emit_continuation(lbm_char_channel_t *chan, lbm_value v) {
205
  char buf[EMIT_BUFFER_SIZE];
206
51428
  lbm_uint cont = (v & ~LBM_CONTINUATION_INTERNAL) >> LBM_ADDRESS_SHIFT;
207
51428
  snprintf(buf, EMIT_BUFFER_SIZE, "CONT[" "%"PRI_UINT"]", cont);
208
51428
  return print_emit_string(chan, buf);
209
}
210
211
168
static int print_emit_custom(lbm_char_channel_t *chan, lbm_value v) {
212
168
  lbm_uint *custom = (lbm_uint*)lbm_car(v);
213
  int r; // NULL checks works against SYM_NIL.
214

168
  if (custom && custom[CUSTOM_TYPE_DESCRIPTOR]) {
215
56
    r = print_emit_string(chan, (char*)custom[CUSTOM_TYPE_DESCRIPTOR]);
216
  } else {
217
112
    r = print_emit_string(chan, "INVALID_CUSTOM_TYPE");
218
  }
219
168
  return r;
220
}
221
222
56
static int print_emit_defrag_mem(lbm_char_channel_t *chan, lbm_value v) {
223
  (void) v;
224
56
  return print_emit_string(chan, "DM");
225
}
226
227
12192
static int print_emit_channel(lbm_char_channel_t *chan, lbm_value v) {
228
  (void) v;
229
12192
  return print_emit_string(chan, "~CHANNEL~");
230
}
231
232
1820
static int print_emit_array_data(lbm_char_channel_t *chan, lbm_array_header_t *array) {
233
234
1820
  int r = print_emit_char(chan, '[');
235
236
1820
  if (r == EMIT_OK) {
237
238
14588
    for (unsigned int i = 0; i < array->size; i ++) {
239
240
12768
      char *c_data = (char*)array->data;
241
12768
      r = print_emit_byte(chan, (uint8_t)c_data[i], false);
242
243

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

1275574
  while (!lbm_stack_is_empty(&print_stack) && !chan_full) {
288
609390
    lbm_pop(&print_stack, &instr);
289


609390
    switch (instr) {
290
56
    case START_ARRAY: {
291
56
      lbm_pop(&print_stack, &curr);
292
56
      int res = 1;
293
56
      r = print_emit_char(chan, '[');
294
56
      lbm_array_header_t *arr = (lbm_array_header_t*)lbm_car(curr);
295
56
      lbm_uint size = arr->size / sizeof(lbm_value);
296
56
      lbm_value *arrdata = (lbm_value*)arr->data;
297
56
      if (size >= 1) {
298
56
        lbm_value continuation[5] =
299
          {1,  // next index
300
56
           (lbm_uint) arr,
301
           CONTINUE_ARRAY,
302
56
           arrdata[0], // first elt
303
           PRINT};
304

56
        res = res && push_n(&print_stack, continuation, 5);
305
      } else {
306
        res = res && lbm_push(&print_stack, END_LIST);
307
      }
308
56
      if (!res) {
309
        return EMIT_FAILED;
310
      }
311
56
      break;
312
    }
313
168
    case CONTINUE_ARRAY: {
314
      lbm_uint arr_ptr;
315
      lbm_array_header_t *arr;
316
      lbm_uint ix;
317
168
      int res = 1;
318
168
      lbm_pop_2(&print_stack, &arr_ptr, &ix);
319
168
      arr = (lbm_array_header_t *)arr_ptr;
320
168
      lbm_value *arrdata = (lbm_value*)arr->data;
321
168
      if (ix < (arr->size / sizeof(lbm_value))) {
322
112
        r = print_emit_char(chan, ' ');
323
112
        lbm_value continuation[5] =
324
112
          {ix + 1,
325
112
           (lbm_uint) arr,
326
           CONTINUE_ARRAY,
327
112
           arrdata[ix],
328
           PRINT};
329

112
        res = res && push_n(&print_stack, continuation, 5);
330
      } else {
331

56
        res = res && lbm_push(&print_stack, END_ARRAY);
332
      }
333
168
      if (!res) {
334
        return EMIT_FAILED;
335
      }
336
168
      break;
337
    }
338
56
    case END_ARRAY: {
339
56
      r = print_emit_char(chan, ']');
340
56
      break;
341
    }
342
50644
    case START_LIST: {
343
50644
      lbm_pop(&print_stack, &curr);
344
50644
      r = print_emit_char(chan, '(');
345
50644
      if (r != EMIT_OK) return r;
346
50644
      lbm_value car_val = lbm_car(curr);
347
50644
      lbm_value cdr_val = lbm_cdr(curr);
348
50644
      int res = 1;
349

58728
      if (lbm_type_of(cdr_val) == LBM_TYPE_CONS ||
350
8084
          lbm_type_of(cdr_val) == (LBM_TYPE_CONS | LBM_PTR_TO_CONSTANT_BIT)) {
351
42560
        lbm_value cont[2] = {cdr_val, CONTINUE_LIST};
352

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

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

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

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

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

100748
      if (lbm_type_of(cdr_val) == LBM_TYPE_CONS ||
384
42444
          lbm_type_of(cdr_val) == (LBM_TYPE_CONS | LBM_PTR_TO_CONSTANT_BIT)) {
385
15860
        lbm_value cont[2] = {cdr_val, CONTINUE_LIST};
386

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

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

42444
        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
58304
      lbm_value cont[2] = {car_val, PRINT};
395

58304
      res = res && push_n(&print_stack, cont, 2);
396
58304
      if (!res) {
397
        return EMIT_FAILED;
398
      }
399
58304
      break;
400
    }
401
50472
    case END_LIST:
402
50472
      r = print_emit_char(chan, ')');
403
50472
      if (r != EMIT_OK) return r;
404
50388
      break;
405
    case PRINT_SPACE:
406
      r = print_emit_char(chan, ' ');
407
      if (r != EMIT_OK) return r;
408
      break;
409
3724
    case PRINT_DOT:
410
3724
      r = print_emit_string(chan, " . ");
411
3724
      if (r != EMIT_OK) return r;
412
3696
      break;
413
445962
    case PRINT:
414
445962
      lbm_pop(&print_stack, &curr);
415
416
445962
      lbm_type t = lbm_type_of(curr);
417
445962
      if (lbm_is_ptr(curr))
418
120004
          t = t & LBM_PTR_TO_CONSTANT_MASK; // print constants normally
419
420
      switch(t) {
421
50644
      case LBM_TYPE_CONS: {
422
50644
        lbm_value cont[2] = {curr, START_LIST};
423
50644
        int res = push_n(&print_stack, cont, 2);
424
50644
        if (!res) {
425
          print_emit_string(chan," ...");
426
          return EMIT_OK;
427
        }
428
50644
        break;
429
      }
430
262305
      case LBM_TYPE_SYMBOL:
431
262305
        r = print_emit_symbol(chan, curr);
432
262305
        break;
433
41749
      case LBM_TYPE_I:
434
41749
        r = print_emit_i(chan, lbm_dec_i(curr));
435
41749
        break;
436
21624
      case LBM_TYPE_U:
437
21624
        r = print_emit_u(chan, lbm_dec_u(curr), true);
438
21624
        break;
439
280
      case LBM_TYPE_CHAR:
440
280
        r = print_emit_byte(chan, (uint8_t)lbm_dec_char(curr), true);
441
280
        break;
442
420
      case LBM_TYPE_FLOAT:
443
420
        r = print_emit_float(chan, lbm_dec_float(curr), true);
444
420
        break;
445
336
      case LBM_TYPE_DOUBLE:
446
336
        r = print_emit_double(chan, lbm_dec_double(curr), true);
447
336
        break;
448
112
      case LBM_TYPE_U32:
449
112
        r = print_emit_u32(chan, lbm_dec_u32(curr), true);
450
112
        break;
451
112
      case LBM_TYPE_I32:
452
112
        r = print_emit_i32(chan, lbm_dec_i32(curr), true);
453
112
        break;
454
112
      case LBM_TYPE_U64:
455
112
        r = print_emit_u64(chan, lbm_dec_u64(curr), true);
456
112
        break;
457
112
      case LBM_TYPE_I64:
458
112
        r = print_emit_i64(chan, lbm_dec_i64(curr), true);
459
112
        break;
460
51428
      case LBM_CONTINUATION_INTERNAL_TYPE:
461
51428
        r = print_emit_continuation(chan, curr);
462
51428
        break;
463
168
      case LBM_TYPE_CUSTOM:
464
168
        r = print_emit_custom(chan, curr);
465
168
        break;
466
12192
      case LBM_TYPE_CHANNEL:
467
12192
        r = print_emit_channel(chan, curr);
468
12192
        break;
469
4256
      case LBM_TYPE_ARRAY:
470
4256
        r = print_emit_bytearray(chan, curr);
471
4256
        break;
472
56
      case LBM_TYPE_DEFRAG_MEM:
473
56
	r = print_emit_defrag_mem(chan, curr);
474
56
	break;
475
56
      case LBM_TYPE_LISPARRAY: {
476
56
        lbm_value cont[2] = {curr, START_ARRAY};
477
56
        int res = push_n(&print_stack, cont, 2);
478
56
        if (!res) {
479
          print_emit_string(chan, " ...");
480
          return EMIT_OK;
481
        }
482
56
        break;
483
      }
484
      default:
485
        return EMIT_FAILED;
486
      }
487
942424
    }
488
  }
489
333034
  return r;
490
}
491
492
333150
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
333150
  memset(buf, 0, len);
498
333150
  lbm_create_string_char_channel_size(&st, &chan, buf, len);
499
333150
  if (lbm_print_internal(&chan,v) == EMIT_OK)
500
333020
    return 1;
501
130
  return 0;
502
}