Cthulhu  0.2.10
Cthulhu compiler collection
main.c
Go to the documentation of this file.
1 // SPDX-License-Identifier: GPL-3.0-only
2 
3 #include "argparse/argparse.h"
4 #include "format/backtrace.h"
5 #include "format/colour.h"
6 #include "config/config.h"
7 #include "setup/setup.h"
8 #include "format/config.h"
9 #include "format/notify2.h"
10 #include "io/console.h"
11 #include "io/io.h"
12 
13 #include "memory/memory.h"
14 
15 #include "notify/notify.h"
16 #include "format/notify.h"
17 
18 #include "scan/node.h"
19 
20 #include <tool_config.h>
21 
22 typedef struct tool_t
23 {
25 
29 
31 } tool_t;
32 
33 static const version_info_t kToolVersion = {
34  .license = "GPLv3",
35  .desc = "Notification testing tool",
36  .author = "Elliot Haisley",
37  .version = CT_NEW_VERSION(0, 0, 1),
38 };
39 
40 static const cfg_info_t kToolInfo = {
41  .name = "notify",
42  .brief = "Notification testing options",
43 };
44 
45 static const cfg_arg_t kBacktraceArgs[] = { CT_ARG_SHORT("bt"), CT_ARG_LONG("backtrace") };
46 
47 static const cfg_info_t kBacktraceInfo = {
48  .name = "backtrace",
49  .brief = "Print a backtrace",
50  .args = CT_ARGS(kBacktraceArgs),
51 };
52 
53 static const cfg_choice_t kNotifyOptions[] = {
54  { .text = "brief", .value = eNotifyBrief },
55  { .text = "full", .value = eNotifyFull },
56  { .text = "none", .value = eNotifyCount },
57 };
58 #define NOTIFY_OPTION_COUNT (sizeof(kNotifyOptions) / sizeof(cfg_choice_t))
59 
60 static const cfg_arg_t kNotifyArgs[] = { CT_ARG_SHORT("n"), CT_ARG_LONG("notify") };
61 
62 static const cfg_info_t kNotifyInfo = {
63  .name = "notify",
64  .brief = "Notification style",
65  .args = CT_ARGS(kNotifyArgs),
66 };
67 
68 static const cfg_arg_t kZeroIndexedArgs[] = { CT_ARG_SHORT("zi"), CT_ARG_LONG("zero-indexed") };
69 
70 static const cfg_info_t kZeroIndexedInfo = {
71  .name = "zero_indexed",
72  .brief = "Print zero indexed line numbers",
73  .args = CT_ARGS(kZeroIndexedArgs),
74 };
75 
76 static tool_t make_config(arena_t *arena)
77 {
78  cfg_group_t *config = config_root(&kToolInfo, arena);
79 
80  setup_options_t defaults = setup_options(kToolVersion, config);
81 
82  cfg_field_t *test_backtrace = config_bool(config, &kBacktraceInfo, false);
83  cfg_enum_t notify_info = {
84  .options = kNotifyOptions,
85  .count = NOTIFY_OPTION_COUNT,
86  .initial = eNotifyCount,
87  };
88 
89  cfg_field_t *notify_style = config_enum(config, &kNotifyInfo, notify_info);
90 
91  cfg_field_t *zero_indexed = config_bool(config, &kZeroIndexedInfo, false);
92 
93  tool_t tool = {
94  .m_config = config,
95  .test_backtrace = test_backtrace,
96  .notify_style = notify_style,
97  .zero_indexed = zero_indexed,
98 
99  .options = defaults,
100  };
101 
102  return tool;
103 }
104 
105 const char *const kSampleSourceLeft =
106  "module multi.lhs;\n" // 1
107  "\n" // 2
108  "import multi.rhs,\n" // 3
109  " main;\n" // 4
110  "\n" // 5
111  "procedure lhs;\n" // 3
112  "begin\n" // 4
113  " x := x + 1;\n" // 5
114  " if x < LIMIT then\n" // 6
115  " call rhs\n" // 7
116  "end;\n" // 8
117  ".\n"; // 9
118 
119 const char *const kSampleSourceRight =
120  "module multi.rhs;\n" // 1
121  "\n" // 2
122  "import multi.lhs,\n" // 3
123  " main;\n" // 4
124  "\n" // 5
125  "procedure rhs;\n" // 6
126  "begin\n" // 7
127  " x := x + 1;\n" // 8
128  " if x < LIMIT then\n" // 9
129  " call lhs\n" // 10
130  "end;\n" // 11
131  ".\n"; // 12
132 
133 const char *const kSampleSourceMain =
134  "module main;\n" // 1
135  "\n" // 2
136  "import multi.lhs,\n" // 3
137  " multi.rhs;\n" // 4
138  "\n" // 5
139  "const LIMIT = 25;\n" // 6
140  "var x;\n" // 7
141  "\n" // 8
142  "procedure entry;\n" // 9
143  "begin\n" // 10
144  " x := 0;\n" // 11
145  " call lhs;\n" // 12
146  " ! x\n" // 13
147  "end;\n" // 14
148  "\n" // 15
149  "call entry\n" // 16
150  ".\n"; // 17
151 
152 static const diagnostic_t kInfoDiagnostic = {
153  .severity = eSeverityInfo,
154  .id = "T0001",
155  .brief = "Test diagnostic at info level",
156  .description =
157  "Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n"
158  "Sed non risus. Suspendisse lectus tortor, dignissim sit amet,\n"
159  "adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam.\n"
160  "Maecenas ligula massa, varius a, semper congue, euismod non, mi.\n"
161  "Proin porttitor, orci nec nonummy molestie, enim est eleifend mi,\n"
162  "non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa,\n"
163  "scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue.\n"
164 };
165 
166 static const diagnostic_t kUndefinedFunctionName = {
167  .severity = eSeverityFatal,
168  .id = "G0001",
169  .brief = "Undefined function name",
170  .description =
171  "A function name must be visible in the current scope\n"
172  "to be used in a call expression or statement.\n"
173 };
174 
175 static const diagnostic_t kUnresolvedImport = {
176  .severity = eSeverityFatal,
177  .id = "G0002",
178  .brief = "Unresolved import",
179  .description =
180  "An import statement must refer to a valid module.\n"
181  "The module must be visible in the current scope.\n"
182 };
183 
184 static const diagnostic_t kReservedName = {
185  .severity = eSeverityFatal,
186  .id = "G0003",
187  .brief = "Reserved name",
188  .description =
189  "A reserved name cannot be used as an identifier.\n"
190  "Reserved names are keywords and builtin names.\n"
191 };
192 
193 void event_simple(logger_t *logs, const node_t *builtin)
194 {
195  event_builder_t event = msg_notify(logs, &kInfoDiagnostic, builtin, "test");
196  msg_append(event, builtin, "hello %s", "world");
197 }
198 
199 void event_missing_call(logger_t *logs, scan_t *scan_main, scan_t *scan_lhs, const node_t *builtin)
200 {
201  where_t where = {
202  .first_line = 11,
203  .last_line = 11,
204  .first_column = 4,
205  .last_column = 4 + 8
206  };
207 
208  node_t *node = node_new(scan_main, where);
209 
210  where_t where2 = {
211  .first_line = 8,
212  .last_line = 8,
213  .first_column = 4,
214  .last_column = 4 + 8
215  };
216 
217  node_t *node2 = node_new(scan_lhs, where2);
218 
219  where_t where3 = {
220  .first_line = 12,
221  .last_line = 12,
222  .first_column = 8,
223  .last_column = 8 + 3
224  };
225 
226  node_t *node3 = node_new(scan_lhs, where3);
227 
228  event_builder_t event = msg_notify(logs, &kUndefinedFunctionName, node, "function `%s` is undefined in the current context", "lhs");
229  msg_note(event, "did you mean `%s`?", "rhs");
230  msg_append(event, node, "function called here");
231  msg_append(event, node, "function called here but with a different message");
232  msg_append(event, node2, "function defined here");
233  msg_append(event, node3, "foo bar");
234 
235  msg_append(event, builtin, "builtin node");
236 }
237 
238 void event_invalid_import(logger_t *logs, scan_t *scan, scan_t *scan_rhs)
239 {
240  where_t where = {
241  .first_line = 2,
242  .last_line = 2,
243  .first_column = 7,
244  .last_column = 7 + 9
245  };
246 
247  node_t *node = node_new(scan, where);
248 
249  where_t where2 = {
250  .first_line = 3,
251  .last_line = 3,
252  .first_column = 7,
253  .last_column = 7 + 9
254  };
255 
256  node_t *node2 = node_new(scan_rhs, where2);
257 
258  event_builder_t event = msg_notify(logs, &kUnresolvedImport, node, "unresolved import `%s`", "multi.lhs");
259  msg_note(event, "did you mean `%s`?", "multi.rhs");
260  msg_note(event, "did you mean `%s`?", "multi.rhx");
261  msg_append(event, node, "import statement here");
262  msg_append(event, node2, "module declaration here");
263 }
264 
266 {
267  where_t where = {
268  .first_line = 8,
269  .last_line = 13,
270  .first_column = 0,
271  .last_column = 4
272  };
273 
274  node_t *node = node_new(scan, where);
275 
276  event_builder_t event = msg_notify(logs, &kReservedName, node, "reserved name `%s`", "entry");
277  msg_append(event, node, "procedure declaration here");
278  msg_note(event, "did you mean `%s`?", "main");
279 }
280 
281 static scan_t *scan_string(const char *name, const char *lang, const char *source, arena_t *arena)
282 {
283  io_t *io = io_string(name, source, arena);
284  return scan_io(lang, io, arena);
285 }
286 
287 static void do_print_backtrace(fmt_backtrace_t config, arena_t *arena)
288 {
289  bt_report_t *report = bt_report_collect(arena);
290 
291  fmt_backtrace(config, report);
292 }
293 
294 int recurse(int x, fmt_backtrace_t config, arena_t *arena)
295 {
296  if (x == 0)
297  {
298  do_print_backtrace(config, arena);
299  return 0;
300  }
301 
302  return recurse(x - 1, config, arena);
303 }
304 
305 static int rec3(int x, fmt_backtrace_t config, arena_t *arena)
306 {
307  if (x == 0)
308  {
309  do_print_backtrace(config, arena);
310  return 0;
311  }
312 
313  return recurse(x - 1, config, arena);
314 }
315 
316 static int inner(int x, fmt_backtrace_t config, arena_t *arena)
317 {
318  return rec3(x, config, arena);
319 }
320 
321 static int rec2(int x, int y, fmt_backtrace_t config, arena_t *arena)
322 {
323  if (x == 0)
324  {
325  return inner(y, config, arena);
326  }
327 
328  return rec2(x - 1, y, config, arena);
329 }
330 
331 static int recurse_head(int x, fmt_backtrace_t config, arena_t *arena);
332 static int recurse_middle(int x, fmt_backtrace_t config, arena_t *arena);
333 static int recurse_tail(int x, fmt_backtrace_t config, arena_t *arena);
334 
335 static int recurse_head(int x, fmt_backtrace_t config, arena_t *arena)
336 {
337  if (x == 0)
338  {
339  do_print_backtrace(config, arena);
340  return 0;
341  }
342 
343  return recurse_middle(x - 1, config, arena);
344 }
345 
346 static int recurse_middle(int x, fmt_backtrace_t config, arena_t *arena)
347 {
348  if (x == 0)
349  {
350  do_print_backtrace(config, arena);
351  return 0;
352  }
353 
354  return recurse_tail(x - 1, config, arena);
355 }
356 
357 static int recurse_tail(int x, fmt_backtrace_t config, arena_t *arena)
358 {
359  if (x == 0)
360  {
361  do_print_backtrace(config, arena);
362  return 0;
363  }
364 
365  return recurse_head(x - 1, config, arena);
366 }
367 
368 static void do_backtrace(io_t *io, arena_t *arena)
369 {
370  io_printf(io, "\n=== backtrace ===\n\n");
371 
372  const char *source_root = TOOL_ROOT;
373 
374  io_printf(io, "source root: %s\n\n", source_root);
375 
376  print_options_t options = {
377  .arena = arena,
378  .io = io,
379  .pallete = &kColourDefault,
380  };
381 
382  fmt_backtrace_t config1 = {
383  .options = options,
384  .header = eHeadingGeneric,
385  .config = eBtZeroIndexedLines,
386  .project_source_path = source_root,
387  };
388 
389  fmt_backtrace_t config2 = {
390  .options = options,
391  .header = eHeadingMicrosoft,
392  .config = eBtZeroIndexedLines,
393  .project_source_path = source_root,
394  };
395 
396  recurse(15, config1, arena);
397  recurse(1000, config2, arena);
398 
399  rec2(200, 100, config1, arena);
400  rec2(5, 100, config2, arena);
401 
402  recurse_head(25, config1, arena);
403 }
404 
405 int main(int argc, const char **argv)
406 {
407  setup_default(NULL);
408 
409  arena_t *arena = get_global_arena();
410  io_t *con = io_stdout();
411  tool_t tool = make_config(arena);
412  node_t *node = node_builtin("notify", arena);
413 
414  setup_init_t setup = setup_parse(argc, argv, tool.options);
415 
416  if (setup_should_exit(&setup))
417  return setup_exit_code(&setup);
418 
419  io_printf(con, "notify tool\n");
420 
421  bool backtraces = cfg_bool_value(tool.test_backtrace);
422 
424  bool zero_indexed = cfg_bool_value(tool.zero_indexed);
425 
426  logger_t *logs = logger_new(arena);
427 
428  scan_t *scan_main = scan_string("sample.pl0", "PL/0", kSampleSourceMain, arena);
429  scan_t *scan_lhs = scan_string("lhs.mod", "Oberon-2", kSampleSourceLeft, arena);
430  scan_t *scan_rhs = scan_string("rhs.ctu", "Cthulhu", kSampleSourceRight, arena);
431 
432  event_simple(logs, node);
433  event_missing_call(logs, scan_main, scan_lhs, node);
434  event_invalid_import(logs, scan_main, scan_rhs);
435  event_invalid_function(logs, scan_main);
436 
437  if (style != eNotifyCount)
438  {
439  print_options_t options = {
440  .arena = arena,
441  .io = con,
442  .pallete = setup.pallete
443  };
444 
445  print_notify_t notify_options = {
446  .options = options,
447  .heading = setup.heading,
448  .style = style,
449  .zero_indexed_lines = zero_indexed,
450  };
451 
452  const typevec_t *events = logger_get_events(logs);
453  print_notify_many(notify_options, events);
454  }
455 
456  if (backtraces)
457  {
458  do_backtrace(con, arena);
459  }
460 
461  io_close(con);
462 }
CT_NODISCARD CT_SCAN_API scan_t * scan_io(const char *language, io_t *io, arena_t *arena)
create a scanner from an io source
Definition: scan.c:121
int main(int argc, const char **argv)
Definition: main.c:168
CT_FORMAT_API const colour_pallete_t kColourDefault
a colour pallete that applies ANSI VT100 colours
Definition: colour.c:24
CT_CONFIG_API cfg_field_t * config_enum(cfg_group_t *group, const cfg_info_t *info, cfg_enum_t cfg)
add a new choice field to a configuration group
Definition: config.c:162
CT_CONFIG_API cfg_field_t * config_bool(cfg_group_t *group, const cfg_info_t *info, bool initial)
add a new yes/no field to a configuration group
Definition: config.c:126
CT_PUREFN CT_CONFIG_API bool cfg_bool_value(const cfg_field_t *field)
get the current boolean value of a configuration field
Definition: reflect.c:143
CT_PUREFN CT_CONFIG_API size_t cfg_enum_value(const cfg_field_t *field)
get the current enum value of a configuration field
Definition: reflect.c:167
#define CT_ARGS(it)
Definition: config.h:57
#define CT_ARG_LONG(name)
Definition: config.h:55
CT_CONFIG_API cfg_group_t * config_root(const cfg_info_t *info, arena_t *arena)
create a new configuration group
Definition: config.c:97
#define CT_ARG_SHORT(name)
Definition: config.h:54
#define CT_NEW_VERSION(major, minor, patch)
creates a new ctu_version_t from major, minor and patch
Definition: version_def.h:20
CT_FORMAT_API bt_report_t * bt_report_collect(arena_t *arena)
collect a backtrace into a report this is equivalent to calling bt_report_new() and then bt_report_ad...
Definition: backtrace.c:504
CT_FORMAT_API void fmt_backtrace(fmt_backtrace_t fmt, bt_report_t *report)
print a backtrace report
Definition: backtrace.c:426
notify_style_t
notification style
Definition: notify2.h:24
CT_FORMAT_API void print_notify_many(print_notify_t config, const typevec_t *events)
format many events for printing
Definition: notify.c:111
@ eNotifyCount
Definition: notify2.h:33
@ eNotifyBrief
print a single line for each event just the location and the message
Definition: notify2.h:27
@ eNotifyFull
print a more full event includes source text if as well as the location and message
Definition: notify2.h:31
CT_MEMORY_API arena_t * get_global_arena(void)
get the global memory arena
Definition: memory.c:16
CT_NODISCARD CT_IO_API io_t * io_string(const char *name, const char *string, arena_t *arena)
create an IO view of a string create a readonly IO view of a string
Definition: view.c:90
CT_IO_API os_error_t io_close(INOUT_NOTNULL io_t *io)
destroy an IO object
CT_IO_API io_t * io_stdout(void)
get the global stdout IO object
Definition: console.c:60
CT_IO_API size_t io_printf(io_t *io, STA_FORMAT_STRING const char *fmt,...)
printf to an io object
CT_NODISCARD CT_SCAN_API node_t * node_builtin(const char *name, arena_t *arena)
get the builtin node node used for drivers that declare builtin symbols
Definition: node.c:11
CT_NODISCARD CT_SCAN_API node_t * node_new(const scan_t *scan, where_t where)
create a new node on the heap
Definition: node.c:40
CT_NOTIFY_API event_builder_t msg_notify(INOUT_NOTNULL logger_t *logs, const diagnostic_t *diagnostic, const node_t *node, STA_FORMAT_STRING const char *fmt,...)
notify the logger of a new message
CT_NOTIFY_API void msg_append(event_builder_t builder, const node_t *node, STA_FORMAT_STRING const char *fmt,...)
append additional information to a message
CT_NODISCARD CT_NOTIFY_API logger_t * logger_new(arena_t *arena)
create a new logger
Definition: notify.c:21
CT_NOTIFY_API void msg_note(event_builder_t builder, STA_FORMAT_STRING const char *fmt,...)
add a note to an existing message
CT_NODISCARD CT_NOTIFY_API typevec_t * logger_get_events(const logger_t *logs)
get the events from the logger
Definition: notify.c:36
CT_SETUP_API setup_init_t setup_parse(int argc, const char **argv, setup_options_t setup)
parse the command line
Definition: setup.c:366
CT_PUREFN CT_SETUP_API bool setup_should_exit(const setup_init_t *init)
accessor functions
Definition: setup.c:434
CT_SETUP_API void setup_default(arena_t *arena)
initialise the runtime with default options
Definition: setup.c:188
CT_SETUP_API setup_options_t setup_options(version_info_t info, cfg_group_t *root)
setup default options
Definition: setup.c:215
CT_PUREFN CT_SETUP_API int setup_exit_code(const setup_init_t *init)
get the exit code
Definition: setup.c:442
an allocator object
Definition: arena.h:86
a backtrace report context
Definition: backtrace.c:29
a choice in a set of options
Definition: config.h:97
STA_FIELD_STRING const char * text
the name of this choice
Definition: config.h:99
a choice from a set of options
Definition: config.h:107
information about a configuration field
Definition: config.h:69
STA_FIELD_STRING const char * name
the name of this field
Definition: config.h:71
a diagnostic
Definition: diagnostic.h:27
severity_t severity
the severity of the diagnostic
Definition: diagnostic.h:29
an event builder handles adding additional information to an event
Definition: notify.h:68
printing options for a stacktrace
Definition: backtrace.h:36
print_options_t options
basic print options
Definition: backtrace.h:38
generic print options
Definition: format.h:35
arena_t * arena
temporary arena
Definition: format.h:37
io object implementation
Definition: impl.h:122
a logging sink
Definition: notify.c:14
a position in a source file
Definition: node.h:23
notification formatting options
Definition: notify2.h:38
print_options_t options
base print options
Definition: notify2.h:40
a source file scanner
Definition: scan.h:24
the result of parsing the command line
Definition: setup.h:69
const colour_pallete_t * pallete
the chosen colour pallete
Definition: setup.h:82
heading_style_t heading
the chosen heading style
Definition: setup.h:85
default options shared by all tools
Definition: setup.h:31
Definition: cmd.h:14
cfg_field_t * notify_style
Definition: main.c:27
cfg_field_t * zero_indexed
Definition: main.c:28
setup_options_t options
Definition: cmd.h:31
cfg_group_t * m_config
Definition: main.c:17
cfg_field_t * test_backtrace
Definition: main.c:26
A vector with a fixed type size.
Definition: vector.h:24
version information for a driver/interface/plugin
Definition: version_def.h:48
const char * license
the license of this component
Definition: version_def.h:49
a location inside a scanner locations are inclusive and 0-based
Definition: where.h:23
ctu_line_t first_line
the first line of the location
Definition: where.h:25
#define NOTIFY_OPTION_COUNT
Definition: main.c:58
void event_invalid_import(logger_t *logs, scan_t *scan, scan_t *scan_rhs)
Definition: main.c:238
int recurse(int x, fmt_backtrace_t config, arena_t *arena)
Definition: main.c:294
const char *const kSampleSourceMain
Definition: main.c:133
void event_missing_call(logger_t *logs, scan_t *scan_main, scan_t *scan_lhs, const node_t *builtin)
Definition: main.c:199
void event_invalid_function(logger_t *logs, scan_t *scan)
Definition: main.c:265
const char *const kSampleSourceLeft
Definition: main.c:105
void event_simple(logger_t *logs, const node_t *builtin)
Definition: main.c:193
const char *const kSampleSourceRight
Definition: main.c:119