Fun 0.41.5
The programming language that makes You have fun
Loading...
Searching...
No Matches
funstx.c
Go to the documentation of this file.
1/*
2 * This file is part of the Fun programming language.
3 * https://fun-lang.xyz/
4 *
5 * Copyright 2026 Johannes Findeisen <you@hanez.org>
6 * Licensed under the terms of the Apache-2.0 license.
7 * https://opensource.org/license/apache-2-0
8 */
9
14
15#include <ctype.h>
16#include <stdio.h>
17#include <stdlib.h>
18#include <string.h>
19
20#include "bytecode.h"
21#include "parser.h"
22
27static void usage(const char *prog) {
28 fprintf(stderr, "Usage: %s [--fix] [--quiet] <file1.fun> [file2.fun ...]\n", prog);
29}
30
41static int read_all(const char *path, char **out_buf, size_t *out_len) {
42 *out_buf = NULL;
43 *out_len = 0;
44 FILE *f = fopen(path, "rb");
45 if (!f) return 0;
46 if (fseek(f, 0, SEEK_END) != 0) {
47 fclose(f);
48 return 0;
49 }
50 long sz = ftell(f);
51 if (sz < 0) {
52 fclose(f);
53 return 0;
54 }
55 if (fseek(f, 0, SEEK_SET) != 0) {
56 fclose(f);
57 return 0;
58 }
59 char *buf = (char *)malloc((size_t)sz + 1);
60 if (!buf) {
61 fclose(f);
62 return 0;
63 }
64 size_t rd = fread(buf, 1, (size_t)sz, f);
65 fclose(f);
66 buf[rd] = '\0';
67 *out_buf = buf;
68 *out_len = rd;
69 return 1;
70}
71
80static int write_all(const char *path, const char *buf, size_t len) {
81 FILE *f = fopen(path, "wb");
82 if (!f) return 0;
83 size_t wr = fwrite(buf, 1, len, f);
84 int ok = (wr == len);
85 fclose(f);
86 return ok;
87}
88
94static int is_word(int c) {
95 return (c == '_' || isalnum(c));
96}
97
116static char *apply_fixes(const char *src, size_t len, size_t *out_len) {
117 /* First pass: normalize line endings to LF and compute an upper bound size */
118 /* We'll build into a dynamic buffer that grows if needed. */
119 size_t cap = len + 32;
120 char *out = (char *)malloc(cap);
121 if (!out) return NULL;
122 size_t o = 0;
123
124 size_t i = 0;
125 while (i < len) {
126 /* Grow if needed */
127 if (o + 16 >= cap) {
128 cap = cap * 2 + 64;
129 char *n = (char *)realloc(out, cap);
130 if (!n) {
131 free(out);
132 return NULL;
133 }
134 out = n;
135 }
136
137 /* Start of line: handle indentation fixes */
138 size_t line_start_out = o;
139 int at_bol = 1;
140 int spaces = 0;
141
142 /* Process indentation: convert tabs to two spaces and count spaces */
143 while (i < len) {
144 char c = src[i];
145 if (c == '\r' || c == '\n') break; /* empty line */
146 if (at_bol && (c == ' ' || c == '\t')) {
147 if (c == ' ') {
148 spaces++;
149 } else if (c == '\t') {
150 spaces += 2;
151 } /* replace leading tab with 2 spaces */
152 i++;
153 continue;
154 }
155 break;
156 }
157 /* Emit normalized indentation: nearest even multiple (round up if odd) */
158 if (at_bol) {
159 int normalized = (spaces % 2 == 0) ? spaces : spaces + 1;
160 for (int k = 0; k < normalized; k++) {
161 if (o + 1 >= cap) {
162 cap = cap * 2 + 64;
163 char *n = realloc(out, cap);
164 if (!n) {
165 free(out);
166 return NULL;
167 }
168 out = n;
169 }
170 out[o++] = ' ';
171 }
172 at_bol = 0;
173 }
174
175 /* Copy rest of line, performing CRLF->LF, trimming trailing spaces later, and identifier normalization */
176 size_t line_content_start = i;
177 size_t line_end_i = i;
178 int saw_cr = 0;
179 while (line_end_i < len) {
180 char c = src[line_end_i];
181 if (c == '\r') {
182 saw_cr = 1;
183 break;
184 }
185 if (c == '\n') break;
186 line_end_i++;
187 }
188 /* Now we have [i, line_end_i) content. We'll trim trailing spaces. */
189 size_t trim_end = line_end_i;
190 while (trim_end > i && src[trim_end - 1] == ' ')
191 trim_end--;
192
193 /* Identifier normalization within the line content: replace sint* -> int* when at word boundaries */
194 size_t p = i;
195 while (p < trim_end) {
196 /* Patterns to check */
197 const char *keys[] = {"sint8", "sint16", "sint32", "sint64"};
198 const char *vals[] = {"int8", "int16", "int32", "int64"};
199 int replaced = 0;
200 for (int idx = 0; idx < 4; idx++) {
201 const char *k = keys[idx];
202 size_t klen = strlen(k);
203 if (p + klen <= trim_end && strncmp(&src[p], k, klen) == 0) {
204 int left_ok = (p == i) || !is_word((unsigned char)src[p - 1]);
205 int right_ok = (p + klen == trim_end) || !is_word((unsigned char)src[p + klen]);
206 if (left_ok && right_ok) {
207 const char *v = vals[idx];
208 size_t vlen = strlen(v);
209 if (o + vlen + 16 >= cap) {
210 cap = cap * 2 + 64;
211 char *n = realloc(out, cap);
212 if (!n) {
213 free(out);
214 return NULL;
215 }
216 out = n;
217 }
218 memcpy(&out[o], v, vlen);
219 o += vlen;
220 p += klen;
221 replaced = 1;
222 break;
223 }
224 }
225 }
226 if (replaced) continue;
227 if (o + 1 + 16 >= cap) {
228 cap = cap * 2 + 64;
229 char *n = realloc(out, cap);
230 if (!n) {
231 free(out);
232 return NULL;
233 }
234 out = n;
235 }
236 out[o++] = src[p++];
237 }
238
239 /* EOL: write single LF if we saw any EOL marker, else we'll add at the very end */
240 if (line_end_i < len) {
241 /* consume CRLF or CR */
242 if (src[line_end_i] == '\r') {
243 line_end_i++;
244 if (line_end_i < len && src[line_end_i] == '\n') line_end_i++;
245 } else if (src[line_end_i] == '\n') {
246 line_end_i++;
247 }
248 if (o + 1 >= cap) {
249 cap = cap * 2 + 64;
250 char *n = realloc(out, cap);
251 if (!n) {
252 free(out);
253 return NULL;
254 }
255 out = n;
256 }
257 out[o++] = '\n';
258 }
259
260 i = line_end_i;
261 (void)line_start_out;
262 (void)line_content_start;
263 (void)saw_cr;
264 }
265
266 /* Ensure file ends with a single LF */
267 if (o == 0 || out[o - 1] != '\n') {
268 if (o + 1 >= cap) {
269 cap = cap + 8;
270 char *n = (char *)realloc(out, cap);
271 if (!n) {
272 free(out);
273 return NULL;
274 }
275 out = n;
276 }
277 out[o++] = '\n';
278 }
279
280 *out_len = o;
281 /* shrink to fit */
282 char *shr = (char *)realloc(out, o + 1);
283 if (shr) out = shr;
284 out[o] = '\0';
285 return out;
286}
287
299int main(int argc, char **argv) {
300 int do_fix = 0;
301 int quiet = 0;
302 int first_file_arg = 1;
303
304 // Parse leading flags
305 for (int i = 1; i < argc; i++) {
306 if (strcmp(argv[i], "--fix") == 0) {
307 do_fix = 1;
308 first_file_arg++;
309 continue;
310 }
311 if (strcmp(argv[i], "--quiet") == 0) {
312 quiet = 1;
313 first_file_arg++;
314 continue;
315 }
316 // stop on first non-flag
317 if (argv[i][0] != '-') break;
318 // unknown flag -> usage error
319 if (strncmp(argv[i], "--", 2) == 0) {
320 usage(argv[0]);
321 return 2;
322 }
323 break;
324 }
325
326 if (first_file_arg >= argc) {
327 usage(argv[0]);
328 return 2;
329 }
330
331 int hadError = 0;
332
333 for (int i = first_file_arg; i < argc; i++) {
334 const char *path = argv[i];
335
336 if (do_fix) {
337 /* Read, fix, validate, and write back if parse succeeds */
338 char *orig = NULL;
339 size_t orig_len = 0;
340 if (!read_all(path, &orig, &orig_len)) {
341 fprintf(stderr, "%s: cannot read file\n", path);
342 hadError = 1;
343 continue;
344 }
345 size_t fixed_len = 0;
346 char *fixed = apply_fixes(orig, orig_len, &fixed_len);
347 if (!fixed) {
348 fprintf(stderr, "%s: out of memory while fixing\n", path);
349 free(orig);
350 hadError = 1;
351 continue;
352 }
353
354 /* Validate by parsing the fixed source using parse_string_to_bytecode */
355 Bytecode *test_bc = parse_string_to_bytecode(fixed);
356 if (!test_bc) {
357 char msg[512];
358 int line = 0, col = 0;
359 if (parser_last_error(msg, (unsigned long)sizeof msg, &line, &col)) {
360 fprintf(stderr, "%s:%d:%d: syntax error after --fix attempt: %s\n", path, line, col, msg);
361 } else {
362 fprintf(stderr, "%s: syntax error (unknown) after --fix attempt\n", path);
363 }
364 hadError = 1;
365 free(orig);
366 free(fixed);
367 continue;
368 }
369 bytecode_free(test_bc);
370
371 /* If content differs or original did not end LF, write back */
372 int changed = 0;
373 if (orig_len != fixed_len || memcmp(orig, fixed, (orig_len < fixed_len ? orig_len : fixed_len)) != 0) changed = 1;
374 if (changed) {
375 if (!write_all(path, fixed, fixed_len)) {
376 fprintf(stderr, "%s: failed to write fixed file\n", path);
377 hadError = 1;
378 free(orig);
379 free(fixed);
380 continue;
381 }
382 }
383 free(orig);
384 free(fixed);
385 }
386
387 /* Finally, run the normal syntax check on the (possibly fixed) file */
389 if (!bc) {
390 char msg[512];
391 int line = 0, col = 0;
392 if (parser_last_error(msg, (unsigned long)sizeof msg, &line, &col)) {
393 fprintf(stderr, "%s:%d:%d: syntax error: %s\n", path, line, col, msg);
394 } else {
395 fprintf(stderr, "%s: syntax error (unknown)\n", path);
396 }
397 hadError = 1;
398 continue; // keep checking remaining files
399 }
400
401 if (!quiet) fprintf(stdout, "%s: OK\n", path);
402 bytecode_free(bc);
403 }
404
405 return hadError ? 1 : 0;
406}
Value out
Definition apop.c:38
void bytecode_free(Bytecode *bc)
Free a Bytecode and all memory it owns.
Definition bytecode.c:92
Definitions for the Fun VM bytecode: opcodes, instruction format, and bytecode container API.
Value v
Definition cast.c:22
int k
Definition cast.c:29
int ok
Definition contains.c:38
int main(void)
Build and execute a demo bytecode program and print results.
Definition fun_test.c:44
int idx
Definition index_of.c:38
size_t len
Definition input_line.c:102
size_t cap
Definition input_line.c:101
char * buf
Definition input_line.c:103
int n
Definition insert.c:41
Value c
Definition load_const.c:31
Value * vals
Definition make_array.c:39
free(vals)
Bytecode * parse_string_to_bytecode(const char *source)
Parse a source string and return compiled bytecode.
Definition parser.c:7714
int parser_last_error(char *msgBuf, unsigned long msgCap, int *outLine, int *outCol)
Retrieve the last parser/compiler error information, if any.
Definition parser.c:7770
Bytecode * parse_file_to_bytecode(const char *path)
Parse a .fun source file and return compiled bytecode.
Definition parser.c:7529
Public API for parsing Fun source into bytecode.
fclose(f)
FILE * f
Definition read_file.c:38
const char * p
Definition read_file.c:37
long sz
Definition read_file.c:50
#define fprintf
Definition vm.c:200
Value path
Definition write_file.c:33