diff --git a/README.md b/README.md index 90d65a4..033992c 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,9 @@ cc src/modal.c -o bin/modal ``` bin/modal examples/hello.modal + -v Print version + -q Quiet mode, no step printing + -n Infinite mode, no rewrites limit ``` ## Credits diff --git a/examples/io_repl.modal b/examples/io_repl.modal deleted file mode 100644 index 5e96b63..0000000 --- a/examples/io_repl.modal +++ /dev/null @@ -1,11 +0,0 @@ -?(?-) (This example demonstrates how to keep the runtime active between prompts.) - -<> ((You said: quit\n) send) ((You quit.) print ') -<> (?: print ') (?:) -<> (?: send) (?: wait stdin) -<> (wait ?~) ((You said: ?~\n) send) -<> (' ?x) (?x ') - -(Say something, or type "quit": \n) print ' - -wait stdin \ No newline at end of file diff --git a/examples/postcard.modal b/examples/postcard.modal new file mode 100644 index 0000000..478b784 --- /dev/null +++ b/examples/postcard.modal @@ -0,0 +1,9 @@ + +<> (what is ?x) (is ?x a (programming language)) +<> (is ?x a ?y) (or, is ?x (a virtual machine)) +<> (or, is ?x ?y) (?x is ?y <> ?y (a meta-language)) +<> ((a meta-language)) modal +<> (?x is ?x) (?(?: ?:) ?x) + +what is modal + diff --git a/examples/repl.modal b/examples/repl.modal new file mode 100644 index 0000000..15cbd7f --- /dev/null +++ b/examples/repl.modal @@ -0,0 +1,8 @@ +?(?-) (This is a fully functional Modal REPL.) + +<> quit EOF +<> wait?~ (?~ wait!) +<> (?: print) ?: +<> (EOF wait!) (Bye.\n print) + +(Say something, or type "quit":\n) print wait! \ No newline at end of file diff --git a/examples/sierpinski.modal b/examples/sierpinski.modal new file mode 100644 index 0000000..9c2a50a --- /dev/null +++ b/examples/sierpinski.modal @@ -0,0 +1,21 @@ +?(?-) (Rules) + +<> (* (. > (. ?x))) (* (. (. > ?x))) +<> (. (. > (* ?x))) (* (. (* > ?x))) + +?(?-) (Physics) + +<> (Tri > (?x ?y)) (Tri (?x > ?y)) +<> (Tri (?x > (?y ?z))) (Tri (?x (?y > ?z))) +<> (?x (?y > (?z ?n))) (. (?y (?z > ?n))) +<> ((?x > ())) (< ()) +<> (Tri < (* ?^)) (?(?: ?:) (*?^ \n)) +<> ((?x < ?y)) (< (?x ?y)) + +?(?-) (Print) + +<> (Tri.join ?x ?:) (Tri > ?x ?:) +<> (Tri.dup ?x ?^) (Tri.join ?x ?^) +<> (Tri < ?x) (Tri.dup (. ?x) (?x \n)) + +?(?* (Tri < (?*))) ...............*............... \ No newline at end of file diff --git a/examples/tests.modal b/examples/tests.modal index f8e329e..cc8a36b 100644 --- a/examples/tests.modal +++ b/examples/tests.modal @@ -1,74 +1,82 @@ ?(?-) (This example tests various aspects of the implementation.) -?(?-) (Inline rules) +?(?-) (Early Test Primitives) -<> ((?x -> ?y)) (<> ?x ?y) -(nap -> (tap =)) +<> (?x = ?x ?n test) (?(?: ?:) (#pass ?n\n)) -nap tap test +<> (ghost) () + +?(?-) (Formatter) + +?((?x ?y) one) aaa(bbb) = one (formatter 1) test +?((?x ?y) one) (bbb)aaa = one (formatter 2) test +(a b c ) = (a b c) (formatter 3) test +( a b c) = (a b c) (formatter 4) test +( a b c ) = (a b c) (formatter 5) test +(a b c ( a b c ) ) = (a b c (a b c)) (formatter 6) test ?(?-) (Empty replacements) -<> (?x pop-plain) -<> (?x pop) () -<> (ghost) () +(ab cd () ghost) = (ab cd ()) (empty 1) test +(ab cd ghost ()) = (ab cd ()) (empty 2) test +(ab ghost cd ()) = (ab cd ()) (empty 3) test +(ghost ab cd ()) = (ab cd ()) (empty 4) test +(ghost) = () (empty 5) test -abc def pop-plain = abc test -abc def pop = abc test -(ghost) = () test +(q ?((?x ?y ?z) (?x ?y)) a b c) = (q a b) (empty 6) test +(q ?((?x ?y ?z) (?x ?z)) a b c) = (q a c) (empty 7) test +(q ?((?x ?y ?z) (?y ?z)) a b c) = (q b c) (empty 8) test -?(?-) (Basic replacements) +<> (prefix/pop ?x) () +<> (?x suffix/pop) () -<> (replace-name (foo)) ((bar) =) - -replace-name (foo) (bar) test +(ab prefix/pop cd ef) = (ab ef) (empty 9) test +(ab cd suffix/pop ef) = (ab ef) (empty 10) test ?(?-) (Basic register setups) -<> (dup (?x)) ((?x ?x) =) -<> (swap (?x ?y)) ((?y ?x) =) -<> (compare (?x ?x ?x)) ((#t) =) +<> (dup (?x)) (?x ?x) +<> (swap (?x ?y)) (?y ?x) +<> (compare (?x ?x ?x)) (#t) +<> (rotate (?x (?y (?z)))) (?y (?z (?x))) +<> (unused ?x) ?y -dup (abc) (abc abc) test -swap (abc def) (def abc) test -compare (abc abc abc) (#t) test +(dup (abc)) = (abc abc) (basic 1) test +(swap (abc def)) = (def abc) (basic 2) test +(compare (abc abc abc)) = (#t) (basic 3) test +(rotate (abc (def (ghi)))) = (def (ghi (abc))) (basic 4) test +(unused hey) = (?y) (basic 5) test -?(?-) (Empty register replacement) +?(?-) (Substring registers) -<> (replace-empty ?x) (?y) +<> (connect ?x ?y ?z) (?x-?y?z) +<> (prefix-?x) (?x-suffix) -replace-empty abc = ?y test - -?(?-) (Guards setups) - -<> (join (String ?x) (String ?y)) ((?x ?y) =) - -join (String abc) (String def) (abc def) test +connect foo bar baz = foo-barbaz (substring 1) test +prefix-anything = anything-suffix (substring 2) test +?(foo QQQ) foobar = foobar (substring 3) test ?(?-) (Lambdas) -?((?x) ((?x ?x) =)) abc (abc abc) test -abc ?(?x) def = abc test +(?(?x (?x ?x)) abc) = (abc abc) (lambda 1) test +abc ?(?x) def = abc (lambda 2) test -?(?-) (Explode) +?(?-) (op: explode) -<> (explode ?*) ((?*) =) +(?(?* ?*) cow) = (c (o (w ()))) (explode word) test +(?(?* ?*) (12 34 45)) = (12 (34 (45 ()))) (explode tuple) test +(?(?* ?*) ()) = () (explode empty) test -explode cow (c (o (w ()))) test -explode (12 34 45) (12 (34 (45 ()))) test +?(?-) (op: join) -?(?-) (Implode) +?(?^ ?^) (b (a (t ()))) = bat (join 1) test +?(?^ ?^) (12 (34 (56 ()))) = 123456 (join 2) test +(?(?^ ?^) ()) = () (join empty) test -<> (implode ?^) (?^ =) +?(?-) (op: unwrap) -implode (b (a (t ()))) bat test -implode (12 (34 (56 ()))) 123456 test - -?(?-) (Test Primitives) - -<> (?: print) (?:) -<> (?x = ?x test) (#ok) -<> (?x = ?y test) (#fail) +?(?. ?.) (abcd) = abcd (unwrap 1) test +(?(?. ?.) ()) = () (unwrap empty) test ?(?-) (List reversal) @@ -76,5 +84,15 @@ implode (12 (34 (56 ()))) 123456 test <> (reverse (?*)) (reverse List (?*) ()) <> (reverse List (?x ?y) ?z) (reverse List ?y (?x ?z)) -reverse (modal) = ladom test +reverse (modal) = ladom (reverse 1) test +?(?-) (Inline rules) + +<> ((?x -> ?y)) (<> ?x ?y) +(nap -> (tap =)) + +nap tap (inline 1) test + +?(?-) (Late Test Primitives) + +<> (?x = ?y ?n test) (?(?: ?:) (#fail ?n found: ?x expect: ?y\n)) \ No newline at end of file diff --git a/makefile b/makefile index ab918b7..24e2457 100644 --- a/makefile +++ b/makefile @@ -8,12 +8,14 @@ all: dest dest: @ mkdir -p bin run: all bin/modal - @ bin/modal examples/hello.modal 2> /dev/null -debug: bin/modal-debug + @ bin/modal -q examples/hello.modal +debug: all bin/modal-debug @ bin/modal-debug examples/hello.modal -test: bin/modal-debug bin/modal +test: all bin/modal-debug bin/modal @ bin/modal -v - @ bin/modal-debug examples/tests.modal + @ bin/modal-debug -q examples/fizzbuzz.modal + @ bin/modal-debug -q examples/sierpinski.modal + @ bin/modal-debug -q examples/tests.modal install: bin/modal cp bin/modal ~/bin/ uninstall: diff --git a/src/modal.c b/src/modal.c index 5f3206b..0c1a83f 100644 --- a/src/modal.c +++ b/src/modal.c @@ -1,16 +1,15 @@ #include typedef struct { - int id; - char *a, *b; + unsigned int id, refs, ptr; + char *a, *b, reg[0x10]; } Rule; -static int flip, rmin = 0xff, rmax = 0x00, cycles = 0x10000; -static Rule rules[0x1000], lambda, *rules_ = rules; -static char dict[0x8000], *dict_ = dict; +static int flip, quiet, cycles = 0x10000; +static Rule rules[0x1000], *rules_ = rules, lambda; +static char dict[0x8000], *dict_ = dict, empty; static char bank_a[0x4000], *src_ = bank_a; static char bank_b[0x4000], *dst_ = bank_b; -static char *regs[0x100]; #define spacer(c) (c <= ' ' || c == '(' || c == ')') @@ -30,62 +29,17 @@ walk(char *s) return s; } -static int -set_reg(int r, char *b) -{ - if(regs[r]) { - char *a = regs[r], *aa = walk(a), *bb = walk(b); - while(a < aa && b < bb) - if(*a++ != *b++) return 0; - } else { - regs[r] = b; - if(r < rmin) rmin = r; - if(r > rmax) rmax = r; - } - return 1; -} - static void -put_reg(char r) +write_reg(char r, char *reg) { - char c, *s = regs[(int)r], *ss; - if(!s) { - *dst_++ = '?', *dst_++ = r; - return; - } - ss = walk(s); - if(r == '*') { - int i, depth = 0; - if(*s == '(') { /* special explode tuple */ - s++; - while(s < ss) { - while((c = *s) && !spacer(c)) - *dst_++ = c, s++; - *dst_++ = ' '; - *dst_++ = '(', s++, depth++; - } - } else { /* special explode token */ - while((c = *s++) && !spacer(c)) - *dst_++ = c, *dst_++ = ' ', *dst_++ = '(', depth++; - } - for(i = 0; i < depth; i++) - *dst_++ = ')'; - } else if(r == '.') { /* special unpack */ - if(*s == '(') s++, --ss; - while(s < ss) *dst_++ = *s++; - } else if(r == '^') { /* special join */ - if(*s == '(') s++, --ss; - while(s < ss && (c = *s++)) - if(!spacer(c)) *dst_++ = c; - } else if(r == '~') { /* special stdin */ - while(fread(&c, 1, 1, stdin) && c >= ' ') - *dst_++ = c; - } else if(r == ':') { /* special stdout */ - if(*s == '(') s++, --ss; - while(s < ss) { - c = *s++; + char c, *cap = walk(reg); + switch(r) { + case ':': /* op: output */ + if(*reg == '(') reg++, --cap; + while(reg < cap) { + c = *reg++; if(c == '\\') { - switch(*s++) { + switch(*reg++) { case 't': putc(0x09, stdout); break; case 'n': putc(0x0a, stdout); break; case 's': putc(0x20, stdout); break; @@ -93,117 +47,178 @@ put_reg(char r) } else putc(c, stdout); } - } else - while(s < ss) *dst_++ = *s++; -} - -static char * -match_rule(Rule *r, char *p) -{ - int i; - char c, last = 0, *a = r->a, *b = p; - if(rmax) { - for(i = rmin; i <= rmax; i++) - regs[i] = 0; - rmin = 0xff, rmax = 0x00; + return; + case '~': { /* op: input */ + while(fread(&c, 1, 1, stdin) && c >= ' ') + *dst_++ = c; + if(feof(stdin)) + *dst_++ = 'E', *dst_++ = 'O', *dst_++ = 'F'; + return; } - while((c = *a)) { - if(spacer(last) && c == '?') { - if(!set_reg(*(++a), b)) return NULL; - a++, b = walk(b); - continue; - } - if(c != *b) return NULL; - a++, b++, last = c; + case '^': /* op: join */ + if(*reg == '(') reg++, --cap; + while(reg < cap && (c = *reg++)) + if(!spacer(c)) *dst_++ = c; + return; + case '.': /* op: unwrap */ + if(*reg == '(') reg++, --cap; + while(reg < cap) *dst_++ = *reg++; + return; + case '*': { /* op: explode */ + int i, depth = 0; + if(*reg == '(' && reg[1] != ')') { /* tuple */ + reg++; + while(reg < cap) { + while((c = *reg) && !spacer(c)) + *dst_++ = c, reg++; + *dst_++ = ' '; + *dst_++ = '(', reg++, depth++; + } + } else /* token */ + while((c = *reg++) && !spacer(c)) + *dst_++ = c, *dst_++ = ' ', *dst_++ = '(', depth++; + for(i = 0; i < depth; i++) *dst_++ = ')'; + return; + } + default: + while(reg < cap) *dst_++ = *reg++; + return; } - c = *b; - return spacer(c) ? b : NULL; } static int -commit_rule(Rule *r, char *s, int create) +write_rule(Rule *r, char *s, int create) { while((*dst_++ = *s++)) ; - *dst_++ = 0; + *dst_ = 0; if((flip = !flip)) src_ = bank_b, dst_ = bank_a; else src_ = bank_a, dst_ = bank_b; - if(create) - fprintf(stderr, "<> (%s) (%s)\n", r->a, r->b); - else - fprintf(stderr, "%02d %s\n", r->id, src_); + if(!quiet) { + if(create) + fprintf(stderr, "<> (%s) (%s)\n", r->a, r->b); + else + fprintf(stderr, "%02d %s\n", r->id, src_), ++r->refs; + } return 1; } static int -write_rule(Rule *r, char last, char *res) +apply_rule(Rule *r, char *s) { - char c, *b = r->b, *origin = dst_; - while((c = *b++)) - if(spacer(last) && c == '?') - put_reg(*b++); - else - *dst_++ = c, last = c; - if(dst_ == origin) { - while(*res == ' ') res++; - if(*res == ')' && *(dst_ - 1) == ' ') dst_--; + unsigned int i, id; + char c, *a = r->a, *b = r->b, *origin = dst_, *reg, *regs[0x08]; + /* phase: clean registers */ + for(i = 0; i < r->ptr; i++) + regs[i] = NULL; + /* phase: match rule */ + while((c = *a++)) { + if(c == '?') { + char *pcap = walk(s); + id = *a++ - '0'; + if((reg = regs[id])) { /* reg cmp */ + char *rcap = walk(reg), *pp = s; + while(reg < rcap || pp < pcap) + if(*reg++ != *pp++) return 0; + } else /* reg set */ + regs[id] = s; + s = pcap; + } else if(c != *s++) + return 0; } - return commit_rule(r, res, 0); -} - -static char * -parse_frag(char *s) -{ - char c, *ss; - while((c = *s) && c <= ' ') s++; - if(*s != ')' && !(*s == '<' && s[1] == '>')) { - ss = walk(s); - if(*s == '(') { - s++; - while(s < ss - 1) *dict_++ = *s++; - s++; + c = *s; + if(!spacer(c)) return 0; + /* phase: write rule */ + while((c = *b++)) { + if(c == '?') { + id = *b - '0'; + if(id < 9 && (reg = regs[id])) + b++, write_reg(r->reg[id], reg); + else + *dst_++ = c; } else - while(s < ss) *dict_++ = *s++; + *dst_++ = c; } - *dict_++ = 0; - return s; + if(dst_ == origin) { + while(*s == ' ') s++; + if(*s == ')' && *(dst_ - 1) == ' ') dst_--; + } + return write_rule(r, s, 0); +} + +static int +find_register(Rule *r, char reg) +{ + int i; + for(i = 0; i < (int)r->ptr; i++) + if(r->reg[i] == reg) return i; + return -1; } static char * -create_rule(Rule *r, int id, char *s) +compile_rule(Rule *r, int id, char *src) { - r->id = id, s += 2; - r->a = dict_, s = parse_frag(s); - r->b = dict_, s = parse_frag(s); - return s; + char c, *cap; + int wrapped, reg; + r->id = id, r->ptr = 0, r->a = &empty, r->b = ∅ + /* phase: compile left */ + while((c = *src) && c == ' ') src++; + if(c == ')' || (c == '<' && src[1] == '>')) return src; + r->a = dict_, cap = walk(src), wrapped = c == '('; + if(wrapped) src++, cap--; + while(src < cap) { + c = *src, *dict_++ = *src++; + if(c == '?') { + c = *src++, reg = find_register(r, c); + if(reg == -1 && c != '(') + r->reg[r->ptr] = c, reg = r->ptr++; + *dict_++ = '0' + reg; + } + } + src += wrapped, *dict_++ = 0; + /* phase: compile right */ + while((c = *src) && c == ' ') src++; + if(c == ')' || (c == '<' && src[1] == '>')) return src; + r->b = dict_, cap = walk(src), wrapped = c == '('; + if(wrapped) src++, cap--; + while(src < cap) { + c = *src, *dict_++ = *src++; + if(c == '?') { + c = *src++, reg = find_register(r, c); + *dict_++ = reg != -1 ? '0' + reg : c; + } + } + src += wrapped, *dict_++ = 0; + return src; } static int rewrite(void) { - char c, last = 0, *cap, *s = src_, *res; - while((c = *s) && c <= ' ') s++; + char c, last = 0, *cap, *s = src_; + while(*s == ' ') s++; while((c = *s)) { - if(spacer(last)) { - Rule *r; - if(*s == '<' && s[1] == '>') { + if(c == '(' || spacer(last)) { + Rule *r = NULL; + /* phase: rule */ + if(c == '<' && s[1] == '>') { r = rules_++; - s = create_rule(r, rules_ - rules - 1, s); - while((c = *s) && c <= ' ') s++; - return commit_rule(r, s, 1); + s = compile_rule(r, rules_ - rules - 1, s + 2); + while(*s == ' ') s++; + return write_rule(r, s, 1); } - if(*s == '?' && s[1] == '(') { - r = &lambda, cap = walk(s + 1); - create_rule(&lambda, -1, s), s = cap; - while((c = *s) && c <= ' ') s++; - if((res = match_rule(&lambda, s)) != NULL) - return write_rule(&lambda, last, res); + /* phase: lambda */ + if(c == '?' && s[1] == '(') { + cap = walk(s + 1), compile_rule(&lambda, -1, s + 2), s = cap; + while(*s == ' ') s++; + if(!apply_rule(&lambda, s)) write_rule(&lambda, s, 0); + return 1; } + /* phase: match */ for(r = rules; r < rules_; r++) - if((res = match_rule(r, s)) != NULL) - return write_rule(r, last, res); + if(apply_rule(r, s)) return 1; } *dst_++ = last = c; s++; @@ -216,31 +231,38 @@ int main(int argc, char **argv) { FILE *f; - int i; - char c, *w = bank_a; + int i, pl = 0, pr = 0; + char c, last = 0, *w = bank_a; if(argc < 2) return !printf("usage: modal [-vqn] source.modal\n"); for(i = 1; i < argc && *argv[i] == '-'; i++) { switch(argv[i][1]) { - case 'v': /* version */ return !printf("Modal Interpreter, 18 Apr 2024.\n"); - case 'q': /* quiet */ fclose(stderr); break; + case 'v': /* version */ return !printf("Modal Interpreter, 26 Apr 2024.\n"); + case 'q': /* quiet */ quiet = 1; break; case 'n': /* infinite */ cycles = 0xffffffff; break; } } if(!(f = fopen(argv[i], "r"))) - return !fprintf(stdout, "Invalid Modal file: %s.\n", argv[i]); + return !fprintf(stdout, "Modal file invalid: %s.\n", argv[i]); while(fread(&c, 1, 1, f)) { c = c <= 0x20 ? 0x20 : c; - if(w > bank_a) { - if(c == ' ' && *(w - 1) == '(') continue; - if(c == ')' && *(w - 1) == ' ') w--; - if(c == ' ' && *(w - 1) == ' ') w--; - } - *w++ = c; + if(c == ' ' && last == '(') continue; + if(c == ')' && last == ' ') w--; + if(c == ' ' && last == ' ') w--; + if(c == '(') pl++; + if(c == ')') pr++; + if(c == '(' && last != '?' && !spacer(last)) *w++ = ' '; + if(last == ')' && !spacer(c)) *w++ = ' '; + *w++ = last = c; } while(*(--w) <= ' ') *w = 0; fclose(f); + if(pr != pl) + return !fprintf(stdout, "Modal program imbalanced.\n"); while(rewrite()) if(!cycles--) return !fprintf(stdout, "Modal rewrites exceeded.\n"); + while(rules_-- > rules && !quiet) + if(!rules_->refs) printf("-- Unused rule: %d <> (%s) (%s)\n", rules_->refs, rules_->a, rules_->b); + if(!quiet) printf(".. %s\n", src_); return 0; } \ No newline at end of file