├── test ├── unit │ ├── out-dir │ │ ├── .gitignore │ │ ├── src │ │ │ ├── machine.lucy │ │ │ └── nested │ │ │ │ └── machine.lucy │ │ └── test.sh │ └── remote_imports │ │ ├── input.lucy │ │ ├── expected.js │ │ └── test.sh ├── snapshots │ ├── error_no_state_name │ │ ├── input.lucy │ │ └── expected.error │ ├── dts_entry-action │ │ ├── input.lucy │ │ └── expected.d.ts │ ├── no_target │ │ ├── input.lucy │ │ └── expected.js │ ├── empty_state │ │ ├── input.lucy │ │ └── expected.js │ ├── dts_assign │ │ ├── input.lucy │ │ └── expected.d.ts │ ├── dts_immediate-action │ │ ├── input.lucy │ │ └── expected.d.ts │ ├── dts_immediate-assign │ │ ├── input.lucy │ │ └── expected.d.ts │ ├── spawn_ext │ │ ├── input.lucy │ │ └── expected.js │ ├── always │ │ ├── input.lucy │ │ └── expected.js │ ├── toggle │ │ ├── input.lucy │ │ └── expected.js │ ├── assign_indent │ │ ├── input.lucy │ │ └── expected.js │ ├── inline_action │ │ ├── input.lucy │ │ └── expected.js │ ├── error_no_paren │ │ ├── input.lucy │ │ └── expected.error │ ├── on │ │ ├── input.lucy │ │ └── expected.js │ ├── inline_assign │ │ ├── input.lucy │ │ └── expected.js │ ├── multi_machine_action │ │ ├── input.lucy │ │ └── expected.js │ ├── inline_guard │ │ ├── input.lucy │ │ └── expected.js │ ├── entry_exit │ │ ├── input.lucy │ │ └── expected.js │ ├── dot-event │ │ ├── input.lucy │ │ └── expected.js │ ├── delay │ │ ├── input.lucy │ │ └── expected.js │ ├── comments │ │ ├── input.lucy │ │ └── expected.js │ ├── invoke_machine │ │ ├── input.lucy │ │ └── expected.js │ ├── invoke │ │ ├── input.lucy │ │ └── expected.js │ ├── error_double_digit │ │ ├── input.lucy │ │ └── expected.error │ ├── transition_block │ │ ├── input.lucy │ │ └── expected.js │ ├── machine │ │ ├── input.lucy │ │ └── expected.js │ ├── guards │ │ ├── input.lucy │ │ └── expected.js │ ├── actor │ │ ├── input.lucy │ │ └── expected.js │ ├── guards_and_actions │ │ ├── input.lucy │ │ └── expected.js │ ├── nested_state │ │ ├── input.lucy │ │ └── expected.js │ ├── symbols │ │ ├── input.lucy │ │ └── expected.js │ └── dts_symbols │ │ ├── input.lucy │ │ └── expected.d.ts ├── demos │ ├── editable-title │ │ ├── logic.js │ │ ├── machine.lucy │ │ └── index.html │ └── playground.html └── browser.html ├── .gitignore ├── src ├── core │ ├── identifier.h │ ├── parser │ │ ├── use.h │ │ ├── invoke.h │ │ ├── machine_state.h │ │ ├── transition.h │ │ ├── local.h │ │ ├── spawn.h │ │ ├── machine.h │ │ ├── delay.h │ │ ├── action.h │ │ ├── guard.h │ │ ├── send.h │ │ ├── assign.h │ │ ├── parser.h │ │ ├── core.h │ │ ├── token.h │ │ ├── send.c │ │ ├── spawn.c │ │ ├── parser.c │ │ ├── delay.c │ │ ├── invoke.c │ │ ├── assign.c │ │ ├── guard.c │ │ ├── use.c │ │ ├── local.c │ │ ├── machine.c │ │ ├── machine_state.c │ │ ├── action.c │ │ └── core.c │ ├── xstate │ │ ├── import.h │ │ ├── assignment.h │ │ ├── invoke.h │ │ ├── state.h │ │ ├── machine.h │ │ ├── transition.h │ │ ├── compiler.h │ │ ├── assignment.c │ │ ├── import.c │ │ ├── core.h │ │ ├── invoke.c │ │ ├── ts_printer.h │ │ ├── state.c │ │ ├── core.c │ │ ├── compiler.c │ │ └── machine.c │ ├── local.h │ ├── program.h │ ├── timeframe.h │ ├── error.h │ ├── program.c │ ├── keyword.h │ ├── dict.h │ ├── node │ │ ├── expression.h │ │ └── expression.c │ ├── local.c │ ├── string_list.h │ ├── keyword.c │ ├── js_builder.h │ ├── state.h │ ├── string_list.c │ ├── identifier.c │ ├── timeframe.c │ ├── ht.h │ ├── str_builder.h │ ├── dict.c │ ├── state.c │ ├── str_builder.c │ ├── error.c │ ├── js_builder.c │ ├── node.h │ ├── set.h │ └── ht.c ├── wasm │ └── main.c └── pre_js.js ├── main-node-dev.mjs ├── main-node-prod.mjs ├── main-browser-dev.js ├── main-browser-prod.js ├── scripts ├── mk_node_mjs ├── post_compile_mjs ├── test_unit ├── post_transform ├── lucyc.mjs └── test_snapshots ├── liblucy.d.ts ├── readme.md ├── .github └── workflows │ ├── lucy.yml │ └── release.yml ├── package.json ├── liblucy.mjs └── Makefile /test/unit/out-dir/.gitignore: -------------------------------------------------------------------------------- 1 | test-* -------------------------------------------------------------------------------- /test/unit/remote_imports/input.lucy: -------------------------------------------------------------------------------- 1 | state idle {} -------------------------------------------------------------------------------- /test/snapshots/error_no_state_name/input.lucy: -------------------------------------------------------------------------------- 1 | 2 | state { 3 | 4 | } -------------------------------------------------------------------------------- /test/unit/out-dir/src/machine.lucy: -------------------------------------------------------------------------------- 1 | 2 | state one {} 3 | state two {} -------------------------------------------------------------------------------- /test/unit/out-dir/src/nested/machine.lucy: -------------------------------------------------------------------------------- 1 | 2 | state three {} 3 | state four {} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | dist/ 3 | build/ 4 | 5 | test/demos/editable-title/machine.js -------------------------------------------------------------------------------- /test/snapshots/dts_entry-action/input.lucy: -------------------------------------------------------------------------------- 1 | state error { 2 | @entry => action(:log) 3 | } -------------------------------------------------------------------------------- /test/snapshots/no_target/input.lucy: -------------------------------------------------------------------------------- 1 | initial state initial { 2 | test => assign(someValue) 3 | } -------------------------------------------------------------------------------- /test/snapshots/empty_state/input.lucy: -------------------------------------------------------------------------------- 1 | 2 | state one { 3 | 4 | } 5 | 6 | state two { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/core/identifier.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void identifier_init(); 4 | int is_valid_identifier_char(char); -------------------------------------------------------------------------------- /src/core/parser/use.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../state.h" 4 | 5 | int parser_consume_use(State*); -------------------------------------------------------------------------------- /src/core/parser/invoke.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../state.h" 4 | 5 | int parser_consume_invoke(State*); -------------------------------------------------------------------------------- /src/core/parser/machine_state.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../state.h" 4 | 5 | int parser_consume_state(State*); -------------------------------------------------------------------------------- /src/core/parser/transition.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../state.h" 4 | 5 | int parser_consume_transition(State*); -------------------------------------------------------------------------------- /test/snapshots/dts_assign/input.lucy: -------------------------------------------------------------------------------- 1 | state first { 2 | go => assign(prop, :val) => second 3 | } 4 | 5 | state second {} -------------------------------------------------------------------------------- /test/snapshots/dts_immediate-action/input.lucy: -------------------------------------------------------------------------------- 1 | state first { 2 | => action(:log) => second 3 | } 4 | 5 | state second {} -------------------------------------------------------------------------------- /src/core/parser/local.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../state.h" 4 | 5 | int parser_consume_local(State*, unsigned short); -------------------------------------------------------------------------------- /test/snapshots/dts_immediate-assign/input.lucy: -------------------------------------------------------------------------------- 1 | state first { 2 | => assign(prop, :val) => second 3 | } 4 | 5 | state second {} -------------------------------------------------------------------------------- /test/snapshots/spawn_ext/input.lucy: -------------------------------------------------------------------------------- 1 | 2 | state init { 3 | => assign(mac, spawn(:external)) => idle 4 | } 5 | 6 | state idle { 7 | 8 | } -------------------------------------------------------------------------------- /test/snapshots/always/input.lucy: -------------------------------------------------------------------------------- 1 | 2 | initial state one { 3 | => guard(:canGo) => two 4 | => two 5 | } 6 | 7 | final state two { 8 | 9 | } -------------------------------------------------------------------------------- /test/snapshots/toggle/input.lucy: -------------------------------------------------------------------------------- 1 | state enabled { 2 | toggle => disabled 3 | } 4 | 5 | initial state disabled { 6 | toggle => enabled 7 | } -------------------------------------------------------------------------------- /test/snapshots/assign_indent/input.lucy: -------------------------------------------------------------------------------- 1 | use './util' { logger } 2 | 3 | action log = logger 4 | 5 | state idle { 6 | go => assign(prop) => log 7 | } -------------------------------------------------------------------------------- /src/core/parser/spawn.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../node.h" 4 | #include "../state.h" 5 | 6 | int parser_consume_inline_spawn(State*, AssignExpression*); -------------------------------------------------------------------------------- /src/wasm/main.c: -------------------------------------------------------------------------------- 1 | #include "../core/identifier.h" 2 | #include "../core/parser/parser.h" 3 | 4 | int main() { 5 | identifier_init(); 6 | parser_init(); 7 | } -------------------------------------------------------------------------------- /src/core/parser/machine.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../state.h" 4 | 5 | int parser_consume_machine(State*); 6 | int parser_consume_implicit_machine(State*, int); -------------------------------------------------------------------------------- /test/snapshots/inline_action/input.lucy: -------------------------------------------------------------------------------- 1 | use './util' { pet } 2 | 3 | initial state idle { 4 | meet => action(pet) => goodBoy 5 | } 6 | 7 | final state goodBoy {} -------------------------------------------------------------------------------- /src/core/parser/delay.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../node.h" 4 | #include "../state.h" 5 | 6 | int parser_consume_inline_delay(State*, TransitionNode*, StateNode*); -------------------------------------------------------------------------------- /test/snapshots/error_no_paren/input.lucy: -------------------------------------------------------------------------------- 1 | use './stuff' { update } 2 | 3 | state idle { 4 | next => assign update => finished 5 | } 6 | 7 | final state finished { 8 | 9 | } -------------------------------------------------------------------------------- /src/core/xstate/import.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../js_builder.h" 4 | #include "../node.h" 5 | #include "core.h" 6 | 7 | void xs_enter_import(PrintState*, JSBuilder*, Node*); -------------------------------------------------------------------------------- /src/core/parser/action.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../node.h" 4 | #include "../state.h" 5 | 6 | int parser_consume_inline_action(State*, Node*); 7 | int parser_consume_action(State*); -------------------------------------------------------------------------------- /src/core/xstate/assignment.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../js_builder.h" 4 | #include "../node.h" 5 | #include "core.h" 6 | 7 | void xs_enter_assignment(PrintState*, JSBuilder*, Node*); -------------------------------------------------------------------------------- /src/core/xstate/invoke.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../js_builder.h" 4 | #include "../node.h" 5 | #include "core.h" 6 | 7 | void xs_compile_invoke(PrintState*, JSBuilder*, InvokeNode*); -------------------------------------------------------------------------------- /test/snapshots/on/input.lucy: -------------------------------------------------------------------------------- 1 | 2 | initial state idle { 3 | on(purchase) => end 4 | on(delay) => end 5 | SNAKE_CASE => end 6 | on(ANOTHER_SNAKE) => end 7 | } 8 | 9 | final state end {} -------------------------------------------------------------------------------- /src/core/local.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define LOCAL_ENTRY 1 4 | #define LOCAL_EXIT 2 5 | 6 | unsigned short local_get(char*); 7 | const char* local_get_name(unsigned short); 8 | void local_init(); -------------------------------------------------------------------------------- /src/core/parser/guard.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../node.h" 4 | #include "../state.h" 5 | 6 | int parser_consume_inline_guard(State*, TransitionNode*); 7 | int parser_consume_guard(State*); -------------------------------------------------------------------------------- /test/snapshots/inline_assign/input.lucy: -------------------------------------------------------------------------------- 1 | use './util' { pet } 2 | 3 | initial state idle { 4 | invoke(pet) { 5 | done => assign(wilbur) => goodBoy 6 | } 7 | } 8 | 9 | final state goodBoy {} -------------------------------------------------------------------------------- /test/snapshots/multi_machine_action/input.lucy: -------------------------------------------------------------------------------- 1 | use './util' { logger } 2 | 3 | machine one { 4 | action log = logger 5 | 6 | state idle { 7 | go => log 8 | } 9 | } 10 | 11 | machine two {} -------------------------------------------------------------------------------- /test/snapshots/inline_guard/input.lucy: -------------------------------------------------------------------------------- 1 | use './util' { isDog } 2 | 3 | initial state idle { 4 | pet => guard(isDog) => pet 5 | } 6 | 7 | state pet { 8 | => goodBoy 9 | } 10 | 11 | final state goodBoy {} -------------------------------------------------------------------------------- /src/core/parser/send.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../node.h" 4 | #include "../state.h" 5 | 6 | int parser_consume_inline_send_args(State*, void*, int, char*, int); 7 | int parser_consume_inline_send(State*, Node*); -------------------------------------------------------------------------------- /test/snapshots/entry_exit/input.lucy: -------------------------------------------------------------------------------- 1 | use './util' { logger } 2 | 3 | action logSomething = logger 4 | 5 | state idle { 6 | @entry => logSomething 7 | @exit => assign(wilbur) 8 | } 9 | 10 | final state end {} -------------------------------------------------------------------------------- /src/core/parser/assign.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../node.h" 4 | #include "../state.h" 5 | 6 | int parser_consume_inline_assign_args(State*, void*, int, char*, int); 7 | int parser_consume_inline_assign(State*, Node*); -------------------------------------------------------------------------------- /src/core/xstate/state.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../js_builder.h" 4 | #include "../node.h" 5 | #include "core.h" 6 | 7 | void xs_enter_state(PrintState*, JSBuilder*, Node*); 8 | void xs_exit_state(PrintState*, JSBuilder*, Node*); -------------------------------------------------------------------------------- /test/snapshots/dot-event/input.lucy: -------------------------------------------------------------------------------- 1 | initial state idle { 2 | fetch => fetching 3 | 4 | machine idling { 5 | initial state noError {} 6 | 7 | state errored {} 8 | } 9 | } 10 | 11 | state fetching { 12 | reportError => idle.errored 13 | } -------------------------------------------------------------------------------- /main-node-dev.mjs: -------------------------------------------------------------------------------- 1 | import createModule from './dist/liblucy-debug-node.mjs'; 2 | import init from './liblucy.mjs'; 3 | 4 | export let compileXstate; 5 | 6 | export let ready = init(createModule).then(exports => { 7 | compileXstate = exports.compileXstate; 8 | }); -------------------------------------------------------------------------------- /main-node-prod.mjs: -------------------------------------------------------------------------------- 1 | import createModule from './dist/liblucy-release-node.mjs'; 2 | import init from './liblucy.mjs'; 3 | 4 | export let compileXstate; 5 | 6 | export let ready = init(createModule).then(exports => { 7 | compileXstate = exports.compileXstate; 8 | }); -------------------------------------------------------------------------------- /main-browser-dev.js: -------------------------------------------------------------------------------- 1 | import createModule from './dist/liblucy-debug-browser.mjs'; 2 | import init from './liblucy.mjs'; 3 | 4 | export let compileXstate; 5 | 6 | export let ready = init(createModule).then(exports => { 7 | compileXstate = exports.compileXstate; 8 | }); -------------------------------------------------------------------------------- /main-browser-prod.js: -------------------------------------------------------------------------------- 1 | import createModule from './dist/liblucy-release-browser.mjs'; 2 | import init from './liblucy.mjs'; 3 | 4 | export let compileXstate; 5 | 6 | export let ready = init(createModule).then(exports => { 7 | compileXstate = exports.compileXstate; 8 | }); -------------------------------------------------------------------------------- /test/demos/editable-title/logic.js: -------------------------------------------------------------------------------- 1 | 2 | export const title = ({title}) => title; 3 | 4 | export const setTitle = (ctx, ev) => ev.target.value; 5 | 6 | export const resetTitle = ({oldTitle}) => oldTitle; 7 | 8 | export const titleIsValid = ({title}) => title.length > 0; -------------------------------------------------------------------------------- /src/core/parser/parser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "../program.h" 5 | 6 | typedef struct ParseResult { 7 | bool success; 8 | Program* program; 9 | } ParseResult; 10 | 11 | ParseResult* parse(char*, char*); 12 | void parser_init(); -------------------------------------------------------------------------------- /test/snapshots/delay/input.lucy: -------------------------------------------------------------------------------- 1 | use './util' { calcLightDelay } 2 | 3 | initial state green { 4 | delay(1s) => yellow 5 | } 6 | 7 | state yellow { 8 | go => green 9 | delay(500) => red 10 | } 11 | 12 | state red { 13 | delay(calcLightDelay) => green 14 | } 15 | -------------------------------------------------------------------------------- /scripts/mk_node_mjs: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | in_mjs=$1 4 | out_node=$2 5 | 6 | # Create Node.js version 7 | echo "import fs from 'fs';" > $out_node 8 | echo "import path from 'path';" >> $out_node 9 | echo "const require = Function.prototype;" >> $out_node 10 | cat $in_mjs >> $out_node -------------------------------------------------------------------------------- /test/snapshots/comments/input.lucy: -------------------------------------------------------------------------------- 1 | 2 | state one { 3 | // Comment here 4 | 5 | go => another 6 | } 7 | 8 | // Comment about this state. 9 | state another { 10 | last => third 11 | } 12 | 13 | /** 14 | * Some comments about the state go here. 15 | */ 16 | state third {} -------------------------------------------------------------------------------- /test/unit/remote_imports/expected.js: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'https://cdn.skypack.dev/xstate'; 2 | 3 | export default function({ context = {} } = {}) { 4 | return createMachine({ 5 | context, 6 | states: { 7 | idle: { 8 | 9 | } 10 | } 11 | }); 12 | } -------------------------------------------------------------------------------- /test/snapshots/empty_state/expected.js: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | export default function({ context = {} } = {}) { 4 | return createMachine({ 5 | context, 6 | states: { 7 | one: { 8 | 9 | }, 10 | two: { 11 | 12 | } 13 | } 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /liblucy.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace LibLucy { 2 | interface CompileXStateOptions { 3 | useRemote: boolean; 4 | } 5 | 6 | export const ready: Promise; 7 | 8 | export function compileXstate(source: string, filename: string, options?: CompileXStateOptions): string; 9 | } 10 | 11 | export = LibLucy; 12 | -------------------------------------------------------------------------------- /test/snapshots/invoke_machine/input.lucy: -------------------------------------------------------------------------------- 1 | 2 | machine minute { 3 | initial state active { 4 | timer => finished 5 | } 6 | 7 | final state finished {} 8 | } 9 | 10 | machine parent { 11 | initial state pending { 12 | invoke(minute) { 13 | done => timesUp 14 | } 15 | } 16 | 17 | final state timesUp {} 18 | } -------------------------------------------------------------------------------- /src/core/program.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "node.h" 4 | 5 | #define PROGRAM_USES_ASSIGN 1 << 0 6 | #define PROGRAM_USES_SPAWN 1 << 1 7 | #define PROGRAM_USES_SEND 1 << 2 8 | 9 | typedef struct Program { 10 | Node* body; 11 | int flags; 12 | } Program; 13 | 14 | Program * new_program(); 15 | void program_add_flag(Program*, int); -------------------------------------------------------------------------------- /test/snapshots/invoke/input.lucy: -------------------------------------------------------------------------------- 1 | use './user.js' { getUser, setUser } 2 | 3 | action assignUser = assign(user, setUser) 4 | 5 | state loading { 6 | again => loading 7 | invoke(getUser) { 8 | done => assignUser => ready 9 | error => error 10 | } 11 | } 12 | 13 | state ready { 14 | 15 | } 16 | 17 | state error { 18 | 19 | } -------------------------------------------------------------------------------- /src/core/xstate/machine.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../js_builder.h" 4 | #include "../node.h" 5 | #include "core.h" 6 | #include "ts_printer.h" 7 | 8 | void xs_add_machine_binding_name(PrintState*, JSBuilder*, MachineNode*); 9 | void xs_enter_machine(PrintState*, JSBuilder*, Node*); 10 | void xs_exit_machine(PrintState*, JSBuilder*, Node*); -------------------------------------------------------------------------------- /test/snapshots/error_double_digit/input.lucy: -------------------------------------------------------------------------------- 1 | use './stuff.js' { check } 2 | 3 | guard canGo = check 4 | guard sureCanGo = check 5 | guard AreWeReallySure = check 6 | 7 | initial state start { 8 | go => { 9 | canGo => 10 | sureCanGo [ 11 | AreWeReallySure => 12 | end 13 | } 14 | } 15 | 16 | final state end { 17 | 18 | } -------------------------------------------------------------------------------- /test/snapshots/transition_block/input.lucy: -------------------------------------------------------------------------------- 1 | use './stuff.js' { check } 2 | 3 | guard canGo = check 4 | guard sureCanGo = check 5 | guard AreWeReallySure = check 6 | 7 | initial state start { 8 | go => { 9 | canGo => 10 | sureCanGo => 11 | AreWeReallySure => 12 | end 13 | } 14 | } 15 | 16 | final state end { 17 | 18 | } -------------------------------------------------------------------------------- /src/core/timeframe.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef struct Timeframe { 6 | bool is_integer; 7 | int time; 8 | char* error; 9 | } Timeframe; 10 | 11 | bool is_integer(char); 12 | bool is_timeframe_char(char); 13 | bool timeframe_is_suffix(char*); 14 | Timeframe timeframe_parse(char*, size_t); 15 | void timeframe_init(); -------------------------------------------------------------------------------- /test/snapshots/machine/input.lucy: -------------------------------------------------------------------------------- 1 | 2 | machine light { 3 | initial state green { 4 | timer => yellow 5 | } 6 | 7 | state yellow { 8 | timer => red 9 | } 10 | 11 | state red { 12 | timer => green 13 | } 14 | } 15 | 16 | machine two { 17 | state start { 18 | next => end 19 | } 20 | final state end { 21 | 22 | } 23 | } -------------------------------------------------------------------------------- /test/snapshots/error_no_state_name/expected.error: -------------------------------------------------------------------------------- 1 | test/snapshots/error_no_state_name/input.lucy:2:6 2 | 3 | 𝒙 States must be given a name. 4 | 5 |  1 │ 6 |  2 │ state { 7 | ˄ 8 |  3 │ 9 |  4 │ } 10 | 11 | Compilation failed! 12 | -------------------------------------------------------------------------------- /src/core/error.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "node.h" 4 | #include "state.h" 5 | 6 | void error_message(char*); 7 | void error_msg_with_code_block(State*, Node*, const char*); 8 | void error_msg_with_code_block_pos(State*, pos_t*, const char*); 9 | void error_msg_with_code_block_dec(State*, int, const char*); 10 | void error_unexpected_identifier(State*, Node*); -------------------------------------------------------------------------------- /src/core/parser/core.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../state.h" 4 | 5 | #define _check(f) { int _fa = f; if(_fa == 2) { return 2; } else if(_fa > err) { err = _fa; } } 6 | 7 | typedef int (*consume_call_expr_args)(State*, void*, int, char*, int); 8 | 9 | int consume_token(State*); 10 | int consume_call_expression(State*, const char*, void*, consume_call_expr_args on_args); -------------------------------------------------------------------------------- /scripts/post_compile_mjs: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | mjs_file=$1 4 | SED=gsed 5 | 6 | if ! command -v gsed &> /dev/null 7 | then 8 | SED=sed 9 | fi 10 | 11 | $SED -i "s/IMPORT_META_URL/import.meta.url/g" $mjs_file 12 | $SED -i "s/require(['\"]path['\"])/path/g" $mjs_file 13 | $SED -i "s/require(['\"]fs['\"])/fs/g" $mjs_file 14 | $SED -i "s/__dirname/new URL(import.meta.url)/g" $mjs_file 15 | -------------------------------------------------------------------------------- /src/core/program.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "program.h" 3 | #include "node.h" 4 | 5 | Program * new_program() { 6 | Program * program = malloc(sizeof(Program)); 7 | program->body = NULL; 8 | program->flags = 0; 9 | return program; 10 | } 11 | 12 | __attribute__((always_inline)) inline void program_add_flag(Program* program, int flag) { 13 | program->flags |= flag; 14 | } 15 | -------------------------------------------------------------------------------- /test/snapshots/guards/input.lucy: -------------------------------------------------------------------------------- 1 | use './util' { isValidCard, isInvalidCard, moneyWithdrawn } 2 | 3 | guard isMoneyWithdrawn = moneyWithdrawn 4 | 5 | initial state idle { 6 | next => guard(isValidCard) => purchase 7 | next => guard(isInvalidCard) => error 8 | } 9 | 10 | state purchase { 11 | => isMoneyWithdrawn => purchased 12 | } 13 | 14 | final state purchased {} 15 | 16 | final state error {} -------------------------------------------------------------------------------- /test/snapshots/actor/input.lucy: -------------------------------------------------------------------------------- 1 | 2 | machine other { 3 | state only { 4 | run => only 5 | } 6 | } 7 | 8 | action makeThing = assign(second, spawn(other)) 9 | action sendThing = send(second, run) 10 | 11 | state idle { 12 | on(event) => assign(first, spawn(other)) => idle 13 | another => makeThing => idle 14 | } 15 | 16 | state end { 17 | click => send(first, run) => end 18 | dblclick => sendThing => end 19 | } -------------------------------------------------------------------------------- /test/unit/out-dir/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | dir=$(dirname "$0") 4 | 5 | outdir="$dir/test-out" 6 | srcdir="$dir/src" 7 | 8 | # Before 9 | rm -rf $outdir 10 | 11 | # Run compiler 12 | $LUCYC --out-dir $outdir $srcdir 13 | 14 | # Test to see if expected files exist. 15 | if [ ! -f "$outdir/machine.js" ]; then 16 | exit 1 17 | fi 18 | 19 | if [ ! -f "$outdir/nested/machine.js" ]; then 20 | exit 1 21 | fi 22 | 23 | exit 0 -------------------------------------------------------------------------------- /test/snapshots/dts_entry-action/expected.d.ts: -------------------------------------------------------------------------------- 1 | import { StateMachine } from 'xstate'; 2 | 3 | export interface CreateMachineOptions, TEvent> { 4 | context?: TContext, 5 | actions: { 6 | log: Action 7 | } 8 | } 9 | 10 | export default function createMachine, TEvent>(options: CreateMachineOptions): StateMachine; 11 | -------------------------------------------------------------------------------- /test/snapshots/guards_and_actions/input.lucy: -------------------------------------------------------------------------------- 1 | use './actions.js' { 2 | incrementCount, 3 | decrementCount, 4 | lessThanTen, 5 | greaterThanZero 6 | } 7 | 8 | action increment = assign(count, incrementCount) 9 | action decrement = assign(count, decrementCount) 10 | 11 | guard isNotMax = lessThanTen 12 | guard isNotMin = greaterThanZero 13 | 14 | initial state active { 15 | inc => isNotMax => increment 16 | dec => isNotMin => decrement 17 | } -------------------------------------------------------------------------------- /src/core/xstate/transition.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../js_builder.h" 4 | #include "../node.h" 5 | #include "core.h" 6 | 7 | void xs_compile_event_transition(PrintState*, JSBuilder*, TransitionNode*); 8 | void xs_compile_transition_action(PrintState*, JSBuilder*, TransitionAction*, const char*); 9 | void xs_compile_transition_key(PrintState*, JSBuilder*, Node*, char*); 10 | void xs_compile_inner_transition(PrintState*, JSBuilder*, TransitionNode*); -------------------------------------------------------------------------------- /test/snapshots/comments/expected.js: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | export default function({ context = {} } = {}) { 4 | return createMachine({ 5 | context, 6 | states: { 7 | one: { 8 | on: { 9 | go: 'another' 10 | } 11 | }, 12 | another: { 13 | on: { 14 | last: 'third' 15 | } 16 | }, 17 | third: { 18 | 19 | } 20 | } 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /test/snapshots/toggle/expected.js: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | export default function({ context = {} } = {}) { 4 | return createMachine({ 5 | initial: 'disabled', 6 | context, 7 | states: { 8 | enabled: { 9 | on: { 10 | toggle: 'disabled' 11 | } 12 | }, 13 | disabled: { 14 | on: { 15 | toggle: 'enabled' 16 | } 17 | } 18 | } 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /src/core/xstate/compiler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef struct CompileResult { 6 | bool success; 7 | char* js; 8 | char* dts; 9 | int flags; 10 | } CompileResult; 11 | 12 | CompileResult* xs_create(); 13 | void xs_init(CompileResult*, bool, bool); 14 | void compile_xstate(CompileResult*, char*, char*); 15 | char* xs_get_js(CompileResult*); 16 | char* xs_get_dts(CompileResult*); 17 | void destroy_xstate_result(CompileResult*); -------------------------------------------------------------------------------- /test/snapshots/error_no_paren/expected.error: -------------------------------------------------------------------------------- 1 | test/snapshots/error_no_paren/input.lucy:4:18 2 | 3 | 𝒙 assign must be called like a function. Expected assign(update) 4 | 5 |  2 │ 6 |  3 │ state idle { 7 |  4 │ next => assign update => finished 8 | ˄ 9 |  5 │ } 10 |  6 │ 11 | 12 | 13 | Compilation failed! 14 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # liblucy 2 | 3 | __liblucy__ is the core piece of [Lucy](https://lucylang.org/) the DSL. It contains the core compiler that is compiled to wasm for usage in JavaScript environments such as Node.js. It also includes the CLI compiler, `lucyc`. 4 | 5 | ## Contributing 6 | 7 | See the [contributing page](https://lucylang.org/docs/contributing/) for information on how to build liblucy and the workflow for making changes. 8 | 9 | ## License 10 | 11 | BSD-2-Clause -------------------------------------------------------------------------------- /test/unit/remote_imports/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | dir=$(dirname "$0") 4 | 5 | ret=0 6 | input="$dir/input.lucy" 7 | expected="$dir/expected.js" 8 | 9 | # Before 10 | tmp="$(mktemp -d)/out.js" 11 | 12 | # Run compiler 13 | $LUCYC --remote-imports --out-file $tmp $input 14 | 15 | d=$(diff $expected $tmp | colordiff) 16 | 17 | if [ ${#d} -ge 1 ]; then 18 | echo -e "${red}FAILED${nc} - $input" 19 | echo "" 20 | echo "$d" 21 | 22 | ret=1 23 | fi 24 | 25 | exit $ret -------------------------------------------------------------------------------- /test/snapshots/error_double_digit/expected.error: -------------------------------------------------------------------------------- 1 | test/snapshots/error_double_digit/input.lucy:10:17 2 | 3 | 𝒙 Expected a destination state for this event. 4 | 5 |  8 │ go => { 6 |  9 │ canGo => 7 |  10 │ sureCanGo [ 8 | ˄ 9 |  11 │ AreWeReallySure => 10 |  12 │ end 11 | 12 | 13 | Compilation failed! 14 | -------------------------------------------------------------------------------- /test/snapshots/dts_immediate-action/expected.d.ts: -------------------------------------------------------------------------------- 1 | import { Action, StateMachine } from 'xstate'; 2 | 3 | export interface CreateMachineOptions, TEvent> { 4 | context?: TContext, 5 | actions: { 6 | log: Action< 7 | TContext, 8 | TEvent 9 | > 10 | } 11 | } 12 | 13 | export default function createMachine, TEvent>(options: CreateMachineOptions): StateMachine; 14 | -------------------------------------------------------------------------------- /test/snapshots/on/expected.js: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | export default function({ context = {} } = {}) { 4 | return createMachine({ 5 | initial: 'idle', 6 | context, 7 | states: { 8 | idle: { 9 | on: { 10 | purchase: 'end', 11 | delay: 'end', 12 | SNAKE_CASE: 'end', 13 | ANOTHER_SNAKE: 'end' 14 | } 15 | }, 16 | end: { 17 | type: 'final' 18 | } 19 | } 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /src/core/keyword.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define KW_NOT_A_KEYWORD 0 6 | #define KW_USE 1 7 | #define KW_STATE 2 8 | #define KW_INITIAL 3 9 | #define KW_FINAL 4 10 | #define KW_ACTION 5 11 | #define KW_GUARD 6 12 | #define KW_ASSIGN 7 13 | #define KW_INVOKE 8 14 | #define KW_MACHINE 9 15 | #define KW_DELAY 10 16 | #define KW_ON 11 17 | #define KW_SPAWN 12 18 | #define KW_SEND 13 19 | 20 | bool is_keyword(char*); 21 | unsigned short keyword_get(char*); 22 | void keyword_init(); -------------------------------------------------------------------------------- /src/core/dict.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct elt { 4 | struct elt *next; 5 | char *key; 6 | unsigned short value; 7 | }; 8 | 9 | typedef struct dict { 10 | int size; /* size of the pointer table */ 11 | int n; /* number of elements stored */ 12 | struct elt **table; 13 | } dict; 14 | 15 | unsigned long hash_function(const char*); 16 | dict* dict_create(void); 17 | void dict_insert(dict*, const char*, unsigned short); 18 | unsigned short dict_search(dict*, const char*); -------------------------------------------------------------------------------- /test/snapshots/nested_state/input.lucy: -------------------------------------------------------------------------------- 1 | 2 | machine light { 3 | initial state green { 4 | timer => yellow 5 | } 6 | 7 | state yellow { 8 | timer => red 9 | } 10 | 11 | state red { 12 | timer => green 13 | 14 | machine pedestrian { 15 | initial state walk { 16 | countdown => wait 17 | } 18 | 19 | state wait { 20 | countdown => stop 21 | } 22 | 23 | final state stop { 24 | 25 | } 26 | } 27 | } 28 | 29 | state another {} 30 | } -------------------------------------------------------------------------------- /test/snapshots/inline_action/expected.js: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | import { pet } from './util'; 3 | 4 | export default function({ context = {} } = {}) { 5 | return createMachine({ 6 | initial: 'idle', 7 | context, 8 | states: { 9 | idle: { 10 | on: { 11 | meet: { 12 | target: 'goodBoy', 13 | actions: [pet] 14 | } 15 | } 16 | }, 17 | goodBoy: { 18 | type: 'final' 19 | } 20 | } 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /test/snapshots/no_target/expected.js: -------------------------------------------------------------------------------- 1 | import { createMachine, assign } from 'xstate'; 2 | 3 | export default function({ context = {} } = {}) { 4 | return createMachine({ 5 | initial: 'initial', 6 | context, 7 | states: { 8 | initial: { 9 | on: { 10 | test: { 11 | actions: [ 12 | assign({ 13 | someValue: (context, event) => event.data 14 | }) 15 | ] 16 | } 17 | } 18 | } 19 | } 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /src/core/parser/token.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define TOKEN_EOF 0 4 | #define TOKEN_EOL 1 5 | #define TOKEN_IDENTIFIER 2 6 | #define TOKEN_ASSIGNMENT 3 7 | #define TOKEN_CALL 4 8 | #define TOKEN_BEGIN_BLOCK 5 9 | #define TOKEN_END_BLOCK 6 10 | #define TOKEN_STRING 7 11 | #define TOKEN_INTEGER 8 12 | #define TOKEN_TIMEFRAME 9 13 | #define TOKEN_UNKNOWN 10 14 | #define TOKEN_BEGIN_CALL 11 15 | #define TOKEN_END_CALL 12 16 | #define TOKEN_COMMA 13 17 | #define TOKEN_LOCAL 14 18 | #define TOKEN_SYMBOL 15 19 | #define TOKEN_MEMBER_EXPRESSION 16 -------------------------------------------------------------------------------- /scripts/test_unit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | LUCYC="${LUCYC:-bin/lucyc}" 4 | ret=0 5 | 6 | run_test() { 7 | local d=$1 8 | local skip="${d}.skip" 9 | 10 | if [ -f $skip ]; then 11 | return 0 12 | fi 13 | 14 | local test="${d}test.sh" 15 | LUCYC="$LUCYC" $test 16 | local r=$? 17 | 18 | if [ $ret -eq 0 ]; then 19 | ret=$r 20 | fi 21 | } 22 | 23 | # Allow specifying a folder 24 | folder=$@ 25 | 26 | if [ -z "$folder" ]; then 27 | for d in test/unit/*/ ; do 28 | run_test $d 29 | done 30 | else 31 | run_test $folder 32 | fi 33 | 34 | exit $ret -------------------------------------------------------------------------------- /src/core/xstate/assignment.c: -------------------------------------------------------------------------------- 1 | #include "../js_builder.h" 2 | #include "../node.h" 3 | #include "core.h" 4 | 5 | void xs_enter_assignment(PrintState* state, JSBuilder* jsb, Node* node) { 6 | Assignment* assignment = (Assignment*)node; 7 | 8 | switch(assignment->binding_type) { 9 | case ASSIGNMENT_ACTION: { 10 | xs_add_action_ref(state, assignment->binding_name, assignment->value); 11 | break; 12 | } 13 | case ASSIGNMENT_GUARD: { 14 | xs_add_guard_ref(state, assignment->binding_name, assignment->value); 15 | break; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /test/snapshots/symbols/input.lucy: -------------------------------------------------------------------------------- 1 | action logger = :doLog 2 | guard isValid = :checkValid 3 | 4 | initial state idle { 5 | next => isValid => guard(:check) => action(:log) => assign(name, :namer) => loading 6 | } 7 | 8 | state loading { 9 | @entry => action(:updateUI) => action(:log) => assign(count, :incrementLoads) 10 | 11 | invoke(:loadUsers) { 12 | done => loaded 13 | } 14 | } 15 | 16 | state loaded { 17 | delay(:wait) => logger => homescreen 18 | 19 | @exit => action(:log) 20 | } 21 | 22 | state homescreen { 23 | @entry => assign(todo, spawn(:todoMachine)) 24 | } -------------------------------------------------------------------------------- /test/snapshots/dts_symbols/input.lucy: -------------------------------------------------------------------------------- 1 | action logger = :doLog 2 | guard isValid = :checkValid 3 | 4 | initial state idle { 5 | next => isValid => guard(:check) => action(:log) => assign(name, :namer) => loading 6 | } 7 | 8 | state loading { 9 | @entry => action(:updateUI) => action(:log) => assign(count, :incrementLoads) 10 | 11 | invoke(:loadUsers) { 12 | done => loaded 13 | } 14 | } 15 | 16 | state loaded { 17 | delay(:wait) => logger => homescreen 18 | 19 | @exit => action(:log) 20 | } 21 | 22 | state homescreen { 23 | @entry => assign(todo, spawn(:todoMachine)) 24 | } -------------------------------------------------------------------------------- /src/core/node/expression.h: -------------------------------------------------------------------------------- 1 | 2 | void node_destroy_assignexpression(AssignExpression*); 3 | void node_destroy_identifierexpression(IdentifierExpression*); 4 | void node_destroy_symbolexpression(SymbolExpression*); 5 | void node_destroy_guardexpression(GuardExpression*); 6 | void node_destroy_actionexpression(ActionExpression*); 7 | void node_destroy_delayexpression(DelayExpression*); 8 | void node_destroy_onexpression(OnExpression*); 9 | void node_destroy_spawnexpression(SpawnExpression*); 10 | void node_destroy_sendexpression(SendExpression*); 11 | void node_destroy_invokeexpression(InvokeExpression*); -------------------------------------------------------------------------------- /test/snapshots/always/expected.js: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | export default function({ context = {}, guards } = {}) { 4 | return createMachine({ 5 | initial: 'one', 6 | context, 7 | states: { 8 | one: { 9 | always: [ 10 | { 11 | target: 'two', 12 | cond: 'canGo' 13 | }, { 14 | target: 'two' 15 | } 16 | ] 17 | }, 18 | two: { 19 | type: 'final' 20 | } 21 | } 22 | }, { 23 | guards: { 24 | canGo: guards.canGo 25 | } 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /test/snapshots/entry_exit/expected.js: -------------------------------------------------------------------------------- 1 | import { createMachine, assign } from 'xstate'; 2 | import { logger } from './util'; 3 | 4 | export default function({ context = {} } = {}) { 5 | return createMachine({ 6 | context, 7 | states: { 8 | idle: { 9 | entry: ['logSomething'], 10 | exit: [ 11 | assign({ 12 | wilbur: (context, event) => event.data 13 | }) 14 | ] 15 | }, 16 | end: { 17 | type: 'final' 18 | } 19 | } 20 | }, { 21 | actions: { 22 | logSomething: logger 23 | } 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /test/snapshots/multi_machine_action/expected.js: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | import { logger } from './util'; 3 | 4 | export function createOne({ context = {} } = {}) { 5 | return createMachine({ 6 | context, 7 | states: { 8 | idle: { 9 | on: { 10 | go: { 11 | actions: ['log'] 12 | } 13 | } 14 | } 15 | } 16 | }, { 17 | actions: { 18 | log: logger 19 | } 20 | }); 21 | } 22 | 23 | export function createTwo({ context = {} } = {}) { 24 | return createMachine({ 25 | context 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /test/snapshots/dts_immediate-assign/expected.d.ts: -------------------------------------------------------------------------------- 1 | import { PartialAssigner, StateMachine } from 'xstate'; 2 | 3 | type MachineKnownContextKeys = 'prop'; 4 | 5 | export interface CreateMachineOptions, TEvent> { 6 | context: TContext, 7 | assigns: { 8 | val: PartialAssigner< 9 | TContext, 10 | TEvent, 11 | 'prop' 12 | > 13 | } 14 | } 15 | 16 | export default function createMachine, TEvent>(options: CreateMachineOptions): StateMachine; 17 | -------------------------------------------------------------------------------- /test/snapshots/assign_indent/expected.js: -------------------------------------------------------------------------------- 1 | import { createMachine, assign } from 'xstate'; 2 | import { logger } from './util'; 3 | 4 | export default function({ context = {} } = {}) { 5 | return createMachine({ 6 | context, 7 | states: { 8 | idle: { 9 | on: { 10 | go: { 11 | actions: [ 12 | assign({ 13 | prop: (context, event) => event.data 14 | }), 15 | 'log' 16 | ] 17 | } 18 | } 19 | } 20 | } 21 | }, { 22 | actions: { 23 | log: logger 24 | } 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /src/core/local.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "dict.h" 4 | #include "local.h" 5 | 6 | static dict *locals; 7 | 8 | unsigned short local_get(char* key) { 9 | return dict_search(locals, key); 10 | } 11 | 12 | const char* local_get_name(unsigned short key) { 13 | if(key == LOCAL_ENTRY) { 14 | return "@entry"; 15 | } else if(key == LOCAL_EXIT) { 16 | return "@exit"; 17 | } 18 | // Should never happen. 19 | return NULL; 20 | } 21 | 22 | void local_init() { 23 | locals = dict_create(); 24 | 25 | dict_insert(locals, "@entry", LOCAL_ENTRY); 26 | dict_insert(locals, "@exit", LOCAL_EXIT); 27 | } -------------------------------------------------------------------------------- /test/snapshots/dot-event/expected.js: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | export default function({ context = {} } = {}) { 4 | return createMachine({ 5 | initial: 'idle', 6 | context, 7 | states: { 8 | idle: { 9 | on: { 10 | fetch: 'fetching' 11 | }, 12 | initial: 'noError', 13 | context, 14 | states: { 15 | noError: { 16 | 17 | }, 18 | errored: { 19 | 20 | } 21 | } 22 | }, 23 | fetching: { 24 | on: { 25 | reportError: 'idle.errored' 26 | } 27 | } 28 | } 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /test/snapshots/inline_guard/expected.js: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | import { isDog } from './util'; 3 | 4 | export default function({ context = {} } = {}) { 5 | return createMachine({ 6 | initial: 'idle', 7 | context, 8 | states: { 9 | idle: { 10 | on: { 11 | pet: { 12 | target: 'pet', 13 | cond: isDog 14 | } 15 | } 16 | }, 17 | pet: { 18 | always: [ 19 | { 20 | target: 'goodBoy' 21 | } 22 | ] 23 | }, 24 | goodBoy: { 25 | type: 'final' 26 | } 27 | } 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /test/snapshots/spawn_ext/expected.js: -------------------------------------------------------------------------------- 1 | import { createMachine, assign, spawn } from 'xstate'; 2 | 3 | export default function({ context = {}, services } = {}) { 4 | return createMachine({ 5 | context, 6 | states: { 7 | init: { 8 | always: [ 9 | { 10 | target: 'idle', 11 | actions: [ 12 | 'spawnExternal' 13 | ] 14 | } 15 | ] 16 | }, 17 | idle: { 18 | 19 | } 20 | } 21 | }, { 22 | actions: { 23 | spawnExternal: assign({ 24 | mac: () => spawn(services.external, 'external') 25 | }) 26 | } 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /test/snapshots/inline_assign/expected.js: -------------------------------------------------------------------------------- 1 | import { createMachine, assign } from 'xstate'; 2 | import { pet } from './util'; 3 | 4 | export default function({ context = {} } = {}) { 5 | return createMachine({ 6 | initial: 'idle', 7 | context, 8 | states: { 9 | idle: { 10 | invoke: { 11 | src: pet, 12 | onDone: { 13 | target: 'goodBoy', 14 | actions: [ 15 | assign({ 16 | wilbur: (context, event) => event.data 17 | }) 18 | ] 19 | } 20 | } 21 | }, 22 | goodBoy: { 23 | type: 'final' 24 | } 25 | } 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /test/snapshots/transition_block/expected.js: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | import { check } from './stuff.js'; 3 | 4 | export default function({ context = {} } = {}) { 5 | return createMachine({ 6 | initial: 'start', 7 | context, 8 | states: { 9 | start: { 10 | on: { 11 | go: { 12 | target: 'end', 13 | cond: ['canGo', 'sureCanGo', 'AreWeReallySure'] 14 | } 15 | } 16 | }, 17 | end: { 18 | type: 'final' 19 | } 20 | } 21 | }, { 22 | guards: { 23 | canGo: check, 24 | sureCanGo: check, 25 | AreWeReallySure: check 26 | } 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /scripts/post_transform: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ver=$1 4 | mjs_file=build/liblucy-$ver.mjs 5 | out_node=dist/liblucy-$ver-node.mjs 6 | out_browser=dist/liblucy-$ver-browser.mjs 7 | 8 | gsed -i "s/IMPORT_META_URL/import.meta.url/g" $mjs_file 9 | gsed -i "s/require(['\"]path['\"])/path/g" $mjs_file 10 | gsed -i "s/require(['\"]fs['\"])/fs/g" $mjs_file 11 | gsed -i "s/__dirname/new URL(import.meta.url)/g" $mjs_file 12 | 13 | cp $mjs_file $out_browser 14 | 15 | # Create Node.js version 16 | node_tmp=$(mktemp) 17 | echo "import fs from 'fs';" >> $node_tmp 18 | echo "import path from 'path';" >> $node_tmp 19 | cat $mjs_file >> $node_tmp 20 | mv $node_tmp $out_node 21 | 22 | # Remove temp files 23 | # rm $mjs_file -------------------------------------------------------------------------------- /test/snapshots/delay/expected.js: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | import { calcLightDelay } from './util'; 3 | 4 | export default function({ context = {} } = {}) { 5 | return createMachine({ 6 | initial: 'green', 7 | context, 8 | states: { 9 | green: { 10 | after: { 11 | 1000: 'yellow' 12 | } 13 | }, 14 | yellow: { 15 | on: { 16 | go: 'green' 17 | }, 18 | after: { 19 | 500: 'red' 20 | } 21 | }, 22 | red: { 23 | after: { 24 | calcLightDelay: 'green' 25 | } 26 | } 27 | } 28 | }, { 29 | delays: { 30 | calcLightDelay: calcLightDelay 31 | } 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/lucy.yml: -------------------------------------------------------------------------------- 1 | name: Lucy CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | matrix: 12 | node-version: [14.x, 16.x] 13 | 14 | steps: 15 | - uses: actions/checkout@v1 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | - uses: mymindstorm/setup-emsdk@v7 21 | 22 | - name: Install colordiff 23 | run: sudo apt-get install colordiff 24 | 25 | - name: make test 26 | run: | 27 | make bin/lucyc 28 | make dist/liblucy-debug-node.mjs 29 | make dist/liblucy-release-node.mjs 30 | make test 31 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Releases 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | build: 10 | runs-on: ${{ matrix.os }} 11 | strategy: 12 | matrix: 13 | include: 14 | - os: ubuntu-latest 15 | name: linux 16 | - os: macos-latest 17 | name: mac 18 | steps: 19 | - uses: actions/checkout@v1 20 | - name: build 21 | run: | 22 | make bin/lucyc 23 | cd bin && tar -zcvf ../${{ matrix.name }}.tar.gz lucyc && cd - 24 | - uses: ncipollo/release-action@v1 25 | with: 26 | allowUpdates: true 27 | prerelease: false 28 | artifacts: ${{ matrix.name }}.tar.gz 29 | token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /test/snapshots/invoke/expected.js: -------------------------------------------------------------------------------- 1 | import { createMachine, assign } from 'xstate'; 2 | import { getUser, setUser } from './user.js'; 3 | 4 | export default function({ context = {} } = {}) { 5 | return createMachine({ 6 | context, 7 | states: { 8 | loading: { 9 | on: { 10 | again: 'loading' 11 | }, 12 | invoke: { 13 | src: getUser, 14 | onDone: { 15 | target: 'ready', 16 | actions: ['assignUser'] 17 | }, 18 | onError: 'error' 19 | } 20 | }, 21 | ready: { 22 | 23 | }, 24 | error: { 25 | 26 | } 27 | } 28 | }, { 29 | actions: { 30 | assignUser: assign({ 31 | user: setUser 32 | }) 33 | } 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /test/demos/editable-title/machine.lucy: -------------------------------------------------------------------------------- 1 | import { setTitle, resetTitle, title, titleIsValid } from './logic.js' 2 | 3 | action assignTitle = assign title setTitle 4 | action assignOldTitle = assign oldTitle title 5 | action resetTitle = assign title resetTitle 6 | 7 | initial state preview { 8 | edit => assignOldTitle => edit 9 | } 10 | 11 | state edit { 12 | done => preview 13 | 14 | machine internal { 15 | initial state idle { 16 | cancel => resetTitle => complete 17 | save => validate 18 | input => assignTitle => validate 19 | } 20 | 21 | state validate { 22 | => guard titleIsValid => idle 23 | => invalid 24 | } 25 | 26 | state invalid { 27 | input => assignTitle => idle 28 | } 29 | 30 | final state complete {} 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/snapshots/dts_assign/expected.d.ts: -------------------------------------------------------------------------------- 1 | import { PartialAssigner, StateMachine } from 'xstate'; 2 | 3 | type MachineEventNames = 'go'; 4 | 5 | type MachineKnownContextKeys = 'prop'; 6 | 7 | export interface CreateMachineOptions, TEvent extends { type: MachineEventNames }> { 8 | context: TContext, 9 | assigns: { 10 | val: PartialAssigner< 11 | TContext, 12 | TEvent extends Extract ? Extract : TEvent, 13 | 'prop' 14 | > 15 | } 16 | } 17 | 18 | export default function createMachine, TEvent extends { type: MachineEventNames } = any>(options: CreateMachineOptions): StateMachine; 19 | -------------------------------------------------------------------------------- /test/snapshots/invoke_machine/expected.js: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | export function createMinute({ context = {} } = {}) { 4 | return createMachine({ 5 | initial: 'active', 6 | context, 7 | states: { 8 | active: { 9 | on: { 10 | timer: 'finished' 11 | } 12 | }, 13 | finished: { 14 | type: 'final' 15 | } 16 | } 17 | }); 18 | } 19 | 20 | export function createParent({ context = {} } = {}) { 21 | return createMachine({ 22 | initial: 'pending', 23 | context, 24 | states: { 25 | pending: { 26 | invoke: { 27 | src: createMinute, 28 | onDone: 'timesUp' 29 | } 30 | }, 31 | timesUp: { 32 | type: 'final' 33 | } 34 | } 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /test/snapshots/machine/expected.js: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | export function createLight({ context = {} } = {}) { 4 | return createMachine({ 5 | initial: 'green', 6 | context, 7 | states: { 8 | green: { 9 | on: { 10 | timer: 'yellow' 11 | } 12 | }, 13 | yellow: { 14 | on: { 15 | timer: 'red' 16 | } 17 | }, 18 | red: { 19 | on: { 20 | timer: 'green' 21 | } 22 | } 23 | } 24 | }); 25 | } 26 | 27 | export function createTwo({ context = {} } = {}) { 28 | return createMachine({ 29 | context, 30 | states: { 31 | start: { 32 | on: { 33 | next: 'end' 34 | } 35 | }, 36 | end: { 37 | type: 'final' 38 | } 39 | } 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /test/browser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Demo page 4 | 15 | 16 |
17 | 26 |

27 | 
28 | 29 | -------------------------------------------------------------------------------- /src/pre_js.js: -------------------------------------------------------------------------------- 1 | const isNodeJS = typeof process === 'object' && Object.prototype.toString.call(process) === '[object process]'; 2 | if(isNodeJS) { 3 | Module.instantiateWasm = function(imports, receiveInstance) { 4 | var bytes = getBinary(wasmBinaryFile); 5 | var mod = new WebAssembly.Module(bytes); 6 | var instance = new WebAssembly.Instance(mod, imports); 7 | 8 | receiveInstance(instance, mod); 9 | return instance.exports; 10 | }; 11 | } 12 | 13 | Module.locateFile = function(pth) { 14 | let url; 15 | switch(pth) { 16 | case 'liblucy-debug.wasm': { 17 | url = new URL('./liblucy-debug.wasm', IMPORT_META_URL); 18 | break; 19 | } 20 | case 'liblucy-release.wasm': { 21 | url = new URL('./liblucy-release.wasm', IMPORT_META_URL); 22 | break; 23 | } 24 | } 25 | return url.toString().replace('file://', ''); 26 | }; -------------------------------------------------------------------------------- /test/demos/playground.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Test environment 4 | 5 | -------------------------------------------------------------------------------- /src/core/string_list.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | typedef struct string_list_node_t { 7 | char* value; 8 | struct string_list_node_t* next; 9 | } string_list_node_t; 10 | 11 | typedef struct string_list_t { 12 | string_list_node_t* head; 13 | string_list_node_t* tail; 14 | size_t length; 15 | } string_list_t; 16 | 17 | typedef struct string_list_iterator_t { 18 | string_list_node_t* node; 19 | size_t index; 20 | 21 | string_list_t* _list; 22 | } string_list_iterator_t; 23 | 24 | void string_list_init(string_list_t*); 25 | void string_list_destroy(string_list_t*); 26 | void string_list_append(string_list_t*, char*); 27 | string_list_iterator_t string_list_iterator(string_list_t*); 28 | bool string_list_next(string_list_iterator_t*); 29 | 30 | static inline bool string_list_empty(string_list_t* list) { 31 | return list->head == NULL; 32 | } -------------------------------------------------------------------------------- /test/snapshots/guards_and_actions/expected.js: -------------------------------------------------------------------------------- 1 | import { createMachine, assign } from 'xstate'; 2 | import { incrementCount, decrementCount, lessThanTen, greaterThanZero } from './actions.js'; 3 | 4 | export default function({ context = {} } = {}) { 5 | return createMachine({ 6 | initial: 'active', 7 | context, 8 | states: { 9 | active: { 10 | on: { 11 | inc: { 12 | cond: 'isNotMax', 13 | actions: ['increment'] 14 | }, 15 | dec: { 16 | cond: 'isNotMin', 17 | actions: ['decrement'] 18 | } 19 | } 20 | } 21 | } 22 | }, { 23 | guards: { 24 | isNotMax: lessThanTen, 25 | isNotMin: greaterThanZero 26 | }, 27 | actions: { 28 | increment: assign({ 29 | count: incrementCount 30 | }), 31 | decrement: assign({ 32 | count: decrementCount 33 | }) 34 | } 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /test/snapshots/nested_state/expected.js: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | 3 | export function createLight({ context = {} } = {}) { 4 | return createMachine({ 5 | initial: 'green', 6 | context, 7 | states: { 8 | green: { 9 | on: { 10 | timer: 'yellow' 11 | } 12 | }, 13 | yellow: { 14 | on: { 15 | timer: 'red' 16 | } 17 | }, 18 | red: { 19 | on: { 20 | timer: 'green' 21 | }, 22 | initial: 'walk', 23 | context, 24 | states: { 25 | walk: { 26 | on: { 27 | countdown: 'wait' 28 | } 29 | }, 30 | wait: { 31 | on: { 32 | countdown: 'stop' 33 | } 34 | }, 35 | stop: { 36 | type: 'final' 37 | } 38 | } 39 | }, 40 | another: { 41 | 42 | } 43 | } 44 | }); 45 | } 46 | -------------------------------------------------------------------------------- /src/core/xstate/import.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../js_builder.h" 3 | #include "../node.h" 4 | #include "core.h" 5 | 6 | void xs_enter_import(PrintState* state, JSBuilder* jsb, Node* node) { 7 | js_builder_add_str(jsb, "import "); 8 | 9 | ImportNode *import_node = (ImportNode*)node; 10 | Node* child = node->child; 11 | bool multiple = false; 12 | 13 | if(child == NULL) { 14 | printf("TODO add support for imports with no specifiers\n"); 15 | return; 16 | } 17 | 18 | js_builder_add_str(jsb, "{ "); 19 | 20 | while(child != NULL) { 21 | ImportSpecifier *specifier = (ImportSpecifier*)child; 22 | 23 | if(multiple) { 24 | js_builder_add_str(jsb, ", "); 25 | } 26 | 27 | js_builder_add_str(jsb, specifier->imported); 28 | // TODO support local 29 | 30 | child = child->next; 31 | multiple = true; 32 | } 33 | 34 | js_builder_add_str(jsb, " } from "); 35 | js_builder_add_str(jsb, import_node->from); 36 | js_builder_add_str(jsb, ";\n"); 37 | } -------------------------------------------------------------------------------- /src/core/keyword.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "dict.h" 4 | #include "keyword.h" 5 | 6 | static dict *keywords; 7 | 8 | bool is_keyword(char* key) { 9 | return dict_search(keywords, key) > 0; 10 | } 11 | 12 | unsigned short keyword_get(char* key) { 13 | return dict_search(keywords, key); 14 | } 15 | 16 | void keyword_init() { 17 | keywords = dict_create(); 18 | 19 | dict_insert(keywords, "use", KW_USE); 20 | dict_insert(keywords, "state", KW_STATE); 21 | dict_insert(keywords, "initial", KW_INITIAL); 22 | dict_insert(keywords, "final", KW_FINAL); 23 | dict_insert(keywords, "action", KW_ACTION); 24 | dict_insert(keywords, "guard", KW_GUARD); 25 | dict_insert(keywords, "assign", KW_ASSIGN); 26 | dict_insert(keywords, "invoke", KW_INVOKE); 27 | dict_insert(keywords, "machine", KW_MACHINE); 28 | dict_insert(keywords, "delay", KW_DELAY); 29 | dict_insert(keywords, "on", KW_ON); 30 | dict_insert(keywords, "spawn", KW_SPAWN); 31 | dict_insert(keywords, "send", KW_SEND); 32 | } -------------------------------------------------------------------------------- /test/snapshots/guards/expected.js: -------------------------------------------------------------------------------- 1 | import { createMachine } from 'xstate'; 2 | import { isValidCard, isInvalidCard, moneyWithdrawn } from './util'; 3 | 4 | export default function({ context = {} } = {}) { 5 | return createMachine({ 6 | initial: 'idle', 7 | context, 8 | states: { 9 | idle: { 10 | on: { 11 | next: [ 12 | { 13 | target: 'purchase', 14 | cond: isValidCard 15 | }, 16 | { 17 | target: 'error', 18 | cond: isInvalidCard 19 | } 20 | ] 21 | } 22 | }, 23 | purchase: { 24 | always: [ 25 | { 26 | target: 'purchased', 27 | cond: 'isMoneyWithdrawn' 28 | } 29 | ] 30 | }, 31 | purchased: { 32 | type: 'final' 33 | }, 34 | error: { 35 | type: 'final' 36 | } 37 | } 38 | }, { 39 | guards: { 40 | isMoneyWithdrawn: moneyWithdrawn 41 | } 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lucy/liblucy", 3 | "version": "0.5.8", 4 | "description": "The Lucy compiler project.", 5 | "exports": { 6 | ".": { 7 | "node": { 8 | "development": "./main-node-dev.mjs", 9 | "production": "./main-node-prod.mjs", 10 | "import": "./main-node-prod.mjs" 11 | }, 12 | "browser": { 13 | "development": "./main-browser-dev.js", 14 | "production": "./main-browser-prod.js", 15 | "import": "./main-browser-prod.js" 16 | } 17 | } 18 | }, 19 | "types": "./liblucy.d.ts", 20 | "files": [ 21 | "dist", 22 | "liblucy.mjs", 23 | "liblucy.d.ts", 24 | "main-node-dev.mjs", 25 | "main-node-prod.mjs", 26 | "main-browser-dev.js", 27 | "main-browser-prod.js" 28 | ], 29 | "scripts": { 30 | "test": "make test" 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "git+https://github.com/matthewp/liblucy.git" 35 | }, 36 | "keywords": [ 37 | "Lucy", 38 | "compiler", 39 | "wasm" 40 | ], 41 | "author": "Matthew Phillips ", 42 | "license": "BSD-2-Clause", 43 | "bugs": { 44 | "url": "https://github.com/matthewp/liblucy/issues" 45 | }, 46 | "homepage": "https://github.com/matthewp/liblucy#readme" 47 | } 48 | -------------------------------------------------------------------------------- /src/core/parser/send.c: -------------------------------------------------------------------------------- 1 | #include "../node.h" 2 | #include "../state.h" 3 | #include "core.h" 4 | 5 | int parser_consume_inline_send_args(State* state, void* expr, int _token, char* arg, int argi) { 6 | SendExpression* send_expression = (SendExpression*)expr; 7 | 8 | if(argi == 0) { 9 | send_expression->actor = arg; 10 | } else { 11 | send_expression->event = arg; 12 | } 13 | 14 | return 0; 15 | } 16 | 17 | int parser_consume_inline_send(State* state, Node* node) { 18 | int err = 0; 19 | 20 | SendExpression* send_expression = node_create_sendexpression(); 21 | _check(consume_call_expression(state, "send", send_expression, &parser_consume_inline_send_args)); 22 | 23 | switch(node->type) { 24 | case NODE_TRANSITION_TYPE: { 25 | TransitionNode* transition_node = (TransitionNode*)node; 26 | TransitionAction* action = node_transition_add_action(transition_node, NULL); 27 | action->expression = (Expression*)send_expression; 28 | break; 29 | } 30 | case NODE_LOCAL_TYPE: { 31 | LocalNode* local_node = (LocalNode*)node; 32 | TransitionAction* action = create_transition_action(); 33 | action->expression = (Expression*)send_expression; 34 | node_local_add_action(local_node, action); 35 | break; 36 | } 37 | } 38 | 39 | program_add_flag(state->program, PROGRAM_USES_SEND); 40 | 41 | return err; 42 | } -------------------------------------------------------------------------------- /src/core/parser/spawn.c: -------------------------------------------------------------------------------- 1 | #include "../node.h" 2 | #include "../state.h" 3 | #include "core.h" 4 | #include "token.h" 5 | 6 | static int consume_inline_spawn_args(State* state, void* expr, int token, char* arg, int _argi) { 7 | SpawnExpression* spawn_expression = (SpawnExpression*)expr; 8 | switch(token) { 9 | case TOKEN_IDENTIFIER: { 10 | IdentifierExpression* identifier_expression = node_create_identifierexpression(); 11 | identifier_expression->name = arg; 12 | spawn_expression->target = (Expression*)identifier_expression; 13 | break; 14 | } 15 | case TOKEN_SYMBOL: { 16 | SymbolExpression* symbol_expression = node_create_symbolexpression(); 17 | symbol_expression->name = arg; 18 | spawn_expression->target = (Expression*)symbol_expression; 19 | break; 20 | } 21 | } 22 | return 0; 23 | } 24 | 25 | int parser_consume_inline_spawn(State* state, AssignExpression* assign_expression) { 26 | int err = 0; 27 | 28 | SpawnExpression* spawn_expression = node_create_spawnexpression(); 29 | _check(consume_call_expression(state, "spawn", spawn_expression, &consume_inline_spawn_args)); 30 | assign_expression->value = (Expression*)spawn_expression; 31 | program_add_flag(state->program, PROGRAM_USES_SPAWN); 32 | 33 | if(spawn_expression->target->type == EXPRESSION_SYMBOL) { 34 | state->current_machine_node->flags |= MACHINE_USES_SERVICE; 35 | } 36 | 37 | return err; 38 | } -------------------------------------------------------------------------------- /src/core/js_builder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "str_builder.h" 5 | 6 | typedef struct JSBuilder { 7 | char* indent; 8 | char* indent_str; 9 | size_t indent_len; 10 | 11 | str_builder_t* sb; 12 | str_builder_t* ib; 13 | } JSBuilder; 14 | 15 | JSBuilder* js_builder_create(); 16 | void js_builder_destroy(JSBuilder*); 17 | 18 | void js_builder_add_char(JSBuilder*, char); 19 | void js_builder_add_str(JSBuilder*, char*); 20 | void js_builder_copy_str(JSBuilder*, char*, size_t, size_t); 21 | void js_builder_safe_key(JSBuilder*, char*); 22 | void js_builder_add_string(JSBuilder*, char*); 23 | void js_builder_copy_string(JSBuilder*, char*, size_t, size_t); 24 | 25 | void js_builder_add_indent(JSBuilder*); 26 | void js_builder_increase_indent(JSBuilder*); 27 | void js_builder_decrease_indent(JSBuilder*); 28 | void js_builder_start_object(JSBuilder*); 29 | void js_builder_end_object(JSBuilder*); 30 | void js_builder_start_prop(JSBuilder*, char*); 31 | void js_builder_start_prop_no_copy(JSBuilder*, char*, size_t, size_t); 32 | void js_builder_shorthand_prop(JSBuilder*, char*); 33 | void js_builder_start_call(JSBuilder*, char*); 34 | void js_builder_end_call(JSBuilder*); 35 | void js_builder_start_array(JSBuilder*, bool); 36 | void js_builder_end_array(JSBuilder*, bool); 37 | void js_builder_add_export(JSBuilder*); 38 | void js_builder_add_const(JSBuilder*, char*); 39 | void js_builder_add_arg(JSBuilder*, char*); 40 | 41 | char* js_builder_dump(JSBuilder*); -------------------------------------------------------------------------------- /src/core/xstate/core.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../js_builder.h" 4 | #include "../node.h" 5 | #include "../program.h" 6 | #include "../set.h" 7 | #include "ts_printer.h" 8 | 9 | // API flags 10 | #define XS_FLAG_USE_REMOTE 1 << 0 11 | #define XS_FLAG_DTS 1 << 1 12 | 13 | // Machine implementation flags 14 | #define XS_HAS_STATE_PROP 1 << 0 15 | 16 | typedef struct Ref { 17 | char* key; 18 | struct Ref* next; 19 | Expression* value; 20 | } Ref; 21 | 22 | typedef struct PrintState { 23 | int flags; 24 | char* source; 25 | Program* program; 26 | Ref* guard; 27 | SimpleSet* guard_names; 28 | Ref* action; 29 | SimpleSet* action_names; 30 | Ref* delay; 31 | SimpleSet* delay_names; 32 | Ref* service; 33 | SimpleSet* service_names; 34 | SimpleSet* events; 35 | ts_printer_t* tsprinter; 36 | char* cur_event_name; 37 | size_t cur_state_start; 38 | size_t cur_state_end; 39 | bool in_entry; 40 | } PrintState; 41 | 42 | void xs_destroy_state_refs(PrintState*); 43 | void xs_start_assign_call(JSBuilder*, AssignExpression*); 44 | void xs_end_assign_call(JSBuilder*); 45 | void xs_add_spawn_call(PrintState*, JSBuilder*, SpawnExpression*); 46 | void xs_add_send_call(JSBuilder*, SendExpression*); 47 | bool xs_find_and_add_top_level_machine_name(PrintState*, JSBuilder*, char*); 48 | void xs_add_action_ref(PrintState*, char*, Expression*); 49 | void xs_add_guard_ref(PrintState*, char*, Expression*); 50 | void xs_add_delay_ref(PrintState*, char*, Expression*); 51 | void xs_add_service_ref(PrintState*, char*, Expression*); -------------------------------------------------------------------------------- /src/core/state.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "node.h" 4 | #include "set.h" 5 | #include "program.h" 6 | 7 | #define MODIFIER_NONE 0 8 | #define MODIFIER_TYPE_INITIAL 1 9 | #define MODIFIER_TYPE_FINAL 2 10 | 11 | typedef struct State { 12 | char* source; 13 | char* filename; 14 | size_t source_len; 15 | size_t index; 16 | size_t line; 17 | size_t column; 18 | bool started; 19 | 20 | size_t modifier; 21 | size_t word_start; 22 | size_t word_end; 23 | char* word; 24 | size_t word_len; 25 | size_t token_len; 26 | 27 | SimpleSet* guards; 28 | SimpleSet* actions; 29 | 30 | Program* program; 31 | Node* node; 32 | Node* parent_node; 33 | MachineNode* current_machine_node; 34 | } State; 35 | 36 | typedef struct pos_t { 37 | int line; 38 | int column; 39 | } pos_t; 40 | 41 | int state_inbounds(State*); 42 | char state_char(State*); 43 | char state_peek(State*); 44 | char state_next(State*); 45 | void state_rewind(State*, int); 46 | void state_find_position(State*, pos_t*, int); 47 | 48 | State* state_new_state(char*, char*); 49 | void state_advance_line(State*); 50 | void state_advance_column(State*); 51 | void state_set_word(State*, char*, size_t, size_t); 52 | char* state_take_word(State*); 53 | void state_reset_word(State*); 54 | void state_node_set(State*, Node*); 55 | void state_node_up(State*); 56 | void state_node_start_pos(State*, Node*, unsigned short); 57 | 58 | void state_add_guard(State*, char*); 59 | void state_add_action(State*, char*); 60 | bool state_has_guard(State*, char*); 61 | bool state_has_action(State*, char*); -------------------------------------------------------------------------------- /src/core/string_list.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "string_list.h" 5 | 6 | void string_list_init(string_list_t* list) { 7 | list->head = NULL; 8 | list->tail = NULL; 9 | list->length = 0; 10 | } 11 | 12 | void string_list_destroy(string_list_t* list) { 13 | string_list_node_t* node = list->head; 14 | while(node != NULL) { 15 | string_list_node_t* next = node->next; 16 | if(node->value != NULL) { 17 | free(node->value); 18 | } 19 | free(node); 20 | node = next; 21 | } 22 | } 23 | 24 | void string_list_append(string_list_t* list, char* value) { 25 | string_list_node_t *node = malloc(sizeof(*node)); 26 | node->next = NULL; 27 | node->value = strdup(value); 28 | 29 | if(list->tail != NULL) { 30 | list->tail->next = node; 31 | list->tail = node; 32 | } else { 33 | list->head = node; 34 | list->tail = node; 35 | } 36 | list->length++; 37 | } 38 | 39 | string_list_iterator_t string_list_iterator(string_list_t* list) { 40 | string_list_iterator_t iterator; 41 | iterator._list = list; 42 | iterator.index = 0; 43 | iterator.node = NULL; 44 | return iterator; 45 | } 46 | 47 | bool string_list_next(string_list_iterator_t* iterator) { 48 | if(iterator->node == NULL) { 49 | iterator->node = iterator->_list->head; 50 | return true; 51 | } else { 52 | string_list_node_t* node = iterator->node->next; 53 | if(node == NULL) { 54 | return false; 55 | } 56 | iterator->node = node; 57 | iterator->index++; 58 | return true; 59 | } 60 | } -------------------------------------------------------------------------------- /test/snapshots/actor/expected.js: -------------------------------------------------------------------------------- 1 | import { createMachine, assign, send, spawn } from 'xstate'; 2 | 3 | export function createOther({ context = {} } = {}) { 4 | return createMachine({ 5 | context, 6 | states: { 7 | only: { 8 | on: { 9 | run: 'only' 10 | } 11 | } 12 | } 13 | }); 14 | } 15 | export default function({ context = {} } = {}) { 16 | return createMachine({ 17 | context, 18 | states: { 19 | idle: { 20 | on: { 21 | event: { 22 | target: 'idle', 23 | actions: [ 24 | assign({ 25 | first: () => spawn(createOther, 'other') 26 | }) 27 | ] 28 | }, 29 | another: { 30 | target: 'idle', 31 | actions: ['makeThing'] 32 | } 33 | } 34 | }, 35 | end: { 36 | on: { 37 | click: { 38 | target: 'end', 39 | actions: [ 40 | send((ctx, ev) => ({ type: 'run', data: ev.data }), { 41 | to: (context) => context.first 42 | }) 43 | ] 44 | }, 45 | dblclick: { 46 | target: 'end', 47 | actions: ['sendThing'] 48 | } 49 | } 50 | } 51 | } 52 | }, { 53 | actions: { 54 | makeThing: assign({ 55 | second: () => spawn(createOther, 'other') 56 | }), 57 | sendThing: send((ctx, ev) => ({ type: 'run', data: ev.data }), { 58 | to: (context) => context.second 59 | }) 60 | } 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /test/snapshots/symbols/expected.js: -------------------------------------------------------------------------------- 1 | import { createMachine, assign, spawn } from 'xstate'; 2 | 3 | export default function({ actions, assigns, context = {}, delays, guards, services } = {}) { 4 | return createMachine({ 5 | initial: 'idle', 6 | context, 7 | states: { 8 | idle: { 9 | on: { 10 | next: { 11 | target: 'loading', 12 | cond: ['isValid', 'check'], 13 | actions: ['log', 'namer'] 14 | } 15 | } 16 | }, 17 | loading: { 18 | entry: ['updateUI', 'log', 'incrementLoads'], 19 | invoke: { 20 | src: 'loadUsers', 21 | onDone: 'loaded' 22 | } 23 | }, 24 | loaded: { 25 | exit: ['log'], 26 | after: { 27 | wait: { 28 | target: 'homescreen', 29 | actions: ['logger'] 30 | } 31 | } 32 | }, 33 | homescreen: { 34 | entry: [ 35 | 'spawnTodoMachine' 36 | ] 37 | } 38 | } 39 | }, { 40 | guards: { 41 | isValid: guards.checkValid, 42 | check: guards.check 43 | }, 44 | actions: { 45 | logger: actions.doLog, 46 | log: actions.log, 47 | namer: assign({ 48 | name: assigns.namer 49 | }), 50 | updateUI: actions.updateUI, 51 | incrementLoads: assign({ 52 | count: assigns.incrementLoads 53 | }), 54 | spawnTodoMachine: assign({ 55 | todo: () => spawn(services.todoMachine, 'todoMachine') 56 | }) 57 | }, 58 | delays: { 59 | wait: delays.wait 60 | }, 61 | services: { 62 | loadUsers: services.loadUsers 63 | } 64 | }); 65 | } 66 | -------------------------------------------------------------------------------- /test/snapshots/dts_symbols/expected.d.ts: -------------------------------------------------------------------------------- 1 | import { Action, DelayConfig, ConditionPredicate, InvokeCreator, PartialAssigner, StateMachine } from 'xstate'; 2 | 3 | type MachineEventNames = 'next' | 'done'; 4 | 5 | type MachineKnownContextKeys = 'name' | 'count' | 'todo'; 6 | 7 | export interface CreateMachineOptions, TEvent extends { type: MachineEventNames }> { 8 | context: TContext, 9 | actions: { 10 | log: Action< 11 | TContext, 12 | TEvent extends Extract ? Extract : TEvent 13 | >, 14 | updateUI: Action< 15 | TContext, 16 | TEvent extends Extract ? Extract : TEvent 17 | > 18 | }, 19 | assigns: { 20 | incrementLoads: PartialAssigner< 21 | TContext, 22 | TEvent, 23 | 'count' 24 | >, 25 | namer: PartialAssigner< 26 | TContext, 27 | TEvent extends Extract ? Extract : TEvent, 28 | 'name' 29 | > 30 | }, 31 | delays: { 32 | wait: DelayConfig 33 | }, 34 | guards: { 35 | check: ConditionPredicate< 36 | TContext, 37 | TEvent extends Extract ? Extract : TEvent 38 | > 39 | }, 40 | services: { 41 | loadUsers: InvokeCreator, 42 | todoMachine: StateMachine 43 | } 44 | } 45 | 46 | export default function createMachine, TEvent extends { type: MachineEventNames } = any>(options: CreateMachineOptions): StateMachine; 47 | -------------------------------------------------------------------------------- /src/core/xstate/invoke.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../js_builder.h" 3 | #include "../node.h" 4 | #include "core.h" 5 | #include "transition.h" 6 | 7 | void xs_compile_invoke(PrintState* state, JSBuilder* jsb, InvokeNode* invoke_node) { 8 | js_builder_start_prop(jsb, "invoke"); 9 | js_builder_start_object(jsb); 10 | 11 | js_builder_start_prop(jsb, "src"); 12 | 13 | InvokeExpression* invoke_expression = (InvokeExpression*)invoke_node->expr; 14 | switch(invoke_expression->ref->type) { 15 | case EXPRESSION_IDENTIFIER: { 16 | IdentifierExpression* identifier_expression = (IdentifierExpression*)invoke_expression->ref; 17 | 18 | // Looking for a machine matching this name. 19 | if(!xs_find_and_add_top_level_machine_name(state, jsb, identifier_expression->name)) { 20 | // No in-scope machine found, so just add the identifier directly. 21 | js_builder_add_str(jsb, identifier_expression->name); 22 | } 23 | break; 24 | } 25 | case EXPRESSION_SYMBOL: { 26 | SymbolExpression* symbol_expression = (SymbolExpression*)invoke_expression->ref; 27 | js_builder_add_string(jsb, symbol_expression->name); 28 | xs_add_service_ref(state, symbol_expression->name, (Expression*)invoke_expression); 29 | 30 | if(state->flags & XS_FLAG_DTS) { 31 | ts_printer_add_invoke(state->tsprinter, symbol_expression->name); 32 | } 33 | break; 34 | } 35 | } 36 | 37 | if(invoke_node->event_transition != NULL) { 38 | TransitionNode* transition_node = invoke_node->event_transition; 39 | do { 40 | xs_compile_event_transition(state, jsb, transition_node); 41 | transition_node = transition_node->next; 42 | } while(transition_node != NULL); 43 | } 44 | 45 | js_builder_end_object(jsb); 46 | } 47 | 48 | -------------------------------------------------------------------------------- /scripts/lucyc.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { promises as fsPromises } from 'fs'; 3 | const { readFile, writeFile } = fsPromises; 4 | 5 | let index = 2; 6 | let [,, filename] = process.argv; 7 | const env = process.env.NODE_ENV || 'development'; 8 | 9 | let outFile = null; 10 | let print = 'js'; 11 | const options = { 12 | useRemote: false, 13 | dts: false 14 | }; 15 | 16 | while(filename && filename.startsWith('--')) { 17 | switch(filename) { 18 | case '--remote-imports': { 19 | options.useRemote = true; 20 | break; 21 | } 22 | case '--out-file': { 23 | outFile = process.argv[++index]; 24 | break; 25 | } 26 | case '--experimental-dts': { 27 | options.dts = true; 28 | break; 29 | } 30 | case '--print': { 31 | print = process.argv[++index]; 32 | break; 33 | } 34 | } 35 | filename = process.argv[++index]; 36 | } 37 | 38 | if(!filename) { 39 | console.error('A filename is required') 40 | process.exit(1); 41 | } 42 | 43 | async function run() { 44 | const rel = env === 'production' ? 'prod' : 'dev'; 45 | const [ 46 | contents, 47 | { compileXstate, ready } 48 | ] = await Promise.all([ 49 | readFile(filename, 'utf-8'), 50 | import(`../main-node-${rel}.mjs`) 51 | ]); 52 | await ready; 53 | 54 | try { 55 | const result = compileXstate(contents, filename, options); 56 | 57 | if(outFile) { 58 | await writeFile(outFile, result.js, 'utf-8'); 59 | } else { 60 | if(options.dts && print === 'dts') { 61 | process.stdout.write(result.dts); 62 | } else { 63 | process.stdout.write(result.js); 64 | } 65 | 66 | process.stdout.write("\n"); 67 | } 68 | } catch { 69 | process.stderr.write("Compilation failed!\n"); 70 | process.exit(1); 71 | } 72 | } 73 | 74 | run(); -------------------------------------------------------------------------------- /scripts/test_snapshots: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | LUCYC="${LUCYC:-bin/lucyc}" 4 | NODE_ENV="${NODE_ENV:-development}" 5 | ret=0 6 | upd=0 7 | 8 | red='\033[0;31m' 9 | nc='\033[0m' # No Color 10 | 11 | usage() { 12 | program_name=$(basename $0) 13 | bold=$(tput bold) 14 | normal=$(tput sgr0) 15 | 16 | cat <> $tmp 2>&1 51 | 52 | if [ "$upd" -eq 1 ]; then 53 | mv $tmp $output 54 | return 55 | fi 56 | 57 | if [ ! -f $output ]; then 58 | mv $tmp $output 59 | return 60 | fi 61 | 62 | d=$(diff $output $tmp | colordiff) 63 | 64 | if [ ${#d} -ge 1 ]; then 65 | echo -e "${red}FAILED${nc} - $input" 66 | echo "" 67 | echo "$d" 68 | 69 | ret=1 70 | fi 71 | } 72 | 73 | while getopts "hu" opt; do 74 | case ${opt} in 75 | h ) 76 | usage; 77 | ;; 78 | u ) 79 | upd=1 80 | ;; 81 | esac 82 | done 83 | shift $((OPTIND -1)) 84 | 85 | folder=$@ 86 | 87 | if [ -z "$folder" ]; then 88 | for d in test/snapshots/*/ ; do 89 | run_test $d 90 | done 91 | else 92 | run_test $folder 93 | fi 94 | 95 | exit $ret -------------------------------------------------------------------------------- /src/core/parser/parser.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../error.h" 3 | #include "../keyword.h" 4 | #include "../local.h" 5 | #include "../program.h" 6 | #include "../state.h" 7 | #include "../timeframe.h" 8 | #include "core.h" 9 | #include "machine.h" 10 | #include "parser.h" 11 | #include "token.h" 12 | #include "use.h" 13 | 14 | static int consume_program(State* state) { 15 | int err = 0; 16 | Program* program = state->program; 17 | 18 | int token; 19 | 20 | while(true) { 21 | token = consume_token(state); 22 | 23 | switch(token) { 24 | case TOKEN_EOL: continue; 25 | case TOKEN_EOF: goto end; 26 | case TOKEN_IDENTIFIER: { 27 | char* identifier = state->word; 28 | 29 | if(!is_keyword(identifier)) { 30 | error_msg_with_code_block(state, state->node, "Unknown top-level identifier."); 31 | return 2; 32 | } 33 | 34 | unsigned short key = keyword_get(identifier); 35 | switch(key) { 36 | case KW_USE: { 37 | _check(parser_consume_use(state)); 38 | break; 39 | } 40 | case KW_MACHINE: { 41 | _check(parser_consume_machine(state)); 42 | break; 43 | } 44 | default: { 45 | // Top-level machine 46 | _check(parser_consume_implicit_machine(state, token)); 47 | break; 48 | } 49 | } 50 | 51 | break; 52 | } 53 | } 54 | } 55 | 56 | end: { 57 | return err; 58 | } 59 | } 60 | 61 | ParseResult* parse(char* source, char* filename) { 62 | int err = 0; 63 | Program* program = new_program(); 64 | State* state = state_new_state(source, filename); 65 | state->program = program; 66 | 67 | err = consume_program(state); 68 | 69 | ParseResult *result = malloc(sizeof(*result)); 70 | result->success = err == 0; 71 | result->program = program; 72 | 73 | return result; 74 | } 75 | 76 | void parser_init() { 77 | keyword_init(); 78 | local_init(); 79 | timeframe_init(); 80 | } -------------------------------------------------------------------------------- /src/core/identifier.c: -------------------------------------------------------------------------------- 1 | #include // can remove 2 | #include 3 | #include "set.h" 4 | 5 | SimpleSet *valid_chars; 6 | char str[2] = {0}; 7 | 8 | // This is silly, use Set instead 9 | int is_valid_identifier_char(char c) { 10 | str[0] = c; 11 | return set_contains(valid_chars, str) == SET_TRUE; 12 | } 13 | 14 | void identifier_init() { 15 | valid_chars = malloc(sizeof *valid_chars); 16 | set_init(valid_chars); 17 | set_add(valid_chars, "a"); 18 | set_add(valid_chars, "b"); 19 | set_add(valid_chars, "c"); 20 | set_add(valid_chars, "d"); 21 | set_add(valid_chars, "e"); 22 | set_add(valid_chars, "f"); 23 | set_add(valid_chars, "g"); 24 | set_add(valid_chars, "h"); 25 | set_add(valid_chars, "i"); 26 | set_add(valid_chars, "j"); 27 | set_add(valid_chars, "k"); 28 | set_add(valid_chars, "l"); 29 | set_add(valid_chars, "m"); 30 | set_add(valid_chars, "n"); 31 | set_add(valid_chars, "o"); 32 | set_add(valid_chars, "p"); 33 | set_add(valid_chars, "q"); 34 | set_add(valid_chars, "r"); 35 | set_add(valid_chars, "s"); 36 | set_add(valid_chars, "t"); 37 | set_add(valid_chars, "u"); 38 | set_add(valid_chars, "v"); 39 | set_add(valid_chars, "w"); 40 | set_add(valid_chars, "x"); 41 | set_add(valid_chars, "y"); 42 | set_add(valid_chars, "z"); 43 | set_add(valid_chars, "A"); 44 | set_add(valid_chars, "B"); 45 | set_add(valid_chars, "C"); 46 | set_add(valid_chars, "D"); 47 | set_add(valid_chars, "E"); 48 | set_add(valid_chars, "F"); 49 | set_add(valid_chars, "G"); 50 | set_add(valid_chars, "H"); 51 | set_add(valid_chars, "I"); 52 | set_add(valid_chars, "J"); 53 | set_add(valid_chars, "K"); 54 | set_add(valid_chars, "L"); 55 | set_add(valid_chars, "M"); 56 | set_add(valid_chars, "N"); 57 | set_add(valid_chars, "O"); 58 | set_add(valid_chars, "P"); 59 | set_add(valid_chars, "Q"); 60 | set_add(valid_chars, "R"); 61 | set_add(valid_chars, "S"); 62 | set_add(valid_chars, "T"); 63 | set_add(valid_chars, "U"); 64 | set_add(valid_chars, "V"); 65 | set_add(valid_chars, "W"); 66 | set_add(valid_chars, "X"); 67 | set_add(valid_chars, "Y"); 68 | set_add(valid_chars, "Z"); 69 | set_add(valid_chars, "_"); 70 | } -------------------------------------------------------------------------------- /src/core/xstate/ts_printer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../ht.h" 4 | #include "../js_builder.h" 5 | #include "../string_list.h" 6 | #include "../set.h" 7 | 8 | #define XS_TS_PROGRAM_USES_GUARD 1 << 0 9 | #define XS_TS_PROGRAM_USES_ACTION 1 << 1 10 | #define XS_TS_PROGRAM_USES_INVOKE 1 << 2 11 | #define XS_TS_PROGRAM_USES_DELAY 1 << 3 12 | #define XS_TS_PROGRAM_USES_ASSIGN 1 << 4 13 | 14 | #define XS_TS_MACHINE_DEFAULT 1 << 0 15 | 16 | typedef struct xs_executor_t { 17 | string_list_t* events; 18 | SimpleSet* events_s; 19 | } xs_executor_t; 20 | 21 | typedef struct xs_assign_executor_t { 22 | string_list_t* events; 23 | SimpleSet* events_s; 24 | char* data_prop; 25 | } xs_assign_executor_t; 26 | 27 | typedef struct ts_printer_t { 28 | int flags; 29 | char* source; 30 | 31 | int machine_flags; 32 | size_t machine_name_start; 33 | size_t machine_name_end; 34 | char* xstate_specifier; 35 | JSBuilder* buffer; 36 | str_builder_t* imp_sb; 37 | str_builder_t* event_names_sb; 38 | str_builder_t* data_names_sb; 39 | str_builder_t* machine_def_sb; 40 | 41 | ht* guards; 42 | ht* actions; 43 | ht* assigns; 44 | string_list_t* invokes; 45 | string_list_t* actors; 46 | string_list_t* delays; 47 | 48 | ht* state_entries; // Map> 49 | ht* entry_actions; // Map> 50 | } ts_printer_t; 51 | 52 | void ts_printer_init(ts_printer_t*); 53 | void ts_printer_destroy(ts_printer_t*); 54 | char* ts_printer_dump(ts_printer_t*); 55 | ts_printer_t* ts_printer_alloc(); 56 | 57 | void ts_printer_add_event(ts_printer_t*, char*); 58 | void ts_printer_add_data(ts_printer_t*, char*); 59 | void ts_printer_add_assign(ts_printer_t*, char*, char*, char*); 60 | void ts_printer_add_guard(ts_printer_t*, char*, char*); 61 | void ts_printer_add_action(ts_printer_t*, char*, char*); 62 | void ts_printer_add_invoke(ts_printer_t*, char*); 63 | void ts_printer_add_actor(ts_printer_t*, char*); 64 | void ts_printer_add_delay(ts_printer_t*, char*); 65 | void ts_printer_create_machine(ts_printer_t*); 66 | void ts_printer_add_machine_name(ts_printer_t*, size_t, size_t); 67 | void ts_printer_add_state_entry(ts_printer_t*, char*, char*); 68 | void ts_printer_add_entry_action(ts_printer_t*, char*, char*); 69 | void ts_printer_figure_out_entry_events(ts_printer_t*); -------------------------------------------------------------------------------- /src/core/parser/delay.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../error.h" 3 | #include "../node.h" 4 | #include "../state.h" 5 | #include "../timeframe.h" 6 | #include "core.h" 7 | #include "token.h" 8 | 9 | static int consume_inline_delay_args(State* state, void* expr, int token, char* arg, int _argi) { 10 | DelayExpression* delay_expression = (DelayExpression*)expr; 11 | 12 | int time = 0; 13 | Expression* ref = NULL; 14 | 15 | switch(token) { 16 | case TOKEN_INTEGER: { 17 | time = atoi(arg); 18 | free(arg); 19 | break; 20 | } 21 | case TOKEN_TIMEFRAME: { 22 | Timeframe tf = timeframe_parse(arg, state->word_len); 23 | free(arg); 24 | 25 | if(tf.error != NULL) { 26 | error_msg_with_code_block(state, NULL, tf.error); 27 | return 2; 28 | } 29 | 30 | time = tf.time; 31 | break; 32 | } 33 | case TOKEN_IDENTIFIER: { 34 | IdentifierExpression* identifier_expression = node_create_identifierexpression(); 35 | identifier_expression->name = arg; 36 | ref = (Expression*)identifier_expression; 37 | break; 38 | } 39 | case TOKEN_SYMBOL: { 40 | SymbolExpression* symbol_expression = node_create_symbolexpression(); 41 | symbol_expression->name = arg; 42 | ref = (Expression*)symbol_expression; 43 | break; 44 | } 45 | default: { 46 | error_msg_with_code_block(state, NULL, "Expected either an integer time (in milliseconds) or a timeframe such as 200ms."); 47 | return 2; 48 | } 49 | } 50 | 51 | delay_expression->time = time; 52 | delay_expression->ref = ref; 53 | 54 | return 0; 55 | } 56 | 57 | int parser_consume_inline_delay(State* state, TransitionNode* transition_node, StateNode* state_node) { 58 | int err = 0; 59 | 60 | transition_node->type = TRANSITION_DELAY_TYPE; 61 | DelayExpression* expression = node_create_delayexpression(); 62 | 63 | _check(consume_call_expression(state, "delay", expression, &consume_inline_delay_args)); 64 | 65 | node_transition_add_delay(transition_node, NULL, expression); 66 | if(expression-> ref != NULL && expression->ref->type == EXPRESSION_SYMBOL) { 67 | MachineNode* machine_node = find_closest_machine_node((Node*)state_node); 68 | machine_node->flags |= MACHINE_USES_DELAY; 69 | } 70 | 71 | return err; 72 | } -------------------------------------------------------------------------------- /liblucy.mjs: -------------------------------------------------------------------------------- 1 | //import createModule from './dist/liblucy-debug.mjs'; 2 | 3 | export default async function(createModule) { 4 | const moduleReady = createModule(); 5 | const Module = await moduleReady; 6 | 7 | const { 8 | stackAlloc, 9 | stackRestore, 10 | stackSave, 11 | stringToUTF8, 12 | UTF8ToString 13 | } = Module; 14 | 15 | const _compileXstate = Module._compile_xstate; 16 | const _xsGetJS = Module._xs_get_js; 17 | const _xsGetDTS = Module._xs_get_dts; 18 | const _xsCreate = Module._xs_create; 19 | const _xsInit = Module._xs_init; 20 | const _destroyXstateResult = Module._destroy_xstate_result; 21 | 22 | function stringToPtr(str) { 23 | var ret = 0; 24 | if (str !== null && str !== undefined && str !== 0) { // null string 25 | // at most 4 bytes per UTF-8 code point, +1 for the trailing '\0' 26 | var len = (str.length << 2) + 1; 27 | ret = stackAlloc(len); 28 | stringToUTF8(str, ret, len); 29 | } 30 | return ret; 31 | } 32 | 33 | /** 34 | * Compile Lucy source a module of XState machines. 35 | * @param source {String} the input Lucy source. 36 | * @param filename {String} the name of the Lucy file. 37 | * @param options {Object} 38 | * @returns {String} The compiled JavaScript module. 39 | */ 40 | function compileXstate(source, filename, options = { 41 | useRemote: false 42 | }) { 43 | if(!source || !filename) { 44 | throw new Error('Source and filename are both required.'); 45 | } 46 | 47 | let stack = stackSave(); 48 | let srcPtr = stringToPtr(source); 49 | let fnPtr = stringToPtr(filename); 50 | let resPtr = _xsCreate(); 51 | _xsInit(resPtr, options.useRemote, options.dts); 52 | _compileXstate(resPtr, srcPtr, fnPtr); 53 | stackRestore(stack); 54 | 55 | const HEAPU8 = Module.HEAPU8; 56 | let success = !!HEAPU8[resPtr]; 57 | 58 | if(success) { 59 | let out = {}; 60 | let jsPtr = _xsGetJS(resPtr); 61 | out.js = UTF8ToString(jsPtr); 62 | if(options.dts) { 63 | let dtsPtr = _xsGetDTS(resPtr); 64 | out.dts = UTF8ToString(dtsPtr); 65 | } 66 | _destroyXstateResult(resPtr); 67 | return out; 68 | } 69 | 70 | let err = new Error('Compiler error'); 71 | throw err; 72 | } 73 | 74 | return { 75 | compileXstate 76 | }; 77 | } -------------------------------------------------------------------------------- /test/demos/editable-title/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | A title 4 | 13 | 14 |
15 | 16 | -------------------------------------------------------------------------------- /src/core/parser/invoke.c: -------------------------------------------------------------------------------- 1 | #include "../error.h" 2 | #include "../node.h" 3 | #include "../state.h" 4 | #include "core.h" 5 | #include "token.h" 6 | #include "transition.h" 7 | 8 | static int consume_invoke_args(State* state, void* expr, int token, char* arg, int _argi) { 9 | InvokeExpression* invoke_expression = (InvokeExpression*)expr; 10 | 11 | if(token == TOKEN_IDENTIFIER) { 12 | IdentifierExpression* identifier_expression = node_create_identifierexpression(); 13 | identifier_expression->name = arg; 14 | invoke_expression->ref = (Expression*)identifier_expression; 15 | } else if(token == TOKEN_SYMBOL) { 16 | SymbolExpression* symbol_expression = node_create_symbolexpression(); 17 | symbol_expression->name = arg; 18 | invoke_expression->ref = (Expression*)symbol_expression; 19 | } else { 20 | error_msg_with_code_block_dec(state, state->token_len, "Unexpected argument to invoke()"); 21 | return 1; 22 | } 23 | 24 | return 0; 25 | } 26 | 27 | static inline void link_invoke(Node* state_node_node, InvokeNode* invoke_node) { 28 | StateNode* state_node = (StateNode*)state_node_node; 29 | state_node->invoke = invoke_node; 30 | } 31 | 32 | int parser_consume_invoke(State* state) { 33 | int err = 0; 34 | 35 | InvokeNode* invoke_node = node_create_invoke(); 36 | Node* node = (Node*)invoke_node; 37 | state_node_start_pos(state, node, 6); // "invoke" 38 | 39 | Node* parent_node = state->node; 40 | if(parent_node->type != NODE_STATE_TYPE) { 41 | error_msg_with_code_block(state, node, "Unexpected parent for invoke."); 42 | return 2; 43 | } 44 | 45 | state_node_set(state, node); 46 | link_invoke(parent_node, invoke_node); 47 | 48 | invoke_node->expr = node_create_invokeexpression(); 49 | _check(consume_call_expression(state, "invoke", invoke_node->expr, &consume_invoke_args)); 50 | if(invoke_node->expr->ref->type == EXPRESSION_SYMBOL) { 51 | MachineNode* machine_node = find_closest_machine_node(parent_node); 52 | machine_node->flags |= MACHINE_USES_SERVICE; 53 | } 54 | 55 | int token = consume_token(state); 56 | 57 | if(token != TOKEN_BEGIN_BLOCK) { 58 | error_unexpected_identifier(state, node); 59 | return 2; 60 | } 61 | 62 | while(true) { 63 | token = consume_token(state); 64 | 65 | switch(token) { 66 | case TOKEN_EOL: continue; 67 | case TOKEN_END_BLOCK: { 68 | goto end; 69 | } 70 | case TOKEN_IDENTIFIER: { 71 | _check(parser_consume_transition(state)); 72 | break; 73 | } 74 | default: { 75 | error_unexpected_identifier(state, node); 76 | err = 2; 77 | goto end; 78 | } 79 | } 80 | } 81 | 82 | end: { 83 | state_node_up(state); 84 | return err; 85 | } 86 | } -------------------------------------------------------------------------------- /src/core/timeframe.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "dict.h" 6 | #include "timeframe.h" 7 | #include "str_builder.h" 8 | 9 | #define TF_MS 0 10 | #define TF_S 1 11 | #define TF_M 2 12 | 13 | static dict* suffixes; 14 | 15 | bool is_integer(char c) { 16 | return c == '0' || c == '1' || c == '2' || c == '3' || c == '4' || c == '5' || 17 | c == '6' || c == '7' || c == '8' || c == '9'; 18 | } 19 | 20 | bool is_timeframe_char(char c) { 21 | return is_integer(c) || c == 'm' || c == 's'; 22 | } 23 | 24 | bool timeframe_is_suffix(char* key) { 25 | return dict_search(suffixes, key) > 0; 26 | } 27 | 28 | unsigned short timeframe_get(char* key) { 29 | return dict_search(suffixes, key); 30 | } 31 | 32 | Timeframe timeframe_parse(char* word, size_t word_len) { 33 | str_builder_t* int_sb = str_builder_create(); 34 | str_builder_t* tf_sb = str_builder_create(); 35 | size_t int_len = 0; 36 | size_t tf_len = 0; 37 | bool is_int = true; 38 | 39 | int i = 0; 40 | char c; 41 | while(i < word_len) { 42 | c = word[i]; 43 | if(is_integer(c)) { 44 | str_builder_add_char(int_sb, c); 45 | int_len++; 46 | } else { 47 | str_builder_add_char(tf_sb, c); 48 | tf_len++; 49 | is_int = false; 50 | } 51 | i++; 52 | } 53 | 54 | char* int_str = str_builder_dump(int_sb, &int_len); 55 | char* tf_str = str_builder_dump(tf_sb, &tf_len); 56 | 57 | int value = atoi(int_str); 58 | if(!is_int) { 59 | if(!timeframe_is_suffix(tf_str)) { 60 | Timeframe tf = { 61 | .is_integer = false, 62 | .time = 0, 63 | .error = "Unknown timeframe suffix" 64 | }; 65 | free(int_str); 66 | free(tf_str); 67 | str_builder_destroy(tf_sb); 68 | str_builder_destroy(int_sb); 69 | return tf; 70 | } 71 | 72 | unsigned short key = timeframe_get(tf_str); 73 | switch(key) { 74 | case TF_MS: { 75 | // 2ms = 2 76 | break; 77 | } 78 | case TF_S: { 79 | // 2s = 2000 80 | value = value * 1000; 81 | break; 82 | } 83 | case TF_M: { 84 | // 2m = 120000 85 | value = value * 60000; 86 | break; 87 | } 88 | } 89 | } 90 | 91 | free(int_str); 92 | free(tf_str); 93 | str_builder_destroy(tf_sb); 94 | str_builder_destroy(int_sb); 95 | 96 | Timeframe tf = { 97 | .is_integer = is_int, 98 | .time = value, 99 | .error = NULL 100 | }; 101 | 102 | return tf; 103 | } 104 | 105 | void timeframe_init() { 106 | suffixes = dict_create(); 107 | 108 | dict_insert(suffixes, "ms", TF_MS); 109 | dict_insert(suffixes, "s", TF_S); 110 | dict_insert(suffixes, "m", TF_M); 111 | } -------------------------------------------------------------------------------- /src/core/parser/assign.c: -------------------------------------------------------------------------------- 1 | #include "../keyword.h" 2 | #include "../node.h" 3 | #include "../state.h" 4 | #include "core.h" 5 | #include "spawn.h" 6 | #include "token.h" 7 | 8 | static inline void maybe_add_machine_uses_assign(AssignExpression* assign_expression, Node* parent_node) { 9 | Expression* value = assign_expression->value; 10 | if(value != NULL && value->type == EXPRESSION_SYMBOL) { 11 | MachineNode* machine_node = find_closest_machine_node(parent_node); 12 | machine_node->flags |= MACHINE_USES_ASSIGN; 13 | } 14 | } 15 | 16 | /* Public API */ 17 | int parser_consume_inline_assign_args(State* state, void* expr, int token, char* arg, int argi) { 18 | int err = 0; 19 | AssignExpression* assign_expression = (AssignExpression*)expr; 20 | 21 | if(argi == 0) { 22 | assign_expression->key = arg; 23 | } else { 24 | unsigned short key = keyword_get(arg); 25 | switch(key) { 26 | case KW_SPAWN: { 27 | _check(parser_consume_inline_spawn(state, assign_expression)); 28 | break; 29 | } 30 | default: { 31 | switch(token) { 32 | case TOKEN_IDENTIFIER: { 33 | IdentifierExpression* identifier_expression = node_create_identifierexpression(); 34 | identifier_expression->name = arg; 35 | assign_expression->value = (Expression*)identifier_expression; 36 | break; 37 | } 38 | case TOKEN_SYMBOL: { 39 | SymbolExpression* symbol_expression = node_create_symbolexpression(); 40 | symbol_expression->name = arg; 41 | assign_expression->value = (Expression*)symbol_expression; 42 | break; 43 | } 44 | } 45 | 46 | // Not a keyword 47 | break; 48 | } 49 | } 50 | } 51 | 52 | return err; 53 | } 54 | 55 | int parser_consume_inline_assign(State* state, Node* node) { 56 | int err = 0; 57 | AssignExpression* assign_expression = node_create_assignexpression(); 58 | 59 | _check(consume_call_expression(state, "assign", assign_expression, &parser_consume_inline_assign_args)); 60 | 61 | switch(node->type) { 62 | case NODE_TRANSITION_TYPE: { 63 | TransitionNode* transition_node = (TransitionNode*)node; 64 | TransitionAction* action = node_transition_add_action(transition_node, NULL); 65 | action->expression = (Expression*)assign_expression; 66 | break; 67 | } 68 | case NODE_LOCAL_TYPE: { 69 | LocalNode* local_node = (LocalNode*)node; 70 | TransitionAction* action = create_transition_action(); 71 | action->expression = (Expression*)assign_expression; 72 | node_local_add_action(local_node, action); 73 | break; 74 | } 75 | } 76 | 77 | program_add_flag(state->program, PROGRAM_USES_ASSIGN); 78 | maybe_add_machine_uses_assign(assign_expression, node); 79 | 80 | return err; 81 | } -------------------------------------------------------------------------------- /src/core/ht.h: -------------------------------------------------------------------------------- 1 | /** 2 | MIT License 3 | 4 | Copyright (c) 2021 Ben Hoyt 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | // Simple hash table implemented in C. 26 | 27 | #ifndef _HT_H 28 | #define _HT_H 29 | 30 | #include 31 | #include 32 | 33 | // Hash table structure: create with ht_create, free with ht_destroy. 34 | typedef struct ht ht; 35 | 36 | // Create hash table and return pointer to it, or NULL if out of memory. 37 | ht* ht_create(void); 38 | 39 | // Free memory allocated for hash table, including allocated keys. 40 | void ht_destroy(ht* table); 41 | 42 | // Get item with given key (NUL-terminated) from hash table. Return 43 | // value (which was set with ht_set), or NULL if key not found. 44 | void* ht_get(ht* table, const char* key); 45 | 46 | // Set item with given key (NUL-terminated) to value (which must not 47 | // be NULL). If not already present in table, key is copied to newly 48 | // allocated memory (keys are freed automatically when ht_destroy is 49 | // called). Return address of copied key, or NULL if out of memory. 50 | const char* ht_set(ht* table, const char* key, void* value); 51 | 52 | // Return number of items in hash table. 53 | size_t ht_length(ht* table); 54 | 55 | // Hash table iterator: create with ht_iterator, iterate with ht_next. 56 | typedef struct { 57 | const char* key; // current key 58 | void* value; // current value 59 | 60 | // Don't use these fields directly. 61 | ht* _table; // reference to hash table being iterated 62 | size_t _index; // current index into ht._entries 63 | } hti; 64 | 65 | // Return new hash table iterator (for use with ht_next). 66 | hti ht_iterator(ht* table); 67 | 68 | // Move iterator to next item in hash table, update iterator's key 69 | // and value to current item, and return true. If there are no more 70 | // items, return false. Don't call ht_set during iteration. 71 | bool ht_next(hti* it); 72 | 73 | #endif // _HT_H -------------------------------------------------------------------------------- /src/core/parser/guard.c: -------------------------------------------------------------------------------- 1 | #include "../error.h" 2 | #include "../node.h" 3 | #include "../state.h" 4 | #include "core.h" 5 | #include "token.h" 6 | 7 | static int consume_inline_guard_args(State* state, void* expr, int token, char* arg, int _argi) { 8 | GuardExpression* guard_expression = (GuardExpression*)expr; 9 | 10 | if(token == TOKEN_IDENTIFIER) { 11 | IdentifierExpression* ref = node_create_identifierexpression(); 12 | ref->name = arg; 13 | guard_expression->ref = (Expression*)ref; 14 | } else if(token == TOKEN_SYMBOL) { 15 | SymbolExpression* ref = node_create_symbolexpression(); 16 | ref->name = arg; 17 | guard_expression->ref = (Expression*)ref; 18 | } else { 19 | error_msg_with_code_block_dec(state, state->token_len, "Unexpected function argument to guard()"); 20 | return 1; 21 | } 22 | 23 | return 0; 24 | } 25 | 26 | int parser_consume_inline_guard(State* state, TransitionNode* transition_node) { 27 | int err = 0; 28 | GuardExpression* guard_expression = node_create_guardexpression(); 29 | 30 | _check(consume_call_expression(state, "guard", guard_expression, &consume_inline_guard_args)); 31 | 32 | TransitionGuard* guard = node_transition_add_guard(transition_node, NULL); 33 | guard->expression = guard_expression; 34 | 35 | if(guard->expression->ref->type == EXPRESSION_SYMBOL) { 36 | MachineNode* parent_machine_node = find_closest_machine_node((Node*)transition_node); 37 | parent_machine_node->flags |= MACHINE_USES_GUARD; 38 | } 39 | 40 | return err; 41 | } 42 | 43 | /* Public API */ 44 | int parser_consume_guard(State* state) { 45 | Assignment* assignment = node_create_assignment(ASSIGNMENT_GUARD); 46 | Node* node = (Node*)assignment; 47 | state_node_set(state, node); 48 | 49 | int token; 50 | 51 | token = consume_token(state); 52 | 53 | if(token != TOKEN_IDENTIFIER) { 54 | error_unexpected_identifier(state, node); 55 | return 2; 56 | } 57 | 58 | assignment->binding_name = state_take_word(state); 59 | 60 | token = consume_token(state); 61 | 62 | if(token != TOKEN_ASSIGNMENT) { 63 | error_msg_with_code_block(state, node, "Expected an identifier"); 64 | return 2; 65 | } 66 | 67 | token = consume_token(state); 68 | switch(token) { 69 | case TOKEN_IDENTIFIER: { 70 | IdentifierExpression *expression = node_create_identifierexpression(); 71 | expression->name = state_take_word(state); 72 | assignment->value = (Expression*)expression; 73 | break; 74 | } 75 | case TOKEN_SYMBOL: { 76 | SymbolExpression* expression = node_create_symbolexpression(); 77 | expression->name = state_take_word(state); 78 | assignment->value = (Expression*)expression; 79 | break; 80 | } 81 | default: { 82 | error_msg_with_code_block_dec(state, state->token_len, "Expected an identifier"); 83 | return 2; 84 | } 85 | } 86 | 87 | state_add_guard(state, assignment->binding_name); 88 | state_node_up(state); 89 | return 0; 90 | } 91 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | EMCC=emcc 2 | 3 | VERSION=$(shell cat package.json | jq -r '.version') 4 | SRC_FILES=$(shell find src -type f) 5 | CORE_C_FILES=$(shell find src/core -type f -name "*.c") 6 | BIN_C_FILES=$(shell find src/bin -type f -name "*.c") 7 | WASM_C_FILES=$(shell find src/wasm -type f -name "*.c") 8 | 9 | all: dist/liblucy-debug-node.mjs dist/liblucy-debug-browser.mjs \ 10 | dist/liblucy-release-node.mjs dist/liblucy-release-browser.mjs bin/lucyc 11 | .PHONY: all 12 | 13 | build: 14 | @mkdir -p build 15 | 16 | dist: 17 | @mkdir -p dist 18 | 19 | build/liblucy-debug.mjs: build $(SRC_FILES) 20 | $(EMCC) $(WASM_C_FILES) $(CORE_C_FILES) -o $@ \ 21 | --pre-js src/pre_js.js \ 22 | -s EXPORTED_FUNCTIONS='["_main", "_compile_xstate", "_xs_get_js", "_xs_get_dts", "_xs_init", "_xs_create", "_destroy_xstate_result"]' \ 23 | -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall", "cwrap", "addOnPostRun", "stringToUTF8", "UTF8ToString"]' \ 24 | -s EXPORT_ES6 \ 25 | -s TEXTDECODER=1 26 | scripts/post_compile_mjs $@ 27 | 28 | build/liblucy-release.mjs: build $(SRC_FILES) 29 | $(EMCC) $(WASM_C_FILES) $(CORE_C_FILES) -o $@ \ 30 | --pre-js src/pre_js.js \ 31 | -s EXPORTED_FUNCTIONS='["_main", "_compile_xstate", "_xs_get_js", "_xs_get_dts", "_xs_init", "_xs_create", "_destroy_xstate_result"]' \ 32 | -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall", "cwrap", "addOnPostRun", "stringToUTF8", "UTF8ToString"]' \ 33 | -s TEXTDECODER=1 \ 34 | -O3 35 | scripts/post_compile_mjs $@ 36 | 37 | dist/liblucy-debug.wasm: dist build/liblucy-debug.mjs 38 | @mv build/liblucy-debug.wasm $@ 39 | 40 | dist/liblucy-release.wasm: dist build/liblucy-release.mjs 41 | @mv build/liblucy-release.wasm $@ 42 | 43 | dist/liblucy-debug-node.mjs: dist build/liblucy-debug.mjs dist/liblucy-debug.wasm 44 | scripts/mk_node_mjs build/liblucy-debug.mjs $@ 45 | 46 | dist/liblucy-debug-browser.mjs: dist build/liblucy-debug.mjs dist/liblucy-debug.wasm 47 | cp build/liblucy-debug.mjs $@ 48 | 49 | dist/liblucy-release-node.mjs: dist build/liblucy-release.mjs dist/liblucy-release.wasm 50 | scripts/mk_node_mjs build/liblucy-release.mjs $@ 51 | 52 | dist/liblucy-release-browser.mjs: dist build/liblucy-release.mjs dist/liblucy-release.wasm 53 | cp build/liblucy-release.mjs $@ 54 | 55 | bin/lucyc: $(SRC_FILES) 56 | @mkdir -p bin 57 | $(CC) ${BIN_C_FILES} $(CORE_C_FILES) -o $@ \ 58 | -DVERSION=\"$(VERSION)\" \ 59 | -lm 60 | 61 | clean: 62 | @rm -f dist/liblucy-debug-browser.mjs dist/liblucy-debug-node.mjs \ 63 | dist/liblucy-debug.wasm dist/liblucy-release-browser.mjs \ 64 | dist/liblucy-release-node.mjs dist/liblucy-release.wasm 65 | @rm -f bin/lucyc 66 | @rm -f build/liblucy-debug.mjs build/liblucy-release.mjs 67 | @rmdir dist bin build 2> /dev/null 68 | .PHONY: clean 69 | 70 | test-native: 71 | @scripts/test_snapshots 72 | @scripts/test_unit 73 | .PHONY: test-native 74 | 75 | test-wasm: 76 | @LUCYC=scripts/lucyc.mjs scripts/test_snapshots 77 | .PHONY: test-wasm 78 | 79 | test-wasm-release: 80 | @LUCYC=scripts/lucyc.mjs NODE_ENV=production scripts/test_snapshots 81 | .PHONY: test-wasm-release 82 | 83 | test: test-native test-wasm test-wasm-release 84 | .PHONY: test 85 | -------------------------------------------------------------------------------- /src/core/str_builder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /*! addtogroup str_builder String Builder 6 | * https://nachtimwald.com/2017/02/26/efficient-c-string-builder/ 7 | * A mutable string of characters used to dynamically build a string. 8 | * 9 | * @{ 10 | */ 11 | 12 | struct str_builder; 13 | typedef struct str_builder str_builder_t; 14 | 15 | /* - - - - */ 16 | 17 | /*! Create a str builder. 18 | * 19 | * return str builder. 20 | */ 21 | str_builder_t *str_builder_create(void); 22 | 23 | /*! Destroy a str builder. 24 | * 25 | * param[in,out] sb Builder. 26 | */ 27 | void str_builder_destroy(str_builder_t *sb); 28 | 29 | /* - - - - */ 30 | 31 | /*! Add a string to the builder. 32 | * 33 | * param[in,out] sb Builder. 34 | * param[in] str String to add. 35 | * param[in] len Length of string to add. If 0, strlen will be called 36 | * internally to determine length. 37 | */ 38 | void str_builder_add_str(str_builder_t *sb, const char *str, size_t len); 39 | 40 | void str_builder_copy_str(str_builder_t *sb, char *str, size_t start, size_t end); 41 | 42 | /*! Add a character to the builder. 43 | * 44 | * param[in,out] sb Builder. 45 | * param[in] c Character. 46 | */ 47 | void str_builder_add_char(str_builder_t *sb, char c); 48 | 49 | /*! Add an integer as to the builder. 50 | * 51 | * param[in,out] sb Builder. 52 | * param[in] val Int to add. 53 | */ 54 | void str_builder_add_int(str_builder_t *sb, int val); 55 | 56 | /* - - - - */ 57 | 58 | /*! Clear the builder. 59 | * 60 | * param[in,out] sb Builder. 61 | */ 62 | void str_builder_clear(str_builder_t *sb); 63 | 64 | /*! Remove data from the end of the builder. 65 | * 66 | * param[in,out] sb Builder. 67 | * param[in] len The new length of the string. 68 | * Anything after this length is removed. 69 | */ 70 | void str_builder_truncate(str_builder_t *sb, size_t len); 71 | 72 | /*! Remove data from the beginning of the builder. 73 | * 74 | * param[in,out] sb Builder. 75 | * param[in] len The length to remove. 76 | */ 77 | void str_builder_drop(str_builder_t *sb, size_t len); 78 | 79 | /* - - - - */ 80 | 81 | /*! The length of the string contained in the builder. 82 | * 83 | * param[in] sb Builder. 84 | * 85 | * return Length. 86 | */ 87 | size_t str_builder_len(const str_builder_t *sb); 88 | 89 | /*! A pointer to the internal buffer with the builder's string data. 90 | * 91 | * The data is guaranteed to be NULL terminated. 92 | * 93 | * param[in] sb Builder. 94 | * 95 | * return Pointer to internal string data. 96 | */ 97 | const char *str_builder_peek(const str_builder_t *sb); 98 | 99 | /*! Return a copy of the string data. 100 | * 101 | * param[in] sb Builder. 102 | * param[out] len Length of returned data. Can be NULL if not needed. 103 | * 104 | * return Copy of the internal string data. 105 | */ 106 | char *str_builder_dump(const str_builder_t *sb, size_t *len); 107 | 108 | char str_builder_char_at(const str_builder_t *sb, int index); 109 | 110 | /*! @} 111 | */ -------------------------------------------------------------------------------- /src/core/parser/use.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../error.h" 3 | #include "../node.h" 4 | #include "../state.h" 5 | #include "core.h" 6 | #include "token.h" 7 | 8 | static int consume_use_specifiers(ImportNode* import_node, State* state) { 9 | int err = 0; 10 | int token; 11 | 12 | ImportSpecifier *specifier; 13 | while(true) { 14 | token = consume_token(state); 15 | 16 | switch(token) { 17 | case TOKEN_IDENTIFIER: { 18 | char* identifier = state_take_word(state); 19 | if(strcmp(identifier, "as") == 0) { 20 | 21 | error_msg_with_code_block(state, (Node*)import_node, "Import aliases are not currently supported."); 22 | return 2; 23 | } 24 | 25 | specifier = node_create_import_specifier(identifier); 26 | break; 27 | } 28 | case TOKEN_END_BLOCK: { 29 | node_append((Node*)import_node, (Node*)specifier); 30 | goto end; 31 | } 32 | case TOKEN_EOL: { 33 | break; 34 | } 35 | case TOKEN_COMMA: { 36 | node_append((Node*)import_node, (Node*)specifier); 37 | break; 38 | } 39 | default: { 40 | error_unexpected_identifier(state, (Node*)import_node); 41 | goto end; 42 | } 43 | } 44 | } 45 | 46 | end: { 47 | return err; 48 | } 49 | } 50 | 51 | int parser_consume_use(State* state) { 52 | int err = 0; 53 | Node *current_node = state->node; 54 | 55 | if(current_node != NULL) { 56 | error_msg_with_code_block(state, current_node, "Import statement must be at the top of the file."); 57 | err = 1; 58 | goto end; 59 | } 60 | 61 | ImportNode *import_node = node_create_import_statement(); 62 | Node *node = (Node*)import_node; 63 | state_node_start_pos(state, node, 4); 64 | state_node_set(state, node); 65 | 66 | int token; 67 | 68 | token = consume_token(state); 69 | 70 | // use './util' 71 | if(token != TOKEN_STRING) { 72 | error_msg_with_code_block(state, current_node, "Expected a string."); 73 | err = 1; 74 | goto end; 75 | } 76 | import_node->from = state_take_word(state); 77 | 78 | // Start grabbing specififers 79 | bool consumed_loc = false; 80 | bool consumed_specifiers = false; 81 | bool consumed_from = false; 82 | loop: while(true) { 83 | token = consume_token(state); 84 | 85 | switch(token) { 86 | case TOKEN_EOL: { 87 | if(consumed_specifiers) { 88 | goto end; 89 | } 90 | }; 91 | case TOKEN_BEGIN_BLOCK: { 92 | _check(consume_use_specifiers(import_node, state)); 93 | consumed_specifiers = true; 94 | 95 | break; 96 | } 97 | case TOKEN_IDENTIFIER: { 98 | error_unexpected_identifier(state, node); 99 | err = 1; 100 | goto end; 101 | } 102 | case TOKEN_UNKNOWN: { 103 | error_unexpected_identifier(state, node); 104 | err = 2; 105 | goto end; 106 | } 107 | default: { 108 | error_unexpected_identifier(state, node); 109 | err = 2; 110 | goto end; 111 | } 112 | } 113 | } 114 | 115 | end: { 116 | state_node_up(state); 117 | return err; 118 | } 119 | } -------------------------------------------------------------------------------- /src/core/parser/local.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "../error.h" 4 | #include "../keyword.h" 5 | #include "../local.h" 6 | #include "../node.h" 7 | #include "../state.h" 8 | #include "action.h" 9 | #include "assign.h" 10 | #include "core.h" 11 | #include "send.h" 12 | #include "token.h" 13 | 14 | int parser_consume_local(State* state, unsigned short key) { 15 | int err = 0; 16 | 17 | LocalNode* local_node = node_create_local(); 18 | Node* node = (Node*)local_node; 19 | local_node->key = key; 20 | state_node_start_pos(state, node, state->token_len); 21 | 22 | Node* parent_node = state->node; 23 | if(parent_node->type != NODE_STATE_TYPE) { 24 | char buffer[100]; 25 | sprintf(buffer, "Unexpected parent of %s", local_get_name(key)); 26 | error_msg_with_code_block_dec(state, state->token_len, buffer); 27 | err = 1; 28 | } 29 | 30 | state_node_set(state, node); 31 | 32 | StateNode* parent_state_node = (StateNode*)parent_node; 33 | if(key == LOCAL_ENTRY) { 34 | parent_state_node->entry = local_node; 35 | } else { 36 | parent_state_node->exit = local_node; 37 | } 38 | 39 | int token; 40 | while(true) { 41 | token = consume_token(state); 42 | 43 | if(token == TOKEN_EOL) { 44 | break; 45 | } 46 | 47 | if(token != TOKEN_CALL) { 48 | error_msg_with_code_block_dec(state, state->token_len, "Expected a => here."); 49 | err = 1; 50 | } 51 | 52 | token = consume_token(state); 53 | 54 | if(token != TOKEN_IDENTIFIER) { 55 | error_msg_with_code_block_dec(state, state->token_len, "Expected an action."); 56 | err = 2; 57 | goto end; 58 | } 59 | 60 | char* identifier = state_take_word(state); 61 | unsigned short key = keyword_get(identifier); 62 | switch(key) { 63 | case KW_ACTION: { 64 | _check(parser_consume_inline_action(state, node)); 65 | free(identifier); 66 | break; 67 | } 68 | case KW_ASSIGN: { 69 | _check(parser_consume_inline_assign(state, node)); 70 | free(identifier); 71 | break; 72 | } 73 | case KW_SEND: { 74 | _check(parser_consume_inline_send(state, node)); 75 | free(identifier); 76 | break; 77 | } 78 | case KW_GUARD: { 79 | err = 1; 80 | char buffer[100]; 81 | sprintf(buffer, "Guards are not allowed in %s", local_get_name(key)); 82 | error_msg_with_code_block_dec(state, state->token_len, buffer); 83 | free(identifier); 84 | break; 85 | } 86 | // Not a keyword 87 | case KW_NOT_A_KEYWORD: { 88 | if(state_has_action(state, identifier)) { 89 | TransitionAction* action = create_transition_action(); 90 | action->name = identifier; 91 | node_local_add_action(local_node, action); 92 | } else { 93 | err = 1; 94 | char buffer[100]; 95 | sprintf(buffer, "Unknown action [%s]. Did you mean to declare this action at the top of the machine?", identifier); 96 | error_msg_with_code_block_dec(state, state->token_len, buffer); 97 | free(identifier); 98 | } 99 | break; 100 | } 101 | default: { 102 | error_msg_with_code_block_dec(state, state->token_len, "Unexpected keyword in action assignment."); 103 | err = 1; 104 | free(identifier); 105 | break; 106 | } 107 | } 108 | } 109 | 110 | end: { 111 | state_node_up(state); 112 | return err; 113 | } 114 | } -------------------------------------------------------------------------------- /src/core/parser/machine.c: -------------------------------------------------------------------------------- 1 | #include "../error.h" 2 | #include "../keyword.h" 3 | #include "../node.h" 4 | #include "../state.h" 5 | #include "action.h" 6 | #include "core.h" 7 | #include "guard.h" 8 | #include "machine_state.h" 9 | #include "token.h" 10 | #include "use.h" 11 | 12 | static int consume_machine_inner(State* state, bool is_implicit, int initial_token) { 13 | int err = 0; 14 | int token = is_implicit ? initial_token : consume_token(state); 15 | 16 | while(true) { 17 | switch(token) { 18 | case TOKEN_EOL: goto next; 19 | case TOKEN_EOF: goto end; 20 | case TOKEN_END_BLOCK: { 21 | if(!is_implicit) { 22 | goto end; 23 | } 24 | error_unexpected_identifier(state, state->node); 25 | err = 1; 26 | break; 27 | } 28 | case TOKEN_IDENTIFIER: { 29 | char* identifier = state->word; 30 | 31 | if(!is_keyword(identifier)) { 32 | error_msg_with_code_block(state, state->node, "Unknown top-level identifier."); 33 | err = 2; 34 | goto end; 35 | } 36 | 37 | unsigned short key = keyword_get(identifier); 38 | switch(key) { 39 | case KW_INITIAL: { 40 | state->modifier = MODIFIER_TYPE_INITIAL; 41 | break; 42 | } 43 | case KW_FINAL: { 44 | state->modifier = MODIFIER_TYPE_FINAL; 45 | break; 46 | } 47 | case KW_STATE: { 48 | _check(parser_consume_state(state)); 49 | break; 50 | } 51 | case KW_USE: { 52 | _check(parser_consume_use(state)); 53 | break; 54 | } 55 | case KW_ACTION: { 56 | _check(parser_consume_action(state)); 57 | break; 58 | } 59 | case KW_GUARD: { 60 | _check(parser_consume_guard(state)); 61 | break; 62 | } 63 | } 64 | 65 | break; 66 | } 67 | default: { 68 | error_unexpected_identifier(state, state->node); 69 | err = 2; 70 | goto end; 71 | } 72 | } 73 | 74 | next: { 75 | token = consume_token(state); 76 | } 77 | } 78 | 79 | end: { 80 | return err; 81 | } 82 | } 83 | 84 | /* Public API */ 85 | int parser_consume_machine(State* state) { 86 | int err = 0; 87 | 88 | MachineNode* machine_node = node_create_machine(); 89 | Node* node = (Node*)machine_node; 90 | state_node_set(state, node); 91 | 92 | int token = consume_token(state); 93 | if(token != TOKEN_IDENTIFIER) { 94 | error_msg_with_code_block(state, node, "Machine must have a name."); 95 | err = 1; 96 | goto end; 97 | } 98 | machine_node->name_start = state->word_start; 99 | machine_node->name_end = state->word_end; 100 | 101 | token = consume_token(state); 102 | 103 | if(token != TOKEN_BEGIN_BLOCK) { 104 | error_unexpected_identifier(state, node); 105 | err = 1; 106 | goto end; 107 | } 108 | 109 | state->current_machine_node = machine_node; 110 | _check(consume_machine_inner(state, false, 0)); 111 | state->current_machine_node = NULL; 112 | 113 | end: { 114 | state_node_up(state); 115 | return err; 116 | } 117 | } 118 | 119 | int parser_consume_implicit_machine(State* state, int current_token) { 120 | int err = 0; 121 | 122 | MachineNode* machine_node = node_create_machine(); 123 | Node* node = (Node*)machine_node; 124 | 125 | state_node_set(state, node); 126 | 127 | state->current_machine_node = machine_node; 128 | _check(consume_machine_inner(state, true, current_token)); 129 | state->current_machine_node = NULL; 130 | 131 | state_node_up(state); 132 | return err; 133 | } -------------------------------------------------------------------------------- /src/core/xstate/state.c: -------------------------------------------------------------------------------- 1 | #include "../js_builder.h" 2 | #include "../local.h" 3 | #include "../node.h" 4 | #include "../set.h" 5 | #include "core.h" 6 | #include "invoke.h" 7 | #include "transition.h" 8 | #include "ts_printer.h" 9 | 10 | static void compile_local_node(PrintState* state, JSBuilder* jsb, LocalNode* local_node) { 11 | switch(local_node->key) { 12 | case LOCAL_ENTRY: { 13 | state->in_entry = true; 14 | xs_compile_transition_action(state, jsb, local_node->action, "entry"); 15 | state->in_entry = false; 16 | break; 17 | } 18 | case LOCAL_EXIT: { 19 | xs_compile_transition_action(state, jsb, local_node->action, "exit"); 20 | break; 21 | } 22 | } 23 | } 24 | 25 | void xs_enter_state(PrintState* state, JSBuilder* jsb, Node* node) { 26 | StateNode* state_node = (StateNode*)node; 27 | MachineNode* machine_node = (MachineNode*)node->parent; 28 | if(!(machine_node->impl_flags & XS_HAS_STATE_PROP)) { 29 | machine_node->impl_flags |= XS_HAS_STATE_PROP; 30 | js_builder_start_prop(jsb, "states"); 31 | js_builder_start_object(jsb); 32 | } 33 | 34 | js_builder_start_prop_no_copy(jsb, state->source, state_node->name_start, state_node->name_end); 35 | js_builder_start_object(jsb); 36 | 37 | if(state_node->final) { 38 | js_builder_start_prop(jsb, "type"); 39 | js_builder_add_string(jsb, "final"); 40 | } 41 | 42 | if(state_node->entry != NULL) { 43 | state->cur_state_start = state_node->name_start; 44 | state->cur_state_end = state_node->name_end; 45 | compile_local_node(state, jsb, state_node->entry); 46 | state->cur_state_start = 0; 47 | state->cur_state_end = 0; 48 | } 49 | if(state_node->exit != NULL) { 50 | compile_local_node(state, jsb, state_node->exit); 51 | } 52 | if(state_node->event_transition != NULL) { 53 | js_builder_start_prop(jsb, "on"); 54 | js_builder_start_object(jsb); 55 | TransitionNode* transition_node = state_node->event_transition; 56 | while(transition_node != NULL) { 57 | xs_compile_event_transition(state, jsb, transition_node); 58 | transition_node = transition_node->next; 59 | } 60 | js_builder_end_object(jsb); 61 | } 62 | if(state_node->immediate_transition != NULL) { 63 | js_builder_start_prop(jsb, "always"); 64 | js_builder_start_array(jsb, true); 65 | js_builder_add_indent(jsb); 66 | 67 | TransitionNode* transition_node = state_node->immediate_transition; 68 | bool comma = false; 69 | do { 70 | if(comma) { 71 | js_builder_add_str(jsb, ", "); 72 | } 73 | xs_compile_inner_transition(state, jsb, transition_node); 74 | transition_node = transition_node->next; 75 | comma = true; 76 | } while(transition_node != NULL); 77 | js_builder_end_array(jsb, true); 78 | } 79 | if(state_node->delay_transition != NULL) { 80 | js_builder_start_prop(jsb, "after"); 81 | js_builder_start_object(jsb); 82 | TransitionNode* transition_node = state_node->delay_transition; 83 | do { 84 | Node* transition_node_node = (Node*)transition_node; 85 | xs_compile_transition_key(state, jsb, transition_node_node, NULL); 86 | xs_compile_inner_transition(state, jsb, transition_node); 87 | transition_node = transition_node->next; 88 | } while(transition_node != NULL); 89 | js_builder_end_object(jsb); 90 | } 91 | if(state_node->invoke != NULL) { 92 | xs_compile_invoke(state, jsb, state_node->invoke); 93 | } 94 | } 95 | 96 | void xs_exit_state(PrintState* state, JSBuilder* jsb, Node* node) { 97 | StateNode* state_node = (StateNode*)node; 98 | 99 | // Close out this state node. 100 | js_builder_end_object(jsb); 101 | 102 | // End of all state nodes 103 | if(!node->next || node->next->type != NODE_STATE_TYPE) { 104 | js_builder_end_object(jsb); 105 | } 106 | set_clear(state->events); 107 | } -------------------------------------------------------------------------------- /src/core/parser/machine_state.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "../error.h" 4 | #include "../keyword.h" 5 | #include "../local.h" 6 | #include "../node.h" 7 | #include "../state.h" 8 | #include "core.h" 9 | #include "invoke.h" 10 | #include "local.h" 11 | #include "machine.h" 12 | #include "token.h" 13 | #include "transition.h" 14 | 15 | int parser_consume_state(State* state) { 16 | int err = 0; 17 | 18 | StateNode* state_node = node_create_state(); 19 | Node* state_node_node = (Node*)state_node; 20 | state_node_start_pos(state, state_node_node, 5); 21 | 22 | Node* parent_node = state->node; 23 | if(parent_node->type != NODE_MACHINE_TYPE) { 24 | error_msg_with_code_block(state, state_node_node, "Unexpected parent node for state."); 25 | return 2; 26 | } 27 | 28 | state_node_set(state, state_node_node); 29 | 30 | int token = consume_token(state); 31 | 32 | switch(token) { 33 | case TOKEN_IDENTIFIER: { 34 | // Set the name of the state 35 | state_node->name_start = state->word_start; 36 | state_node->name_end = state->word_end; 37 | 38 | switch(state->modifier) { 39 | case MODIFIER_TYPE_INITIAL: { 40 | state->modifier = MODIFIER_NONE; 41 | MachineNode* machine_node = (MachineNode*)parent_node; 42 | machine_node->initial_start = state_node->name_start; 43 | machine_node->initial_end = state_node->name_end; 44 | break; 45 | } 46 | case MODIFIER_TYPE_FINAL: { 47 | state->modifier = MODIFIER_NONE; 48 | state_node->final = true; 49 | break; 50 | } 51 | } 52 | 53 | token = consume_token(state); 54 | break; 55 | } 56 | default: { 57 | err = 1; 58 | error_msg_with_code_block_dec(state, state->token_len, "States must be given a name."); 59 | break; 60 | } 61 | } 62 | 63 | if(token != TOKEN_BEGIN_BLOCK) { 64 | error_unexpected_identifier(state, state_node_node); 65 | return 2; 66 | } 67 | 68 | while(true) { 69 | token = consume_token(state); 70 | 71 | switch(token) { 72 | case TOKEN_EOL: continue; 73 | case TOKEN_END_BLOCK: { 74 | state_node_node->end = state->index; 75 | goto end; 76 | }; 77 | case TOKEN_CALL: { 78 | state_set_word(state, NULL, 0, 0); 79 | _check(parser_consume_transition(state)); 80 | break; 81 | } 82 | case TOKEN_IDENTIFIER: { 83 | unsigned short key = keyword_get(state->word); 84 | 85 | switch(key) { 86 | case KW_INVOKE: { 87 | _check(parser_consume_invoke(state)); 88 | break; 89 | } 90 | case KW_MACHINE: { 91 | _check(parser_consume_machine(state)); 92 | break; 93 | } 94 | default: { 95 | _check(parser_consume_transition(state)); 96 | break; 97 | } 98 | } 99 | 100 | break; 101 | } 102 | case TOKEN_LOCAL: { 103 | char* local = state_take_word(state); 104 | unsigned short key = local_get(local); 105 | switch(key) { 106 | case LOCAL_ENTRY: 107 | case LOCAL_EXIT: { 108 | _check(parser_consume_local(state, key)); 109 | break; 110 | } 111 | default: { 112 | char buffer[100]; 113 | sprintf(buffer, "Unknown local variable (%s)", state->word); 114 | error_msg_with_code_block_dec(state, state->token_len, buffer); 115 | err = 1; 116 | break; 117 | } 118 | } 119 | break; 120 | } 121 | default: { 122 | error_unexpected_identifier(state, state_node_node); 123 | goto end; 124 | } 125 | } 126 | } 127 | 128 | end: { 129 | state_node_up(state); 130 | return err; 131 | } 132 | } -------------------------------------------------------------------------------- /src/core/dict.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "dict.h" 4 | 5 | #define INITIAL_SIZE (1024) 6 | #define GROWTH_FACTOR (2) 7 | #define MAX_LOAD_FACTOR (1) 8 | 9 | /* dictionary initialization code used in both DictCreate and grow */ 10 | static dict* internal_dict_create(int size) { 11 | dict *d; 12 | int i; 13 | 14 | d = malloc(sizeof(*d)); 15 | 16 | d->size = size; 17 | d->n = 0; 18 | d->table = malloc(sizeof(struct elt *) * d->size); 19 | 20 | for(i = 0; i < d->size; i++) d->table[i] = 0; 21 | 22 | return d; 23 | } 24 | 25 | dict* dict_create(void) { 26 | return internal_dict_create(INITIAL_SIZE); 27 | } 28 | 29 | static void dict_destroy(dict *d) { 30 | int i; 31 | struct elt *e; 32 | struct elt *next; 33 | 34 | for(i = 0; i < d->size; i++) { 35 | for(e = d->table[i]; e != 0; e = next) { 36 | next = e->next; 37 | 38 | free(e->key); 39 | free(e); 40 | } 41 | } 42 | 43 | free(d->table); 44 | free(d); 45 | } 46 | 47 | #define MULTIPLIER (97) 48 | 49 | unsigned long hash_function(const char *s) { 50 | unsigned const char *us; 51 | unsigned long h; 52 | 53 | h = 0; 54 | 55 | for(us = (unsigned const char *) s; *us; us++) { 56 | h = h * MULTIPLIER + *us; 57 | } 58 | 59 | return h; 60 | } 61 | 62 | static void grow(dict *d) { 63 | dict *d2; /* new dictionary we'll create */ 64 | struct dict swap; /* temporary structure for brain transplant */ 65 | int i; 66 | struct elt *e; 67 | 68 | d2 = internal_dict_create(d->size * GROWTH_FACTOR); 69 | 70 | for(i = 0; i < d->size; i++) { 71 | for(e = d->table[i]; e != 0; e = e->next) { 72 | /* note: this recopies everything */ 73 | /* a more efficient implementation would 74 | * patch out the strdups inside DictInsert 75 | * to avoid this problem */ 76 | dict_insert(d2, e->key, e->value); 77 | } 78 | } 79 | 80 | /* the hideous part */ 81 | /* We'll swap the guts of d and d2 */ 82 | /* then call DictDestroy on d2 */ 83 | swap = *d; 84 | *d = *d2; 85 | *d2 = swap; 86 | 87 | dict_destroy(d2); 88 | } 89 | 90 | /* insert a new key-value pair into an existing dictionary */ 91 | void dict_insert(dict *d, const char *key, unsigned short value) { 92 | struct elt *e; 93 | unsigned long h; 94 | 95 | e = malloc(sizeof(*e)); 96 | 97 | e->key = strdup(key); 98 | e->value = value; 99 | 100 | h = hash_function(key) % d->size; 101 | 102 | e->next = d->table[h]; 103 | d->table[h] = e; 104 | 105 | d->n++; 106 | 107 | /* grow table if there is not enough room */ 108 | if(d->n >= d->size * MAX_LOAD_FACTOR) { 109 | grow(d); 110 | } 111 | } 112 | 113 | unsigned short dict_search(dict *d, const char *key) { 114 | struct elt *e; 115 | 116 | for(e = d->table[hash_function(key) % d->size]; e != 0; e = e->next) { 117 | if(!strcmp(e->key, key)) { 118 | /* got it */ 119 | return e->value; 120 | } 121 | } 122 | 123 | return 0; 124 | } 125 | 126 | /* delete the most recently inserted record with the given key */ 127 | /* if there is no such record, has no effect */ 128 | static void dict_delete(dict *d, const char *key) { 129 | struct elt **prev; /* what to change when elt is deleted */ 130 | struct elt *e; /* what to delete */ 131 | 132 | for(prev = &(d->table[hash_function(key) % d->size]); 133 | *prev != 0; 134 | prev = &((*prev)->next)) { 135 | if(!strcmp((*prev)->key, key)) { 136 | /* got it */ 137 | e = *prev; 138 | *prev = e->next; 139 | 140 | free(e->key); 141 | free(e); 142 | 143 | return; 144 | } 145 | } 146 | } -------------------------------------------------------------------------------- /src/core/state.c: -------------------------------------------------------------------------------- 1 | #include // TODO remove 2 | #include 3 | #include 4 | #include 5 | #include "node.h" 6 | #include "state.h" 7 | 8 | State* state_new_state(char* source, char* filename) { 9 | State *state = malloc(sizeof *state); 10 | state->source = source; 11 | state->filename = filename; 12 | state->source_len = strlen(source); 13 | state->index = 0; 14 | state->started = false; 15 | 16 | state->guards = malloc(sizeof(SimpleSet)); 17 | set_init(state->guards); 18 | state->actions = malloc(sizeof(SimpleSet)); 19 | set_init(state->actions); 20 | 21 | state->node = NULL; 22 | state->parent_node = NULL; 23 | state->current_machine_node = NULL; 24 | 25 | state->word = NULL; 26 | state->line = 0; 27 | state->column = 0; 28 | return state; 29 | } 30 | 31 | void state_set_word(State* state, char* word, size_t start, size_t end) { 32 | state_reset_word(state); 33 | state->word = word; 34 | state->word_start = start; 35 | state->word_end = end; 36 | } 37 | 38 | char* state_take_word(State* state) { 39 | char* word = state->word; 40 | state->word = NULL; 41 | return word; 42 | } 43 | 44 | void state_reset_word(State* state) { 45 | if(state->word != NULL) { 46 | free(state->word); 47 | state->word = NULL; 48 | } 49 | } 50 | 51 | void state_advance_column(State* state) { 52 | state->column++; 53 | } 54 | 55 | void state_advance_line(State* state) { 56 | state->line++; 57 | state->column = 0; 58 | } 59 | 60 | int state_inbounds(State* state) { 61 | return state->index < state->source_len; 62 | } 63 | 64 | static inline char state_char_at(State* state, unsigned short index) { 65 | return state->source[index]; 66 | } 67 | 68 | char state_char(State* state) { 69 | return state->source[state->index]; 70 | } 71 | 72 | char state_peek(State* state) { 73 | return state->source[state->index + 1]; 74 | } 75 | 76 | char state_next(State* state) { 77 | if(!state->started) { 78 | state->started = true; 79 | } else { 80 | state->index++; 81 | } 82 | 83 | return state_char(state); 84 | } 85 | 86 | void state_find_position(State* state, pos_t* pos, int inc) { 87 | size_t index = state->index; 88 | int col = 0; 89 | int line = state->line; 90 | int i = inc; 91 | char c; 92 | while(index > 0) { 93 | c = state_char_at(state, index); 94 | 95 | if(i <= 0) { 96 | col++; 97 | } 98 | 99 | if(c == '\n') { 100 | if(i <= 0) { 101 | // Reached the beginning of the line. 102 | break; 103 | } else { 104 | line--; 105 | } 106 | } 107 | 108 | i--; 109 | index--; 110 | } 111 | 112 | pos->line = line; 113 | pos->column = col; 114 | } 115 | 116 | void state_rewind(State* state, int inc) { 117 | pos_t pos; 118 | state_find_position(state, &pos, inc); 119 | state->index = state->index - inc; 120 | state->line = pos.line; 121 | state->column = pos.column; 122 | } 123 | 124 | void state_node_set(State* state, Node* node) { 125 | if(state->node != NULL) { 126 | Node* parent_node = state->node; 127 | state->parent_node = parent_node; 128 | 129 | node_append(parent_node, node); 130 | } else { 131 | Program* program = state->program; 132 | if(program->body == NULL) { 133 | program->body = node; 134 | } else { 135 | node_after_last(program->body, node); 136 | } 137 | } 138 | 139 | state->node = node; 140 | } 141 | 142 | void state_node_up(State* state) { 143 | Node* current_parent = state->parent_node; 144 | if(current_parent != NULL) { 145 | Node* new_parent = current_parent->parent; 146 | state->node = current_parent; 147 | state->parent_node = new_parent; 148 | } else { 149 | state->node = NULL; 150 | } 151 | } 152 | 153 | void state_node_start_pos(State* state, Node* node, unsigned short rewind_amount) { 154 | size_t start = state->index - rewind_amount; 155 | node->start = start; 156 | node->line = state->line; 157 | } 158 | 159 | void state_add_guard(State* state, char* name) { 160 | set_add(state->guards, name); 161 | } 162 | 163 | bool state_has_guard(State* state, char* name) { 164 | return set_contains(state->guards, name) == SET_TRUE; 165 | } 166 | 167 | void state_add_action(State* state, char* name) { 168 | set_add(state->actions, name); 169 | } 170 | 171 | bool state_has_action(State* state, char* name) { 172 | return set_contains(state->actions, name) == SET_TRUE; 173 | } -------------------------------------------------------------------------------- /src/core/parser/action.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../error.h" 3 | #include "../keyword.h" 4 | #include "../node.h" 5 | #include "../state.h" 6 | #include "assign.h" 7 | #include "core.h" 8 | #include "send.h" 9 | #include "token.h" 10 | 11 | static int consume_inline_action_args(State* state, void* expr, int token, char* arg, int _argi) { 12 | ActionExpression* action_expression = (ActionExpression*)expr; 13 | 14 | if(token == TOKEN_IDENTIFIER) { 15 | IdentifierExpression* ref = node_create_identifierexpression(); 16 | ref->name = arg; 17 | action_expression->ref = (Expression*)ref; 18 | } else if(token == TOKEN_SYMBOL) { 19 | SymbolExpression* ref = node_create_symbolexpression(); 20 | ref->name = arg; 21 | action_expression->ref = (Expression*)ref; 22 | 23 | } else { 24 | error_msg_with_code_block_dec(state, state->token_len, "Unexpected argument to action()"); 25 | return 1; 26 | } 27 | 28 | return 0; 29 | } 30 | 31 | int parser_consume_inline_action(State* state, Node* node) { 32 | int err = 0; 33 | 34 | ActionExpression* action_expression = node_create_actionexpression(); 35 | 36 | _check(consume_call_expression(state, "action", action_expression, &consume_inline_action_args)); 37 | 38 | switch(node->type) { 39 | case NODE_TRANSITION_TYPE: { 40 | TransitionNode* transition_node = (TransitionNode*)node; 41 | TransitionAction* action = node_transition_add_action(transition_node, NULL); 42 | action->expression = (Expression*)action_expression; 43 | 44 | if(action_expression->ref->type == EXPRESSION_SYMBOL) { 45 | MachineNode* parent_machine_node = find_closest_machine_node((Node*)transition_node); 46 | parent_machine_node->flags |= MACHINE_USES_ACTION; 47 | } 48 | break; 49 | } 50 | case NODE_LOCAL_TYPE: { 51 | LocalNode* local_node = (LocalNode*)node; 52 | TransitionAction* action = create_transition_action(); 53 | action->expression = (Expression*)action_expression; 54 | node_local_add_action(local_node, action); 55 | break; 56 | } 57 | } 58 | 59 | if(action_expression->ref->type == EXPRESSION_SYMBOL) { 60 | MachineNode* parent_machine_node = find_closest_machine_node(node); 61 | parent_machine_node->flags |= MACHINE_USES_ACTION; 62 | } 63 | 64 | return err; 65 | } 66 | 67 | int parser_consume_action(State* state) { 68 | int err = 0; 69 | 70 | Assignment* assignment = node_create_assignment(ASSIGNMENT_ACTION); 71 | Node *node = (Node*)assignment; 72 | state_node_set(state, node); 73 | 74 | int token; 75 | token = consume_token(state); 76 | 77 | if(token != TOKEN_IDENTIFIER) { 78 | error_unexpected_identifier(state, node); 79 | return 2; 80 | } 81 | 82 | char* binding_name = state_take_word(state); 83 | assignment->binding_name = binding_name; 84 | 85 | token = consume_token(state); 86 | if(token != TOKEN_ASSIGNMENT) { 87 | error_msg_with_code_block(state, node, "Expected an assignment"); 88 | return 2; 89 | } 90 | 91 | token = consume_token(state); 92 | switch(token) { 93 | case TOKEN_IDENTIFIER: { 94 | char* identifier = state_take_word(state); 95 | unsigned short key = keyword_get(identifier); 96 | switch(key) { 97 | case KW_ASSIGN: { 98 | AssignExpression *expression = node_create_assignexpression(); 99 | consume_call_expression(state, "assign", expression, &parser_consume_inline_assign_args); 100 | assignment->value = (Expression*)expression; 101 | program_add_flag(state->program, PROGRAM_USES_ASSIGN); 102 | free(identifier); 103 | break; 104 | } 105 | case KW_SEND: { 106 | SendExpression* expression = node_create_sendexpression(); 107 | consume_call_expression(state, "send", expression, &parser_consume_inline_send_args); 108 | assignment->value = (Expression*)expression; 109 | program_add_flag(state->program, PROGRAM_USES_SEND); 110 | free(identifier); 111 | break; 112 | } 113 | default: { 114 | IdentifierExpression* expression = node_create_identifierexpression(); 115 | expression->name = identifier; 116 | assignment->value = (Expression*)expression; 117 | } 118 | } 119 | break; 120 | } 121 | case TOKEN_SYMBOL: { 122 | SymbolExpression* expression = node_create_symbolexpression(); 123 | expression->name = state_take_word(state); 124 | assignment->value = (Expression*)expression; 125 | break; 126 | } 127 | default: { 128 | error_msg_with_code_block_dec(state, state->token_len, "Expected an identifier"); 129 | return 2; 130 | } 131 | } 132 | 133 | end: { 134 | state_add_action(state, assignment->binding_name); 135 | state_node_up(state); 136 | return err; 137 | } 138 | } -------------------------------------------------------------------------------- /src/core/str_builder.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "str_builder.h" 8 | 9 | /* - - - - */ 10 | 11 | static const size_t str_builder_min_size = 32; 12 | 13 | struct str_builder { 14 | char *str; 15 | size_t alloced; 16 | size_t len; 17 | }; 18 | 19 | /* - - - - */ 20 | 21 | str_builder_t *str_builder_create(void) 22 | { 23 | str_builder_t *sb; 24 | 25 | sb = calloc(1, sizeof(*sb)); 26 | sb->str = malloc(str_builder_min_size); 27 | *sb->str = '\0'; 28 | sb->alloced = str_builder_min_size; 29 | sb->len = 0; 30 | 31 | return sb; 32 | } 33 | 34 | void str_builder_destroy(str_builder_t *sb) 35 | { 36 | if (sb == NULL) 37 | return; 38 | free(sb->str); 39 | free(sb); 40 | } 41 | 42 | /* - - - - */ 43 | 44 | /*! Ensure there is enough space for data being added plus a NULL terminator. 45 | * 46 | * param[in,out] sb Builder. 47 | * param[in] add_len The length that needs to be added *not* including a NULL terminator. 48 | */ 49 | static void str_builder_ensure_space(str_builder_t *sb, size_t add_len) 50 | { 51 | if (sb == NULL || add_len == 0) 52 | return; 53 | 54 | if (sb->alloced >= sb->len+add_len+1) 55 | return; 56 | 57 | while (sb->alloced < sb->len+add_len+1) { 58 | /* Doubling growth strategy. */ 59 | sb->alloced <<= 1; 60 | if (sb->alloced == 0) { 61 | /* Left shift of max bits will go to 0. An unsigned type set to 62 | * -1 will return the maximum possible size. However, we should 63 | * have run out of memory well before we need to do this. Since 64 | * this is the theoretical maximum total system memory we don't 65 | * have a flag saying we can't grow any more because it should 66 | * be impossible to get to this point. */ 67 | sb->alloced--; 68 | } 69 | } 70 | sb->str = realloc(sb->str, sb->alloced); 71 | } 72 | 73 | /* - - - - */ 74 | 75 | void str_builder_add_str(str_builder_t *sb, const char *str, size_t len) 76 | { 77 | if (sb == NULL || str == NULL || *str == '\0') 78 | return; 79 | 80 | if (len == 0) 81 | len = strlen(str); 82 | 83 | str_builder_ensure_space(sb, len); 84 | memmove(sb->str+sb->len, str, len); 85 | sb->len += len; 86 | sb->str[sb->len] = '\0'; 87 | } 88 | 89 | void str_builder_copy_str(str_builder_t *sb, char *str, size_t start, size_t end) 90 | { 91 | size_t len = end - start; 92 | if (sb == NULL || str == NULL || *str == '\0') 93 | return; 94 | 95 | str_builder_ensure_space(sb, len); 96 | memmove(sb->str+sb->len, str + start, len); 97 | sb->len += len; 98 | sb->str[sb->len] = '\0'; 99 | } 100 | 101 | void str_builder_add_char(str_builder_t *sb, char c) 102 | { 103 | if (sb == NULL) 104 | return; 105 | str_builder_ensure_space(sb, 1); 106 | sb->str[sb->len] = c; 107 | sb->len++; 108 | sb->str[sb->len] = '\0'; 109 | } 110 | 111 | void str_builder_add_int(str_builder_t *sb, int val) 112 | { 113 | char str[12]; 114 | 115 | if (sb == NULL) 116 | return; 117 | 118 | snprintf(str, sizeof(str), "%d", val); 119 | str_builder_add_str(sb, str, 0); 120 | } 121 | 122 | /* - - - - */ 123 | 124 | void str_builder_clear(str_builder_t *sb) 125 | { 126 | if (sb == NULL) 127 | return; 128 | str_builder_truncate(sb, 0); 129 | } 130 | 131 | void str_builder_truncate(str_builder_t *sb, size_t len) 132 | { 133 | if (sb == NULL || len >= sb->len) 134 | return; 135 | 136 | sb->len = len; 137 | sb->str[sb->len] = '\0'; 138 | } 139 | 140 | void str_builder_drop(str_builder_t *sb, size_t len) 141 | { 142 | if (sb == NULL || len == 0) 143 | return; 144 | 145 | if (len >= sb->len) { 146 | str_builder_clear(sb); 147 | return; 148 | } 149 | 150 | sb->len -= len; 151 | /* +1 to move the NULL. */ 152 | memmove(sb->str, sb->str+len, sb->len+1); 153 | } 154 | 155 | /* - - - - */ 156 | 157 | size_t str_builder_len(const str_builder_t *sb) 158 | { 159 | if (sb == NULL) 160 | return 0; 161 | return sb->len; 162 | } 163 | 164 | const char *str_builder_peek(const str_builder_t *sb) 165 | { 166 | if (sb == NULL) 167 | return NULL; 168 | return sb->str; 169 | } 170 | 171 | char *str_builder_dump(const str_builder_t *sb, size_t *len) 172 | { 173 | char *out; 174 | 175 | if (sb == NULL) 176 | return NULL; 177 | 178 | if (len != NULL) 179 | *len = sb->len; 180 | out = malloc(sb->len+1); 181 | memcpy(out, sb->str, sb->len+1); 182 | return out; 183 | } 184 | 185 | char str_builder_char_at(const str_builder_t *sb, int index) { 186 | if(sb == NULL) 187 | return 0; 188 | 189 | return sb->str[index]; 190 | } -------------------------------------------------------------------------------- /src/core/error.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "node.h" 5 | #include "state.h" 6 | #include "str_builder.h" 7 | 8 | #define RESET "\033[0m" 9 | #define BLACK "\033[30m" /* Black */ 10 | #define RED "\033[31m" /* Red */ 11 | #define GREEN "\033[32m" /* Green */ 12 | #define YELLOW "\033[33m" /* Yellow */ 13 | #define BLUE "\033[34m" /* Blue */ 14 | #define MAGENTA "\033[35m" /* Magenta */ 15 | #define CYAN "\033[36m" /* Cyan */ 16 | #define WHITE "\033[37m" /* White */ 17 | #define BOLDBLACK "\033[1m\033[30m" /* Bold Black */ 18 | #define BOLDRED "\033[1m\033[31m" /* Bold Red */ 19 | #define BOLDGREEN "\033[1m\033[32m" /* Bold Green */ 20 | #define BOLDYELLOW "\033[1m\033[33m" /* Bold Yellow */ 21 | #define BOLDBLUE "\033[1m\033[34m" /* Bold Blue */ 22 | #define BOLDMAGENTA "\033[1m\033[35m" /* Bold Magenta */ 23 | #define BOLDCYAN "\033[1m\033[36m" /* Bold Cyan */ 24 | #define BOLDWHITE "\033[1m\033[37m" /* Bold White */ 25 | 26 | #define CODE_BLOCK_INDENT 4 27 | 28 | static int num_places (int n) { 29 | if (n < 0) n = (n == INT_MIN) ? INT_MAX : -n; 30 | if (n < 10) return 1; 31 | if (n < 100) return 2; 32 | if (n < 1000) return 3; 33 | if (n < 10000) return 4; 34 | if (n < 100000) return 5; 35 | if (n < 1000000) return 6; 36 | if (n < 10000000) return 7; 37 | if (n < 100000000) return 8; 38 | if (n < 1000000000) return 9; 39 | /* 2147483647 is 2^31-1 - add more ifs as needed 40 | and adjust this final return as well. */ 41 | return 10; 42 | } 43 | 44 | static void error_file_info(State* state, pos_t* pos) { 45 | int line = pos->line + 1; 46 | int col = pos->column; 47 | fprintf(stderr, BOLDWHITE "%s" RESET ":%i:%i\n", state->filename, line, col); 48 | } 49 | 50 | void error_message(const char* msg) { 51 | fprintf(stderr, "\n " BOLDRED "𝒙" RESET RED " %s\n\n" RESET, msg); 52 | } 53 | 54 | static void print_code_line(str_builder_t *sb, size_t line, int max_spaces) { 55 | int line_spaces = num_places(line); 56 | int num_spaces = max_spaces - line_spaces + 1; 57 | 58 | char spaces[num_spaces + 1]; 59 | for(int i = 0; i < num_spaces; i++) { 60 | spaces[i] = ' '; 61 | } 62 | spaces[num_spaces] = '\0'; 63 | 64 | fprintf(stderr, BOLDWHITE " %zu" RESET "%s│ %s", line, spaces, str_builder_dump(sb, NULL)); 65 | } 66 | 67 | static void error_annotate(State* state, pos_t* pos) { 68 | char* source = state->source; 69 | size_t source_len = state->source_len; 70 | 71 | int problem_line = pos->line; 72 | int problem_col = pos->column; 73 | 74 | unsigned short start_line = problem_line > 2 ? (problem_line - 2) : 0; 75 | unsigned short end_line = problem_line + 2; 76 | unsigned int max_num_places = num_places(end_line); 77 | 78 | str_builder_t *sb = str_builder_create(); 79 | 80 | bool in_block = false; 81 | size_t i = 0; 82 | size_t line = 0; 83 | size_t col = 0; 84 | char c; 85 | while(i < source_len) { 86 | c = source[i]; 87 | 88 | if(line == start_line) { 89 | in_block = true; 90 | } 91 | 92 | if(in_block) { 93 | if(line > end_line) { 94 | in_block = false; 95 | break; 96 | } 97 | 98 | str_builder_add_char(sb, c); 99 | } 100 | 101 | if(c == '\n') { 102 | if(in_block) { 103 | print_code_line(sb, line + 1, max_num_places); 104 | str_builder_clear(sb); 105 | } 106 | 107 | if(line == problem_line) { 108 | unsigned short places = CODE_BLOCK_INDENT + max_num_places + 1 + problem_col + 1; 109 | char spaces[places + 1]; 110 | for(int pi = 0; pi < places; pi++) { 111 | spaces[pi] = ' '; 112 | } 113 | spaces[places] = '\0'; 114 | 115 | fprintf(stderr, "%s" BOLDRED "˄" RESET "\n", spaces); 116 | } 117 | 118 | line++; 119 | col = 0; 120 | } else { 121 | col++; 122 | } 123 | 124 | i++; 125 | } 126 | 127 | // Want at least 4 lines, so print this if not. 128 | if(line <= 3) { 129 | print_code_line(sb, line + 1, max_num_places); 130 | } 131 | 132 | fprintf(stderr, "\n"); 133 | 134 | str_builder_destroy(sb); 135 | } 136 | 137 | void error_msg_with_code_block(State* state, Node* node, const char* msg) { 138 | pos_t pos = { 139 | .line = state->line + 1, 140 | .column = state->column 141 | }; 142 | 143 | error_file_info(state, &pos); 144 | error_message(msg); 145 | error_annotate(state, &pos); 146 | fprintf(stderr, "\n"); 147 | } 148 | 149 | void error_msg_with_code_block_pos(State* state, pos_t* pos, const char* msg) { 150 | error_file_info(state, pos); 151 | error_message(msg); 152 | error_annotate(state, pos); 153 | fprintf(stderr, "\n"); 154 | } 155 | 156 | void error_msg_with_code_block_dec(State* state, int dec, const char* msg) { 157 | pos_t pos; 158 | state_find_position(state, &pos, dec); 159 | error_msg_with_code_block_pos(state, &pos, msg); 160 | } 161 | 162 | void error_unexpected_identifier(State* state, Node* node) { 163 | error_msg_with_code_block(state, node, "Unexpected identifier"); 164 | } -------------------------------------------------------------------------------- /src/core/js_builder.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "js_builder.h" 5 | #include "str_builder.h" 6 | 7 | JSBuilder* js_builder_create() { 8 | JSBuilder* jsb = malloc(sizeof(*jsb)); 9 | jsb->indent_str = " "; 10 | jsb->indent_len = strlen(jsb->indent_str); 11 | jsb->sb = str_builder_create(); 12 | jsb->ib = str_builder_create(); 13 | 14 | return jsb; 15 | } 16 | 17 | void js_builder_destroy(JSBuilder* jsb) { 18 | str_builder_destroy(jsb->sb); 19 | str_builder_destroy(jsb->ib); 20 | free(jsb); 21 | } 22 | 23 | static bool current_is_newline(JSBuilder* jsb) { 24 | int len = str_builder_len(jsb->sb); 25 | char c = str_builder_char_at(jsb->sb, len - 1); 26 | if(c == '\n') { 27 | return true; 28 | } 29 | return false; 30 | } 31 | 32 | void js_builder_add_char(JSBuilder* jsb, char c) { 33 | str_builder_add_char(jsb->sb, c); 34 | } 35 | 36 | void js_builder_add_str(JSBuilder* jsb, char* str) { 37 | str_builder_add_str(jsb->sb, str, 0); 38 | } 39 | 40 | void js_builder_copy_str(JSBuilder* jsb, char* str, size_t start, size_t end) { 41 | str_builder_copy_str(jsb->sb, str, start, end); 42 | } 43 | 44 | void js_builder_safe_key(JSBuilder* jsb, char* str) { 45 | // TODO make sure this string is a valid key 46 | js_builder_add_str(jsb, str); 47 | } 48 | 49 | void js_builder_add_string(JSBuilder* jsb, char* value) { 50 | js_builder_add_str(jsb, "'"); 51 | js_builder_add_str(jsb, value); 52 | js_builder_add_str(jsb, "'"); 53 | } 54 | 55 | void js_builder_copy_string(JSBuilder* jsb, char* input, size_t start, size_t end) { 56 | js_builder_add_str(jsb, "'"); 57 | js_builder_copy_str(jsb, input, start, end); 58 | js_builder_add_str(jsb, "'"); 59 | } 60 | 61 | void js_builder_add_indent(JSBuilder* jsb) { 62 | char* indent = str_builder_dump(jsb->ib, NULL); 63 | str_builder_add_str(jsb->sb, indent, 0); 64 | } 65 | 66 | void js_builder_increase_indent(JSBuilder* jsb) { 67 | str_builder_add_str(jsb->ib, jsb->indent_str, 0); 68 | } 69 | 70 | void js_builder_decrease_indent(JSBuilder* jsb) { 71 | size_t new_size = str_builder_len(jsb->ib) - jsb->indent_len; 72 | str_builder_truncate(jsb->ib, new_size); 73 | } 74 | 75 | void js_builder_start_object(JSBuilder* jsb) { 76 | js_builder_add_str(jsb, "{\n"); 77 | js_builder_increase_indent(jsb); 78 | } 79 | 80 | void js_builder_end_object(JSBuilder* jsb) { 81 | js_builder_add_str(jsb, "\n"); 82 | js_builder_decrease_indent(jsb); 83 | js_builder_add_indent(jsb); 84 | js_builder_add_str(jsb, "}"); 85 | } 86 | 87 | void js_builder_start_prop(JSBuilder* jsb, char* key) { 88 | int len = str_builder_len(jsb->sb); 89 | char c = str_builder_char_at(jsb->sb, len - 2); 90 | 91 | if(c != '{') { 92 | js_builder_add_str(jsb, ",\n"); 93 | } 94 | 95 | // TODO Quote if necessary 96 | js_builder_add_indent(jsb); 97 | js_builder_add_str(jsb, key); 98 | js_builder_add_str(jsb, ": "); 99 | } 100 | 101 | void js_builder_start_prop_no_copy(JSBuilder* jsb, char* key, size_t start, size_t end) { 102 | int len = str_builder_len(jsb->sb); 103 | char c = str_builder_char_at(jsb->sb, len - 2); 104 | 105 | if(c != '{') { 106 | js_builder_add_str(jsb, ",\n"); 107 | } 108 | 109 | // TODO Quote if necessary 110 | js_builder_add_indent(jsb); 111 | js_builder_copy_str(jsb, key, start, end); 112 | js_builder_add_str(jsb, ": "); 113 | } 114 | 115 | void js_builder_shorthand_prop(JSBuilder* jsb, char* key) { 116 | int len = str_builder_len(jsb->sb); 117 | char c = str_builder_char_at(jsb->sb, len - 2); 118 | 119 | if(c != '{') { 120 | js_builder_add_str(jsb, ",\n"); 121 | } 122 | 123 | // TODO Quote if necessary 124 | js_builder_add_indent(jsb); 125 | js_builder_add_str(jsb, key); 126 | } 127 | 128 | void js_builder_start_call(JSBuilder* jsb, char* name) { 129 | int len = str_builder_len(jsb->sb); 130 | char c = str_builder_char_at(jsb->sb, len - 1); 131 | if(c == '\n') { 132 | js_builder_add_indent(jsb); 133 | } 134 | 135 | js_builder_add_str(jsb, name); 136 | js_builder_add_str(jsb, "("); 137 | } 138 | 139 | void js_builder_end_call(JSBuilder* jsb) { 140 | js_builder_add_str(jsb, ")"); 141 | } 142 | 143 | void js_builder_start_array(JSBuilder* jsb, bool newline) { 144 | js_builder_add_str(jsb, "["); 145 | if(newline) { 146 | js_builder_add_str(jsb, "\n"); 147 | js_builder_increase_indent(jsb); 148 | } 149 | } 150 | 151 | void js_builder_end_array(JSBuilder* jsb, bool newline) { 152 | if(newline) { 153 | js_builder_add_str(jsb, "\n"); 154 | js_builder_decrease_indent(jsb); 155 | js_builder_add_indent(jsb); 156 | } 157 | js_builder_add_str(jsb, "]"); 158 | } 159 | 160 | void js_builder_add_export(JSBuilder* jsb) { 161 | if(!current_is_newline(jsb)) { 162 | js_builder_add_str(jsb, "\n"); 163 | } 164 | js_builder_add_str(jsb, "\nexport "); 165 | } 166 | 167 | void js_builder_add_const(JSBuilder* jsb, char* identifier) { 168 | js_builder_add_str(jsb, "const "); 169 | js_builder_add_str(jsb, identifier); 170 | } 171 | 172 | void js_builder_add_arg(JSBuilder* jsb, char* identifier) { 173 | int len = str_builder_len(jsb->sb); 174 | char c = str_builder_char_at(jsb->sb, len - 1); 175 | 176 | // Add a comma if we need to 177 | if(c != ' ' && c != '(') { 178 | js_builder_add_str(jsb, ", "); 179 | } 180 | 181 | js_builder_add_str(jsb, identifier); 182 | } 183 | 184 | char* js_builder_dump(JSBuilder* jsb) { 185 | return str_builder_dump(jsb->sb, NULL); 186 | } -------------------------------------------------------------------------------- /src/core/xstate/core.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "../dict.h" 5 | #include "../js_builder.h" 6 | #include "../node.h" 7 | #include "../set.h" 8 | #include "core.h" 9 | #include "machine.h" 10 | 11 | typedef void (*set_ref)(PrintState*, Ref*); 12 | 13 | static void destroy_ref(Ref* ref) { 14 | if(ref->next != NULL) { 15 | destroy_ref(ref->next); 16 | } 17 | if(ref->key != NULL) { 18 | free(ref->key); 19 | } 20 | if(ref->value != NULL) { 21 | node_destroy_expression(ref->value); 22 | } 23 | free(ref); 24 | } 25 | 26 | void xs_destroy_state_refs(PrintState *state) { 27 | if(state->guard != NULL) { 28 | destroy_ref(state->guard); 29 | state->guard = NULL; 30 | } 31 | if(state->action != NULL) { 32 | destroy_ref(state->action); 33 | state->action = NULL; 34 | } 35 | if(state->delay != NULL) { 36 | destroy_ref(state->delay); 37 | state->delay = NULL; 38 | } 39 | } 40 | 41 | static void add_ref(PrintState* state, char* key, Expression* value, Ref* head, SimpleSet* set, set_ref setter) { 42 | // If already added, don't do so again. 43 | if(set_contains(set, key) == SET_TRUE) { 44 | return; 45 | } 46 | 47 | Ref *ref = malloc(sizeof(Ref)); 48 | ref->key = strdup(key); 49 | ref->value = node_clone_expression(value); 50 | ref->next = NULL; 51 | 52 | if(head == NULL) { 53 | setter(state, ref); 54 | } else { 55 | Ref* cur = head; 56 | while(cur->next != NULL) { 57 | cur = cur->next; 58 | } 59 | cur->next = ref; 60 | } 61 | set_add(set, key); 62 | } 63 | 64 | static void set_action_ref(PrintState* state, Ref* ref) { 65 | state->action = ref; 66 | } 67 | 68 | void xs_add_action_ref(PrintState* state, char* key, Expression* value) { 69 | add_ref(state, key, value, state->action, state->action_names, *set_action_ref); 70 | } 71 | 72 | static void set_guard_ref(PrintState* state, Ref* ref) { 73 | state->guard = ref; 74 | } 75 | 76 | void xs_add_guard_ref(PrintState* state, char* key, Expression* value) { 77 | add_ref(state, key, value, state->guard, state->guard_names, *set_guard_ref); 78 | } 79 | 80 | static void set_delay_ref(PrintState* state, Ref* ref) { 81 | state->delay = ref; 82 | } 83 | 84 | void xs_add_delay_ref(PrintState* state, char* key, Expression* value) { 85 | add_ref(state, key, value, state->delay, state->delay_names, *set_delay_ref); 86 | } 87 | 88 | static void set_service_ref(PrintState* state, Ref* ref) { 89 | state->service = ref; 90 | } 91 | 92 | void xs_add_service_ref(PrintState* state, char* key, Expression* value) { 93 | add_ref(state, key, value, state->service, state->service_names, *set_service_ref); 94 | } 95 | 96 | void xs_start_assign_call(JSBuilder* jsb, AssignExpression* assign_expression) { 97 | js_builder_start_call(jsb, "assign"); 98 | js_builder_start_object(jsb); 99 | js_builder_start_prop(jsb, assign_expression->key); 100 | } 101 | 102 | void xs_end_assign_call(JSBuilder* jsb) { 103 | js_builder_end_object(jsb); 104 | js_builder_end_call(jsb); 105 | } 106 | 107 | char* xs_copy_str_from_source(PrintState* state, size_t start, size_t end) { 108 | size_t len = end - start; 109 | char* out = malloc(len + sizeof(char)); 110 | size_t i = start; 111 | while(i < end) { 112 | out[i - start] = state->source[i]; 113 | i++; 114 | } 115 | out[i - start] = '\0'; 116 | return out; 117 | } 118 | 119 | bool xs_find_and_add_top_level_machine_name(PrintState* state, JSBuilder* jsb, char* matching_name) { 120 | unsigned long searchid = hash_function(matching_name); 121 | Node* cur = state->program->body; 122 | while(cur != NULL) { 123 | if(cur->type == NODE_MACHINE_TYPE) { 124 | MachineNode* cur_machine = (MachineNode*)cur; 125 | if(cur_machine->name_start != 0) { 126 | char* machine_name = xs_copy_str_from_source(state, cur_machine->name_start, cur_machine->name_end); 127 | if(hash_function(machine_name) == searchid) { 128 | xs_add_machine_binding_name(state, jsb, cur_machine); 129 | free(machine_name); 130 | return true; 131 | } 132 | free(machine_name); 133 | } 134 | } 135 | cur = cur->next; 136 | } 137 | return false; 138 | } 139 | 140 | void xs_add_spawn_call(PrintState* state, JSBuilder* jsb, SpawnExpression* spawn_expression) { 141 | js_builder_add_str(jsb, "() => "); 142 | js_builder_start_call(jsb, "spawn"); 143 | Expression* target = spawn_expression->target; 144 | char* name = NULL; 145 | if(target->type == EXPRESSION_IDENTIFIER) { 146 | name = ((IdentifierExpression*)target)->name; 147 | if(!xs_find_and_add_top_level_machine_name(state, jsb, name)) { 148 | js_builder_add_str(jsb, name); 149 | } 150 | } else { 151 | name = ((SymbolExpression*)target)->name; 152 | js_builder_add_str(jsb, "services."); 153 | js_builder_add_str(jsb, name); 154 | } 155 | js_builder_add_str(jsb, ", "); 156 | js_builder_add_string(jsb, name); 157 | js_builder_end_call(jsb); 158 | } 159 | 160 | void xs_add_send_call(JSBuilder* jsb, SendExpression* send_expression) { 161 | js_builder_start_call(jsb, "send"); 162 | js_builder_add_str(jsb, "(ctx, ev) => ({ type: "); 163 | js_builder_add_string(jsb, send_expression->event); 164 | js_builder_add_str(jsb, ", data: ev.data })"); 165 | js_builder_add_str(jsb, ", "); 166 | js_builder_start_object(jsb); 167 | js_builder_start_prop(jsb, "to"); 168 | js_builder_add_str(jsb, "(context) => context."); 169 | js_builder_add_str(jsb, send_expression->actor); 170 | js_builder_end_object(jsb); 171 | js_builder_end_call(jsb); 172 | } -------------------------------------------------------------------------------- /src/core/node/expression.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "../node.h" 4 | 5 | void node_destroy_expression(Expression*); 6 | 7 | /* Creation */ 8 | AssignExpression* node_create_assignexpression() { 9 | AssignExpression* expression = malloc(sizeof *expression); 10 | ((Expression*)expression)->type = EXPRESSION_ASSIGN; 11 | expression->value = NULL; 12 | expression->key = NULL; 13 | return expression; 14 | } 15 | 16 | IdentifierExpression* node_create_identifierexpression() { 17 | IdentifierExpression* expression = malloc(sizeof *expression); 18 | ((Expression*)expression)->type = EXPRESSION_IDENTIFIER; 19 | return expression; 20 | } 21 | 22 | GuardExpression* node_create_guardexpression() { 23 | GuardExpression* expression = malloc(sizeof *expression); 24 | ((Expression*)expression)->type = EXPRESSION_GUARD; 25 | expression->ref = NULL; 26 | return expression; 27 | } 28 | 29 | ActionExpression* node_create_actionexpression() { 30 | ActionExpression* expression = malloc(sizeof *expression); 31 | ((Expression*)expression)->type = EXPRESSION_ACTION; 32 | expression->ref = NULL; 33 | return expression; 34 | } 35 | 36 | DelayExpression* node_create_delayexpression() { 37 | DelayExpression* expression = malloc(sizeof *expression); 38 | ((Expression*)expression)->type = EXPRESSION_DELAY; 39 | expression->ref = NULL; 40 | expression->time = 0; 41 | return expression; 42 | } 43 | 44 | OnExpression* node_create_onexpression() { 45 | OnExpression* expression = malloc(sizeof *expression); 46 | ((Expression*)expression)->type = EXPRESSION_ON; 47 | expression->name = NULL; 48 | return expression; 49 | } 50 | 51 | SpawnExpression* node_create_spawnexpression() { 52 | SpawnExpression* expression = malloc(sizeof *expression); 53 | ((Expression*)expression)->type = EXPRESSION_SPAWN; 54 | expression->target = NULL; 55 | return expression; 56 | } 57 | 58 | SendExpression* node_create_sendexpression() { 59 | SendExpression* expression = malloc(sizeof* expression); 60 | ((Expression*) expression)->type = EXPRESSION_SEND; 61 | expression->actor = NULL; 62 | expression->event = NULL; 63 | return expression; 64 | } 65 | 66 | SymbolExpression* node_create_symbolexpression() { 67 | SymbolExpression* expression = malloc(sizeof *expression); 68 | ((Expression*)expression)->type = EXPRESSION_SYMBOL; 69 | expression->name = NULL; 70 | return expression; 71 | } 72 | 73 | InvokeExpression* node_create_invokeexpression() { 74 | InvokeExpression* expression = malloc(sizeof *expression); 75 | ((Expression*)expression)->type = EXPRESSION_INVOKE; 76 | expression->ref = NULL; 77 | return expression; 78 | } 79 | 80 | MemberExpression* node_create_memberexpression() { 81 | MemberExpression* expression = malloc(sizeof *expression); 82 | ((Expression*)expression)->type = EXPRESSION_MEMBER; 83 | expression->owner = NULL; 84 | expression->property = NULL; 85 | return expression; 86 | } 87 | 88 | /* Teardown */ 89 | void node_destroy_assignexpression(AssignExpression* expression) { 90 | if(expression != NULL) { 91 | if(expression->value != NULL) { 92 | node_destroy_expression(expression->value); 93 | } 94 | if(expression->key != NULL) { 95 | free(expression->key); 96 | } 97 | } 98 | } 99 | 100 | void node_destroy_identifierexpression(IdentifierExpression* expression) { 101 | if(expression != NULL) { 102 | free(expression->name); 103 | } 104 | } 105 | 106 | void node_destroy_guardexpression(GuardExpression* expression) { 107 | if(expression != NULL) { 108 | node_destroy_expression(expression->ref); 109 | } 110 | } 111 | 112 | void node_destroy_actionexpression(ActionExpression* expression) { 113 | if(expression != NULL) { 114 | node_destroy_expression(expression->ref); 115 | } 116 | } 117 | 118 | void node_destroy_delayexpression(DelayExpression* expression) { 119 | if(expression != NULL) { 120 | if(expression->ref != NULL) { 121 | node_destroy_expression(expression->ref); 122 | } 123 | } 124 | } 125 | 126 | void node_destroy_onexpression(OnExpression* expression) { 127 | if(expression != NULL) { 128 | free(expression->name); 129 | } 130 | } 131 | 132 | void node_destroy_spawnexpression(SpawnExpression* expression) { 133 | if(expression != NULL) { 134 | node_destroy_expression(expression->target); 135 | } 136 | } 137 | 138 | void node_destroy_sendexpression(SendExpression* expression) { 139 | if(expression != NULL) { 140 | free(expression->actor); 141 | free(expression->event); 142 | } 143 | } 144 | 145 | void node_destroy_symbolexpression(SymbolExpression* expression) { 146 | if(expression != NULL) { 147 | free(expression->name); 148 | } 149 | } 150 | 151 | void node_destroy_invokeexpression(InvokeExpression* expression) { 152 | if(expression != NULL) { 153 | if(expression->ref != NULL) { 154 | node_destroy_expression(expression->ref); 155 | } 156 | } 157 | } 158 | 159 | void node_destroy_expression(Expression* expression) { 160 | switch(expression->type) { 161 | case EXPRESSION_ASSIGN: { 162 | AssignExpression *assign_expression = (AssignExpression*)expression; 163 | node_destroy_assignexpression(assign_expression); 164 | break; 165 | } 166 | case EXPRESSION_IDENTIFIER: { 167 | IdentifierExpression *identifier_expression = (IdentifierExpression*)expression; 168 | node_destroy_identifierexpression(identifier_expression); 169 | break; 170 | } 171 | case EXPRESSION_GUARD: { 172 | node_destroy_guardexpression((GuardExpression*)expression); 173 | break; 174 | } 175 | case EXPRESSION_ACTION: { 176 | node_destroy_actionexpression((ActionExpression*)expression); 177 | break; 178 | } 179 | case EXPRESSION_ON: { 180 | node_destroy_onexpression((OnExpression*)expression); 181 | break; 182 | } 183 | case EXPRESSION_SPAWN: { 184 | node_destroy_spawnexpression((SpawnExpression*)expression); 185 | break; 186 | } 187 | case EXPRESSION_SEND: { 188 | node_destroy_sendexpression((SendExpression*)expression); 189 | break; 190 | } 191 | case EXPRESSION_SYMBOL: { 192 | node_destroy_symbolexpression((SymbolExpression*)expression); 193 | break; 194 | } 195 | case EXPRESSION_INVOKE: { 196 | node_destroy_invokeexpression((InvokeExpression*)expression); 197 | break; 198 | } 199 | } 200 | free(expression); 201 | } -------------------------------------------------------------------------------- /src/core/xstate/compiler.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "../parser/parser.h" 5 | #include "../program.h" 6 | #include "assignment.h" 7 | #include "compiler.h" 8 | #include "core.h" 9 | #include "import.h" 10 | #include "machine.h" 11 | #include "state.h" 12 | #include "ts_printer.h" 13 | 14 | static void destroy_state(PrintState *state) { 15 | xs_destroy_state_refs(state); 16 | set_destroy(state->events); 17 | set_destroy(state->action_names); 18 | set_destroy(state->guard_names); 19 | set_destroy(state->delay_names); 20 | set_destroy(state->service_names); 21 | } 22 | 23 | CompileResult* xs_create() { 24 | CompileResult* result = malloc(sizeof(*result)); 25 | return result; 26 | } 27 | 28 | void xs_init(CompileResult* result, bool use_remote_source, bool include_dts) { 29 | result->success = false; 30 | result->js = NULL; 31 | result->dts = NULL; 32 | result->flags = 0; 33 | 34 | if(use_remote_source) { 35 | result->flags |= XS_FLAG_USE_REMOTE; 36 | } 37 | if(include_dts) { 38 | result->flags |= XS_FLAG_DTS; 39 | } 40 | } 41 | 42 | void compile_xstate(CompileResult* result, char* source, char* filename) { 43 | ParseResult *parse_result = parse(source, filename); 44 | 45 | if(parse_result->success == false) { 46 | result->success = false; 47 | result->js = NULL; 48 | return; 49 | } 50 | 51 | Program *program = parse_result->program; 52 | char* xstate_specifier; 53 | if(result->flags & XS_FLAG_USE_REMOTE) { 54 | xstate_specifier = "https://cdn.skypack.dev/xstate"; 55 | } else { 56 | xstate_specifier = "xstate"; 57 | } 58 | 59 | JSBuilder *jsb; 60 | jsb = js_builder_create(); 61 | 62 | PrintState state = { 63 | .flags = result->flags, 64 | .source = source, 65 | .program = program, 66 | .guard = NULL, 67 | .action = NULL, 68 | .delay = NULL, 69 | .events = malloc(sizeof(SimpleSet)), 70 | .guard_names = malloc(sizeof(SimpleSet)), 71 | .action_names = malloc(sizeof(SimpleSet)), 72 | .delay_names = malloc(sizeof(SimpleSet)), 73 | .service_names = malloc(sizeof(SimpleSet)), 74 | .tsprinter = NULL, 75 | .cur_event_name = NULL, 76 | .cur_state_start = 0, 77 | .cur_state_end = 0, 78 | .in_entry = false 79 | }; 80 | set_init(state.events); 81 | set_init(state.guard_names); 82 | set_init(state.action_names); 83 | set_init(state.delay_names); 84 | set_init(state.service_names); 85 | 86 | if(state.flags & XS_FLAG_DTS) { 87 | state.tsprinter = ts_printer_alloc(); 88 | ts_printer_init(state.tsprinter); 89 | } 90 | 91 | Node* node; 92 | node = program->body; 93 | 94 | if(node != NULL) { 95 | js_builder_add_str(jsb, "import { createMachine"); 96 | 97 | if(program->flags & PROGRAM_USES_ASSIGN) { 98 | js_builder_add_str(jsb, ", assign"); 99 | } 100 | if(program->flags & PROGRAM_USES_SEND) { 101 | js_builder_add_str(jsb, ", send"); 102 | } 103 | if(program->flags & PROGRAM_USES_SPAWN) { 104 | js_builder_add_str(jsb, ", spawn"); 105 | } 106 | 107 | js_builder_add_str(jsb, " } from '"); 108 | 109 | js_builder_add_str(jsb, xstate_specifier); 110 | js_builder_add_str(jsb, "';\n"); 111 | if(result->flags & XS_FLAG_DTS) { 112 | state.tsprinter->xstate_specifier = xstate_specifier; 113 | } 114 | } 115 | 116 | bool exit = false; 117 | while(node != NULL) { 118 | unsigned short type = node->type; 119 | 120 | if(exit) { 121 | switch(type) { 122 | case NODE_STATE_TYPE: { 123 | xs_exit_state(&state, jsb, node); 124 | node_destroy_state((StateNode*)node); 125 | break; 126 | } 127 | case NODE_TRANSITION_TYPE: { 128 | node_destroy_transition((TransitionNode*)node); 129 | break; 130 | } 131 | case NODE_ASSIGNMENT_TYPE: { 132 | node_destroy_assignment((Assignment*)node); 133 | break; 134 | } 135 | case NODE_IMPORT_SPECIFIER_TYPE: { 136 | node_destroy_import_specifier((ImportSpecifier*)node); 137 | break; 138 | } 139 | case NODE_IMPORT_TYPE: { 140 | node_destroy_import((ImportNode*)node); 141 | break; 142 | } 143 | case NODE_MACHINE_TYPE: { 144 | xs_exit_machine(&state, jsb, node); 145 | node_destroy_machine((MachineNode*)node); 146 | break; 147 | } 148 | case NODE_INVOKE_TYPE: { 149 | node_destroy_invoke((InvokeNode*)node); 150 | break; 151 | } 152 | default: { 153 | printf("Node type %hu not torn down (this is a compiler bug)\n", type); 154 | break; 155 | } 156 | } 157 | } else { 158 | switch(type) { 159 | case NODE_IMPORT_TYPE: { 160 | xs_enter_import(&state, jsb, node); 161 | break; 162 | } 163 | case NODE_STATE_TYPE: { 164 | xs_enter_state(&state, jsb, node); 165 | break; 166 | } 167 | case NODE_TRANSITION_TYPE: { 168 | break; 169 | } 170 | case NODE_ASSIGNMENT_TYPE: { 171 | xs_enter_assignment(&state, jsb, node); 172 | break; 173 | } 174 | case NODE_INVOKE_TYPE: { 175 | break; 176 | } 177 | case NODE_LOCAL_TYPE: { 178 | // Do not walk down local nodes. 179 | exit = true; 180 | node_destroy_local((LocalNode*)node); 181 | break; 182 | } 183 | case NODE_MACHINE_TYPE: { 184 | xs_enter_machine(&state, jsb, node); 185 | break; 186 | } 187 | } 188 | } 189 | 190 | // Node has no children, so go again for the exit. 191 | if(!exit && !node->child) { 192 | exit = true; 193 | continue; 194 | } 195 | // Node has a child 196 | else if(!exit && node->child) { 197 | node = node->child; 198 | } else if(node->next) { 199 | exit = false; 200 | node = node->next; 201 | } else if(node->parent) { 202 | exit = true; 203 | node_destroy(node); 204 | node = node->parent; 205 | } else { 206 | // Reached the end. 207 | node_destroy(node); 208 | break; 209 | } 210 | } 211 | 212 | char* js = js_builder_dump(jsb); 213 | result->success = true; 214 | result->js = js; 215 | 216 | if(result->flags & XS_FLAG_DTS) { 217 | char* dts = ts_printer_dump(state.tsprinter); 218 | result->dts = dts; 219 | ts_printer_destroy(state.tsprinter); 220 | } 221 | 222 | // Teardown 223 | destroy_state(&state); 224 | js_builder_destroy(jsb); 225 | 226 | return; 227 | } 228 | 229 | char* xs_get_js(CompileResult* result) { 230 | return result->js; 231 | } 232 | 233 | char* xs_get_dts(CompileResult* result) { 234 | return result->dts; 235 | } 236 | 237 | void destroy_xstate_result(CompileResult* result) { 238 | if(result->js != NULL) { 239 | free(result->js); 240 | } 241 | if(result->dts != NULL) { 242 | free(result->dts); 243 | } 244 | free(result); 245 | } -------------------------------------------------------------------------------- /src/core/node.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #define NODE_MACHINE_TYPE 0 8 | #define NODE_STATE_TYPE 1 9 | #define NODE_TRANSITION_TYPE 2 10 | #define NODE_IMPORT_TYPE 3 11 | #define NODE_IMPORT_SPECIFIER_TYPE 4 12 | #define NODE_ASSIGNMENT_TYPE 5 13 | #define NODE_INVOKE_TYPE 6 14 | #define NODE_LOCAL_TYPE 7 15 | 16 | #define TRANSITION_EVENT_TYPE 0 17 | #define TRANSITION_IMMEDIATE_TYPE 1 18 | #define TRANSITION_DELAY_TYPE 2 19 | 20 | #define ASSIGNMENT_ACTION 0 21 | #define ASSIGNMENT_GUARD 1 22 | 23 | #define EXPRESSION_ASSIGN 0 24 | #define EXPRESSION_IDENTIFIER 1 25 | #define EXPRESSION_GUARD 2 26 | #define EXPRESSION_ACTION 3 27 | #define EXPRESSION_DELAY 4 28 | #define EXPRESSION_ON 5 29 | #define EXPRESSION_SPAWN 6 30 | #define EXPRESSION_SEND 7 31 | #define EXPRESSION_SYMBOL 8 32 | #define EXPRESSION_INVOKE 9 33 | #define EXPRESSION_MEMBER 10 34 | 35 | #define MACHINE_USES_GUARD 1 << 0 36 | #define MACHINE_USES_ACTION 1 << 1 37 | #define MACHINE_USES_ASSIGN 1 << 2 38 | #define MACHINE_USES_DELAY 1 << 3 39 | #define MACHINE_USES_SERVICE 1 << 4 40 | 41 | typedef struct Node { 42 | unsigned short type; 43 | size_t start; 44 | size_t end; 45 | unsigned short line; 46 | struct Node* parent; 47 | struct Node* child; 48 | struct Node* next; 49 | } Node; 50 | 51 | typedef struct MachineNode { 52 | Node node; 53 | 54 | int impl_flags; 55 | int flags; 56 | size_t name_start; 57 | size_t name_end; 58 | size_t initial_start; 59 | size_t initial_end; 60 | } MachineNode; 61 | 62 | typedef struct StateNode { 63 | Node node; 64 | int name_start; 65 | int name_end; 66 | 67 | bool final; 68 | struct LocalNode* entry; 69 | struct LocalNode* exit; 70 | struct TransitionNode* event_transition; 71 | struct TransitionNode* immediate_transition; 72 | struct TransitionNode* delay_transition; 73 | struct InvokeNode* invoke; 74 | } StateNode; 75 | 76 | typedef struct TransitionGuard { 77 | char* name; 78 | struct TransitionGuard* next; 79 | struct GuardExpression* expression; 80 | } TransitionGuard; 81 | 82 | typedef struct TransitionAction { 83 | char* name; 84 | struct TransitionAction* next; 85 | struct Expression* expression; 86 | } TransitionAction; 87 | 88 | typedef struct TransitionDelay { 89 | char* ref; 90 | struct DelayExpression* expression; 91 | } TransitionDelay; 92 | 93 | typedef struct TransitionNode { 94 | Node node; 95 | 96 | unsigned short type; 97 | struct Expression* event; 98 | TransitionDelay* delay; 99 | 100 | struct Expression* dest; 101 | TransitionAction* action; 102 | TransitionGuard* guard; 103 | struct TransitionNode* next; 104 | struct TransitionNode* link; 105 | } TransitionNode; 106 | 107 | typedef struct LocalNode { 108 | Node node; 109 | unsigned short key; 110 | TransitionAction* action; 111 | } LocalNode; 112 | 113 | typedef struct ImportSpecifier { 114 | Node node; 115 | 116 | char* imported; 117 | char* local; 118 | } ImportSpecifier; 119 | 120 | typedef struct ImportNode { 121 | Node node; 122 | 123 | char* from; 124 | } ImportNode; 125 | 126 | typedef struct InvokeNode { 127 | Node node; 128 | struct InvokeExpression* expr; 129 | struct TransitionNode* event_transition; 130 | } InvokeNode; 131 | 132 | typedef struct Expression { 133 | unsigned short type; 134 | } Expression; 135 | 136 | typedef struct IdentifierExpression { 137 | Expression expression; 138 | char* name; 139 | } IdentifierExpression; 140 | 141 | typedef struct AssignExpression { 142 | Expression expression; 143 | char* key; 144 | Expression* value; 145 | } AssignExpression; 146 | 147 | typedef struct GuardExpression { 148 | Expression expression; 149 | Expression* ref; 150 | } GuardExpression; 151 | 152 | typedef struct ActionExpression { 153 | Expression expression; 154 | Expression* ref; 155 | } ActionExpression; 156 | 157 | typedef struct DelayExpression { 158 | Expression expression; 159 | int time; 160 | Expression* ref; 161 | } DelayExpression; 162 | 163 | typedef struct OnExpression { 164 | Expression expression; 165 | char* name; 166 | } OnExpression; 167 | 168 | typedef struct SpawnExpression { 169 | Expression expression; 170 | Expression* target; 171 | } SpawnExpression; 172 | 173 | typedef struct SendExpression { 174 | Expression expression; 175 | char* actor; 176 | char* event; 177 | } SendExpression; 178 | 179 | typedef struct SymbolExpression { 180 | Expression expression; 181 | char* name; 182 | } SymbolExpression; 183 | 184 | typedef struct InvokeExpression { 185 | Expression expression; 186 | Expression* ref; 187 | } InvokeExpression; 188 | 189 | typedef struct MemberExpression { 190 | Expression expression; 191 | char* owner; 192 | Expression* property; 193 | } MemberExpression; 194 | 195 | typedef struct Assignment { 196 | Node node; 197 | unsigned short binding_type; 198 | char* binding_name; 199 | Expression* value; 200 | } Assignment; 201 | 202 | Node* node_create_type(unsigned short, size_t); 203 | TransitionNode* node_create_transition(); 204 | MachineNode* node_create_machine(); 205 | StateNode* node_create_state(); 206 | ImportNode* node_create_import_statement(); 207 | ImportSpecifier* node_create_import_specifier(char*); 208 | Assignment* node_create_assignment(unsigned short); 209 | InvokeNode* node_create_invoke(); 210 | AssignExpression* node_create_assignexpression(); 211 | IdentifierExpression* node_create_identifierexpression(); 212 | SymbolExpression* node_create_symbolexpression(); 213 | GuardExpression* node_create_guardexpression(); 214 | ActionExpression* node_create_actionexpression(); 215 | DelayExpression* node_create_delayexpression(); 216 | OnExpression* node_create_onexpression(); 217 | SpawnExpression* node_create_spawnexpression(); 218 | SendExpression* node_create_sendexpression(); 219 | InvokeExpression* node_create_invokeexpression(); 220 | MemberExpression* node_create_memberexpression(); 221 | LocalNode* node_create_local(); 222 | 223 | TransitionAction* create_transition_action(); 224 | 225 | bool node_machine_is_nested(Node*); 226 | 227 | void node_append(Node*, Node*); 228 | void node_after_last(Node*, Node*); 229 | 230 | TransitionGuard* node_transition_add_guard(TransitionNode*, char*); 231 | TransitionAction* node_transition_add_action(TransitionNode*, char*); 232 | TransitionDelay* node_transition_add_delay(TransitionNode*, char*, DelayExpression*); 233 | void node_local_add_action(LocalNode*, TransitionAction*); 234 | 235 | Node* find_closest_node_of_type(Node*, int); 236 | MachineNode* find_closest_machine_node(Node*); 237 | Expression* node_clone_expression(Expression*); 238 | 239 | void node_destroy_assignment(Assignment*); 240 | void node_destroy_import(ImportNode*); 241 | void node_destroy_import_specifier(ImportSpecifier*); 242 | void node_destroy_machine(MachineNode*); 243 | void node_destroy_state(StateNode*); 244 | void node_destroy_transition(TransitionNode*); 245 | void node_destroy_expression(Expression*); 246 | void node_destroy_invoke(InvokeNode*); 247 | void node_destroy_local(LocalNode*); 248 | void node_destroy(Node*); -------------------------------------------------------------------------------- /src/core/set.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /******************************************************************************* 4 | *** 5 | *** Author: Tyler Barrus 6 | *** email: barrust@gmail.com 7 | *** 8 | *** Version: 0.2.0 9 | *** Purpose: Simple, yet effective, set implementation 10 | *** 11 | *** License: MIT 2016 12 | *** 13 | *** URL: https://github.com/barrust/set 14 | *** 15 | *******************************************************************************/ 16 | 17 | #ifdef __cplusplus 18 | extern "C" { 19 | #endif 20 | 21 | #include /* uint64_t */ 22 | 23 | 24 | /* https://gcc.gnu.org/onlinedocs/gcc/Alternate-Keywords.html#Alternate-Keywords */ 25 | #ifndef __GNUC__ 26 | #define __inline__ inline 27 | #endif 28 | 29 | typedef uint64_t (*set_hash_function) (const char *key); 30 | 31 | typedef struct { 32 | char* _key; 33 | uint64_t _hash; 34 | } SimpleSetNode, simple_set_node; 35 | 36 | typedef struct { 37 | simple_set_node **nodes; 38 | uint64_t number_nodes; 39 | uint64_t used_nodes; 40 | set_hash_function hash_function; 41 | } SimpleSet, simple_set; 42 | 43 | 44 | 45 | /* Initialize the set either with default parameters (hash function and space) 46 | or optionally set the set with specifed values 47 | 48 | Returns: 49 | SET_MALLOC_ERROR: If an error occured setting up the memory 50 | SET_TRUE: On success 51 | */ 52 | int set_init_alt(SimpleSet *set, uint64_t num_els, set_hash_function hash); 53 | static __inline__ int set_init(SimpleSet *set) { 54 | return set_init_alt(set, 1024, NULL); 55 | } 56 | 57 | /* Utility function to clear out the set */ 58 | int set_clear(SimpleSet *set); 59 | 60 | /* Free all memory that is part of the set */ 61 | int set_destroy(SimpleSet *set); 62 | 63 | /* Add element to set 64 | 65 | Returns: 66 | SET_TRUE if added 67 | SET_ALREADY_PRESENT if already present 68 | SET_CIRCULAR_ERROR if set is completely full 69 | SET_MALLOC_ERROR if unable to grow the set 70 | NOTE: SET_CIRCULAR_ERROR should never happen, but is there for insurance! 71 | */ 72 | int set_add(SimpleSet *set, const char *key); 73 | 74 | /* Remove element from the set 75 | 76 | Returns: 77 | SET_TRUE if removed 78 | SET_FALSE if not present 79 | */ 80 | int set_remove(SimpleSet *set, const char *key); 81 | 82 | /* Check if key in set 83 | 84 | Returns: 85 | SET_TRUE if present, 86 | SET_FALSE if not found 87 | SET_CIRCULAR_ERROR if set is full and not found 88 | NOTE: SET_CIRCULAR_ERROR should never happen, but is there for insurance! 89 | */ 90 | int set_contains(SimpleSet *set, const char *key); 91 | 92 | /* Return the number of elements in the set */ 93 | uint64_t set_length(SimpleSet *set); 94 | 95 | /* Set res to the union of s1 and s2 96 | res = s1 ∪ s2 97 | 98 | The union of a set A with a B is the set of elements that are in either 99 | set A or B. The union is denoted as A ∪ B 100 | */ 101 | int set_union(SimpleSet *res, SimpleSet *s1, SimpleSet *s2); 102 | 103 | /* Set res to the intersection of s1 and s2 104 | res = s1 ∩ s2 105 | 106 | The intersection of a set A with a B is the set of elements that are in 107 | both set A and B. The intersection is denoted as A ∩ B 108 | */ 109 | int set_intersection(SimpleSet *res, SimpleSet *s1, SimpleSet *s2); 110 | 111 | /* Set res to the difference between s1 and s2 112 | res = s1∖ s2 113 | 114 | The set difference between two sets A and B is written A ∖ B, and means 115 | the set that consists of the elements of A which are not elements 116 | of B: x ∈ A ∖ B ⟺ x ∈ A ∧ x ∉ B. Another frequently seen notation 117 | for S ∖ T is S − T. 118 | */ 119 | int set_difference(SimpleSet *res, SimpleSet *s1, SimpleSet *s2); 120 | 121 | /* Set res to the symmetric difference between s1 and s2 122 | res = s1 △ s2 123 | 124 | The symmetric difference of two sets A and B is the set of elements either 125 | in A or in B but not in both. Symmetric difference is denoted 126 | A △ B or A * B 127 | */ 128 | int set_symmetric_difference(SimpleSet *res, SimpleSet *s1, SimpleSet *s2); 129 | 130 | /* Return SET_TRUE if test is fully contained in s2; returns SET_FALSE 131 | otherwise 132 | test ⊆ against 133 | 134 | A set A is a subset of another set B if all elements of the set A are 135 | elements of the set B. In other words, the set A is contained inside 136 | the set B. The subset relationship is denoted as A ⊆ B 137 | */ 138 | int set_is_subset(SimpleSet *test, SimpleSet *against); 139 | 140 | /* Inverse of subset; return SET_TRUE if set test fully contains 141 | (including equal to) set against; return SET_FALSE otherwise 142 | test ⊇ against 143 | 144 | Superset Definition: A set A is a superset of another set B if all 145 | elements of the set B are elements of the set A. The superset 146 | relationship is denoted as A ⊇ B 147 | */ 148 | static __inline__ int set_is_superset(SimpleSet *test, SimpleSet *against) { 149 | return set_is_subset(against, test); 150 | } 151 | 152 | /* Strict subset ensures that the test is a subset of against, but that 153 | the two are also not equal. 154 | test ⊂ against 155 | 156 | Set A is a strict subset of another set B if all elements of the set A 157 | are elements of the set B. In other words, the set A is contained inside 158 | the set B. A ≠ B is required. The strict subset relationship is denoted 159 | as A ⊂ B 160 | */ 161 | int set_is_subset_strict(SimpleSet *test, SimpleSet *against); 162 | 163 | /* Strict superset ensures that the test is a superset of against, but that 164 | the two are also not equal. 165 | test ⊃ against 166 | 167 | Strict Superset Definition: A set A is a superset of another set B if 168 | all elements of the set B are elements of the set A. A ≠ B is required. 169 | The superset relationship is denoted as A ⊃ B 170 | */ 171 | static __inline__ int set_is_superset_strict(SimpleSet *test, SimpleSet *against) { 172 | return set_is_subset_strict(against, test); 173 | } 174 | 175 | /* Return an array of the elements in the set 176 | NOTE: Up to the caller to free the memory */ 177 | char** set_to_array(SimpleSet *set, uint64_t *size); 178 | 179 | /* Compare two sets for equality (size, keys same, etc) 180 | 181 | Returns: 182 | SET_RIGHT_GREATER if left is less than right 183 | SET_LEFT_GREATER if right is less than left 184 | SET_EQUAL if left is the same size as right and keys match 185 | SET_UNEQUAL if size is the same but elements are different 186 | */ 187 | int set_cmp(SimpleSet *left, SimpleSet *right); 188 | 189 | // void set_printf(SimpleSet *set); /* TODO: implement */ 190 | 191 | #define SET_TRUE 0 192 | #define SET_FALSE -1 193 | #define SET_MALLOC_ERROR -2 194 | #define SET_CIRCULAR_ERROR -3 195 | #define SET_OCCUPIED_ERROR -4 196 | #define SET_ALREADY_PRESENT 1 197 | 198 | #define SET_RIGHT_GREATER 3 199 | #define SET_LEFT_GREATER 1 200 | #define SET_EQUAL 0 201 | #define SET_UNEQUAL 2 202 | 203 | 204 | #ifdef __cplusplus 205 | } // extern "C" 206 | #endif -------------------------------------------------------------------------------- /src/core/ht.c: -------------------------------------------------------------------------------- 1 | /** 2 | MIT License 3 | 4 | Copyright (c) 2021 Ben Hoyt 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | // Simple hash table implemented in C. 26 | 27 | #include "ht.h" 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | // Hash table entry (slot may be filled or empty). 35 | typedef struct { 36 | const char* key; // key is NULL if this slot is empty 37 | void* value; 38 | } ht_entry; 39 | 40 | // Hash table structure: create with ht_create, free with ht_destroy. 41 | struct ht { 42 | ht_entry* entries; // hash slots 43 | size_t capacity; // size of _entries array 44 | size_t length; // number of items in hash table 45 | }; 46 | 47 | #define INITIAL_CAPACITY 16 // must not be zero 48 | 49 | ht* ht_create(void) { 50 | // Allocate space for hash table struct. 51 | ht* table = malloc(sizeof(ht)); 52 | if (table == NULL) { 53 | return NULL; 54 | } 55 | table->length = 0; 56 | table->capacity = INITIAL_CAPACITY; 57 | 58 | // Allocate (zero'd) space for entry buckets. 59 | table->entries = calloc(table->capacity, sizeof(ht_entry)); 60 | if (table->entries == NULL) { 61 | free(table); // error, free table before we return! 62 | return NULL; 63 | } 64 | return table; 65 | } 66 | 67 | void ht_destroy(ht* table) { 68 | // First free allocated keys. 69 | for (size_t i = 0; i < table->capacity; i++) { 70 | if (table->entries[i].key != NULL) { 71 | free((void*)table->entries[i].key); 72 | } 73 | } 74 | 75 | // Then free entries array and table itself. 76 | free(table->entries); 77 | free(table); 78 | } 79 | 80 | #define FNV_OFFSET 14695981039346656037UL 81 | #define FNV_PRIME 1099511628211UL 82 | 83 | // Return 64-bit FNV-1a hash for key (NUL-terminated). See description: 84 | // https://en.wikipedia.org/wiki/Fowler–Noll–Vo_hash_function 85 | static uint64_t hash_key(const char* key) { 86 | uint64_t hash = FNV_OFFSET; 87 | for (const char* p = key; *p; p++) { 88 | hash ^= (uint64_t)(unsigned char)(*p); 89 | hash *= FNV_PRIME; 90 | } 91 | return hash; 92 | } 93 | 94 | void* ht_get(ht* table, const char* key) { 95 | // AND hash with capacity-1 to ensure it's within entries array. 96 | uint64_t hash = hash_key(key); 97 | size_t index = (size_t)(hash & (uint64_t)(table->capacity - 1)); 98 | 99 | // Loop till we find an empty entry. 100 | while (table->entries[index].key != NULL) { 101 | if (strcmp(key, table->entries[index].key) == 0) { 102 | // Found key, return value. 103 | return table->entries[index].value; 104 | } 105 | // Key wasn't in this slot, move to next (linear probing). 106 | index++; 107 | if (index >= table->capacity) { 108 | // At end of entries array, wrap around. 109 | index = 0; 110 | } 111 | } 112 | return NULL; 113 | } 114 | 115 | // Internal function to set an entry (without expanding table). 116 | static const char* ht_set_entry(ht_entry* entries, size_t capacity, 117 | const char* key, void* value, size_t* plength) { 118 | // AND hash with capacity-1 to ensure it's within entries array. 119 | uint64_t hash = hash_key(key); 120 | size_t index = (size_t)(hash & (uint64_t)(capacity - 1)); 121 | 122 | // Loop till we find an empty entry. 123 | while (entries[index].key != NULL) { 124 | if (strcmp(key, entries[index].key) == 0) { 125 | // Found key (it already exists), update value. 126 | entries[index].value = value; 127 | return entries[index].key; 128 | } 129 | // Key wasn't in this slot, move to next (linear probing). 130 | index++; 131 | if (index >= capacity) { 132 | // At end of entries array, wrap around. 133 | index = 0; 134 | } 135 | } 136 | 137 | // Didn't find key, allocate+copy if needed, then insert it. 138 | if (plength != NULL) { 139 | key = strdup(key); 140 | if (key == NULL) { 141 | return NULL; 142 | } 143 | (*plength)++; 144 | } 145 | entries[index].key = (char*)key; 146 | entries[index].value = value; 147 | return key; 148 | } 149 | 150 | // Expand hash table to twice its current size. Return true on success, 151 | // false if out of memory. 152 | static bool ht_expand(ht* table) { 153 | // Allocate new entries array. 154 | size_t new_capacity = table->capacity * 2; 155 | if (new_capacity < table->capacity) { 156 | return false; // overflow (capacity would be too big) 157 | } 158 | ht_entry* new_entries = calloc(new_capacity, sizeof(ht_entry)); 159 | if (new_entries == NULL) { 160 | return false; 161 | } 162 | 163 | // Iterate entries, move all non-empty ones to new table's entries. 164 | for (size_t i = 0; i < table->capacity; i++) { 165 | ht_entry entry = table->entries[i]; 166 | if (entry.key != NULL) { 167 | ht_set_entry(new_entries, new_capacity, entry.key, 168 | entry.value, NULL); 169 | } 170 | } 171 | 172 | // Free old entries array and update this table's details. 173 | free(table->entries); 174 | table->entries = new_entries; 175 | table->capacity = new_capacity; 176 | return true; 177 | } 178 | 179 | const char* ht_set(ht* table, const char* key, void* value) { 180 | assert(value != NULL); 181 | if (value == NULL) { 182 | return NULL; 183 | } 184 | 185 | // If length will exceed half of current capacity, expand it. 186 | if (table->length >= table->capacity / 2) { 187 | if (!ht_expand(table)) { 188 | return NULL; 189 | } 190 | } 191 | 192 | // Set entry and update length. 193 | return ht_set_entry(table->entries, table->capacity, key, value, 194 | &table->length); 195 | } 196 | 197 | size_t ht_length(ht* table) { 198 | return table->length; 199 | } 200 | 201 | hti ht_iterator(ht* table) { 202 | hti it; 203 | it._table = table; 204 | it._index = 0; 205 | return it; 206 | } 207 | 208 | bool ht_next(hti* it) { 209 | // Loop till we've hit end of entries array. 210 | ht* table = it->_table; 211 | while (it->_index < table->capacity) { 212 | size_t i = it->_index; 213 | it->_index++; 214 | if (table->entries[i].key != NULL) { 215 | // Found next non-empty item, update iterator key and value. 216 | ht_entry entry = table->entries[i]; 217 | it->key = entry.key; 218 | it->value = entry.value; 219 | return true; 220 | } 221 | } 222 | return false; 223 | } -------------------------------------------------------------------------------- /src/core/parser/core.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../error.h" 3 | #include "../identifier.h" 4 | #include "../node.h" 5 | #include "../state.h" 6 | #include "../str_builder.h" 7 | #include "../timeframe.h" 8 | #include "core.h" 9 | #include "token.h" 10 | 11 | static inline int is_newline(char c) { 12 | return c == '\n'; 13 | } 14 | 15 | static inline int is_whitespace(char c) { 16 | return c == ' '; 17 | } 18 | 19 | typedef bool (*consume_condition)(State*, char); 20 | 21 | static void consume_while(State* state, consume_condition cond) { 22 | char c = state_char(state); 23 | size_t start = state->index; 24 | 25 | str_builder_t *sb; 26 | sb = str_builder_create(); 27 | size_t len = 0; 28 | 29 | do { 30 | str_builder_add_char(sb, c); 31 | len++; 32 | 33 | state_advance_column(state); 34 | state_next(state); 35 | 36 | c = state_char(state); 37 | } while(state_inbounds(state) && cond(state, c)); 38 | state_rewind(state, 1); 39 | 40 | if(is_newline(c)) { 41 | state_advance_line(state); 42 | } 43 | 44 | size_t end = state->index + 1; 45 | char* str = str_builder_dump(sb, NULL); 46 | state_set_word(state, str, start, end); 47 | state->word_len = len; 48 | state->token_len = len; 49 | str_builder_destroy(sb); 50 | } 51 | 52 | static char* consume_string(State* state) { 53 | char c = state_char(state); 54 | char quote = c; 55 | size_t len = 0; 56 | size_t start = state->index; 57 | 58 | str_builder_t *sb; 59 | sb = str_builder_create(); 60 | 61 | do { 62 | str_builder_add_char(sb, c); 63 | len++; 64 | 65 | state_advance_column(state); 66 | state_next(state); 67 | 68 | 69 | c = state_char(state); 70 | } while(c != quote && c != '\n'); 71 | 72 | str_builder_add_char(sb, quote); 73 | 74 | size_t end = state->index; 75 | char* str = str_builder_dump(sb, &len); 76 | state_set_word(state, str, start, end); 77 | str_builder_destroy(sb); 78 | 79 | return str; 80 | } 81 | 82 | static bool identifier_consume_condition(State* state, char c) { 83 | return is_valid_identifier_char(c); 84 | } 85 | 86 | static void consume_identifier(State* state) { 87 | consume_while(state, &identifier_consume_condition); 88 | } 89 | 90 | static bool member_consume_condition(State* state, char c) { 91 | return is_valid_identifier_char(c) || c == '.'; 92 | } 93 | 94 | static bool timeframe_consume_condition(State* state, char c) { 95 | return is_timeframe_char(c); 96 | } 97 | 98 | static void consume_timeframe(State* state) { 99 | consume_while(state, &timeframe_consume_condition); 100 | } 101 | 102 | /* Public API */ 103 | int consume_token(State* state) { 104 | int len = 0; 105 | while(state_inbounds(state)) { 106 | state_next(state); 107 | state_advance_column(state); 108 | char c = state_char(state); 109 | 110 | if(is_whitespace(c)) { 111 | continue; 112 | } 113 | 114 | if(is_newline(c)) { 115 | state_advance_line(state); 116 | state->token_len = 1; 117 | return TOKEN_EOL; 118 | } 119 | 120 | len++; 121 | 122 | if(c == '{') { 123 | state->token_len = 1; 124 | return TOKEN_BEGIN_BLOCK; 125 | } 126 | 127 | if(c == '}') { 128 | state->token_len = 1; 129 | return TOKEN_END_BLOCK; 130 | } 131 | 132 | if(c == '(') { 133 | state->token_len = 1; 134 | return TOKEN_BEGIN_CALL; 135 | } 136 | 137 | if(c == ')') { 138 | state->token_len = 1; 139 | return TOKEN_END_CALL; 140 | } 141 | 142 | if(c == '=') { 143 | if(state_peek(state) == '>') { 144 | state_next(state); 145 | state->token_len = 2; 146 | return TOKEN_CALL; 147 | } 148 | state->token_len = 1; 149 | return TOKEN_ASSIGNMENT; 150 | } 151 | 152 | if(is_valid_identifier_char(c)) { 153 | consume_identifier(state); 154 | if(state_peek(state) == '.') { 155 | return TOKEN_MEMBER_EXPRESSION; 156 | } 157 | return TOKEN_IDENTIFIER; 158 | } 159 | 160 | if(c == ':') { 161 | state_advance_column(state); 162 | state_next(state); 163 | consume_identifier(state); 164 | return TOKEN_SYMBOL; 165 | } 166 | 167 | if(c == '\'' || c == '"') { 168 | consume_string(state); 169 | return TOKEN_STRING; 170 | } 171 | 172 | if(c == ',') { 173 | state->token_len = 1; 174 | return TOKEN_COMMA; 175 | } 176 | 177 | if(c == '\0') { 178 | state->token_len = 1; 179 | return TOKEN_EOF; 180 | } 181 | 182 | if(c == '@') { 183 | consume_identifier(state); 184 | return TOKEN_LOCAL; 185 | } 186 | 187 | if(is_integer(c)) { 188 | consume_timeframe(state); 189 | char last = state->word[state->word_len - 1]; 190 | if(is_integer(last)) { 191 | return TOKEN_INTEGER; 192 | } else { 193 | return TOKEN_TIMEFRAME; 194 | } 195 | } 196 | 197 | if(c == '/') { 198 | char nc = state_peek(state); 199 | if(nc == '/') { 200 | // single-line 201 | do { 202 | state_next(state); 203 | state_advance_column(state); 204 | nc = state_char(state); 205 | } while(state_inbounds(state) && !is_newline(nc)); 206 | if(is_newline(state_char(state))) { 207 | state_advance_line(state); 208 | } 209 | continue; 210 | } else if(nc == '*') { 211 | // multi-line 212 | char lc = c; 213 | do { 214 | state_next(state); 215 | state_advance_column(state); 216 | nc = state_char(state); 217 | if(is_newline(nc)) { 218 | state_advance_line(state); 219 | } 220 | } while(state_inbounds(state) && (lc != '*' && nc != '/')); 221 | continue; 222 | } 223 | } 224 | 225 | state->token_len = len; 226 | return TOKEN_UNKNOWN; 227 | } 228 | 229 | state->token_len = len; 230 | return TOKEN_EOF; 231 | } 232 | 233 | int consume_call_expression(State* state, const char* fn_name, void* expr, consume_call_expr_args on_args) { 234 | int err = 0; 235 | int token = consume_token(state); 236 | 237 | bool in_call = false; 238 | if(token != TOKEN_BEGIN_CALL) { 239 | if(token == TOKEN_IDENTIFIER) { 240 | char buffer[100]; 241 | sprintf(buffer, "%s must be called like a function. Expected %s(%s)", fn_name, fn_name, state->word); 242 | error_msg_with_code_block_dec(state, state->word_len, buffer); 243 | } else if(token == TOKEN_CALL) { 244 | char buffer[100]; 245 | sprintf(buffer, "%s must be called like a function.", fn_name); 246 | error_msg_with_code_block_dec(state, state->token_len, buffer); 247 | } else { 248 | // What should this be? 249 | error_msg_with_code_block(state, NULL, "Expected a ("); 250 | } 251 | err = 1; 252 | } else { 253 | in_call = true; 254 | token = consume_token(state); 255 | } 256 | 257 | char* identifier = NULL; 258 | int argi = 0; 259 | while(token != TOKEN_END_CALL) { 260 | switch(token) { 261 | case TOKEN_COMMA: break; 262 | case TOKEN_IDENTIFIER: 263 | case TOKEN_INTEGER: 264 | case TOKEN_SYMBOL: 265 | case TOKEN_TIMEFRAME: { 266 | identifier = state_take_word(state); 267 | _check(on_args(state, expr, token, identifier, argi)); 268 | break; 269 | } 270 | default: { 271 | if(in_call) { 272 | error_msg_with_code_block_dec(state, state->token_len, "Unknown function argument."); 273 | err = 2; 274 | goto end; 275 | } 276 | goto end_args; 277 | } 278 | } 279 | token = consume_token(state); 280 | argi++; 281 | } 282 | 283 | end_args: { 284 | if(token != TOKEN_END_CALL) { 285 | // Only show this error message if we are currently within a call. 286 | if(in_call) { 287 | char buffer[100]; 288 | char* key = identifier == NULL ? "" : identifier; 289 | sprintf(buffer, "Inline assigns must be called like a function. Expected assign(%s)", key); 290 | error_msg_with_code_block_dec(state, state->token_len, buffer); 291 | } 292 | 293 | // Rewind back out, so the next thing consumes this token. 294 | state_rewind(state, state->token_len); 295 | 296 | err = 1; 297 | } 298 | } 299 | 300 | end: { 301 | return err; 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /src/core/xstate/machine.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "../js_builder.h" 6 | #include "../node.h" 7 | #include "../set.h" 8 | #include "core.h" 9 | #include "ts_printer.h" 10 | 11 | static void add_machine_fn_args(PrintState* state, JSBuilder* jsb, MachineNode* machine_node) { 12 | js_builder_add_str(jsb, "{ "); 13 | if(machine_node->flags & MACHINE_USES_ACTION) { 14 | js_builder_add_arg(jsb, "actions"); 15 | } 16 | if(machine_node->flags & MACHINE_USES_ASSIGN) { 17 | js_builder_add_arg(jsb, "assigns"); 18 | } 19 | js_builder_add_arg(jsb, "context"); 20 | js_builder_add_str(jsb, " = {}"); 21 | if(machine_node->flags & MACHINE_USES_DELAY) { 22 | js_builder_add_arg(jsb, "delays"); 23 | } 24 | if(machine_node->flags & MACHINE_USES_GUARD) { 25 | js_builder_add_arg(jsb, "guards"); 26 | } 27 | if(machine_node->flags & MACHINE_USES_SERVICE) { 28 | js_builder_add_arg(jsb, "services"); 29 | } 30 | js_builder_add_str(jsb, " } = {}"); 31 | } 32 | 33 | void xs_add_machine_binding_name(PrintState* state, JSBuilder* jsb, MachineNode* machine_node) { 34 | js_builder_add_str(jsb, "create"); 35 | js_builder_add_char(jsb, toupper(state->source[machine_node->name_start])); 36 | int i = machine_node->name_start + 1; 37 | while(i < machine_node->name_end) { 38 | js_builder_add_char(jsb, state->source[i]); 39 | i++; 40 | } 41 | } 42 | 43 | static inline void compile_machine(PrintState* state, JSBuilder* jsb, MachineNode* machine_node, bool is_nested) { 44 | if(!is_nested) { 45 | if(machine_node->name_start == 0) { 46 | js_builder_add_str(jsb, "\nexport default function("); 47 | add_machine_fn_args(state, jsb, machine_node); 48 | js_builder_add_str(jsb, ") {\n"); 49 | js_builder_increase_indent(jsb); 50 | } else { 51 | js_builder_add_export(jsb); 52 | js_builder_add_str(jsb, "function "); 53 | xs_add_machine_binding_name(state, jsb, machine_node); 54 | js_builder_add_str(jsb, "("); 55 | add_machine_fn_args(state, jsb, machine_node); 56 | js_builder_add_str(jsb, ") {\n"); 57 | js_builder_increase_indent(jsb); 58 | } 59 | js_builder_start_call(jsb, "return createMachine"); 60 | js_builder_start_object(jsb); 61 | } 62 | 63 | if(machine_node->initial_start != 0) { 64 | js_builder_start_prop(jsb, "initial"); 65 | js_builder_copy_string(jsb, state->source, machine_node->initial_start, machine_node->initial_end); 66 | } 67 | js_builder_shorthand_prop(jsb, "context"); 68 | } 69 | 70 | void xs_enter_machine(PrintState* state, JSBuilder* jsb, Node* node) { 71 | MachineNode *machine_node = (MachineNode*)node; 72 | Node* parent_node = node->parent; 73 | bool is_nested = node_machine_is_nested(node); 74 | compile_machine(state, jsb, machine_node, is_nested); 75 | if(state->flags & XS_FLAG_DTS) { 76 | ts_printer_add_machine_name(state->tsprinter, machine_node->name_start, machine_node->name_end); 77 | } 78 | } 79 | 80 | void xs_exit_machine(PrintState* state, JSBuilder* jsb, Node* node) { 81 | bool has_guard = state->guard != NULL; 82 | bool has_action = state->action != NULL; 83 | bool has_delay = state->delay != NULL; 84 | bool has_service = state->service != NULL; 85 | bool needs_options = has_guard || has_action || has_delay || has_service; 86 | bool is_nested = node_machine_is_nested(node); 87 | 88 | if(!is_nested && needs_options) { 89 | js_builder_end_object(jsb); 90 | js_builder_add_str(jsb, ", "); 91 | 92 | js_builder_start_object(jsb); 93 | 94 | if(has_guard) { 95 | js_builder_start_prop(jsb, "guards"); 96 | js_builder_start_object(jsb); 97 | 98 | Ref* ref = state->guard; 99 | while(ref != NULL) { 100 | js_builder_start_prop(jsb, ref->key); 101 | 102 | Expression* expression = ref->value; 103 | 104 | switch(expression->type) { 105 | case EXPRESSION_IDENTIFIER: { 106 | char* identifier = ((IdentifierExpression*)expression)->name; 107 | js_builder_add_str(jsb, identifier); 108 | break; 109 | } 110 | case EXPRESSION_SYMBOL: { 111 | char* identifier = ((SymbolExpression*)expression)->name; 112 | js_builder_add_str(jsb, "guards."); 113 | js_builder_add_str(jsb, identifier); 114 | break; 115 | } 116 | default: { 117 | printf("Unexpected type of expression\n"); 118 | goto end_guardloop; 119 | } 120 | } 121 | 122 | ref = ref->next; 123 | } 124 | 125 | end_guardloop: {}; 126 | 127 | js_builder_end_object(jsb); 128 | } 129 | 130 | if(has_action) { 131 | js_builder_start_prop(jsb, "actions"); 132 | js_builder_start_object(jsb); 133 | 134 | Ref* ref = state->action; 135 | while(ref != NULL) { 136 | js_builder_start_prop(jsb, ref->key); 137 | 138 | Expression* expression = ref->value; 139 | 140 | switch(expression->type) { 141 | case EXPRESSION_ASSIGN: { 142 | AssignExpression* assign = (AssignExpression*)expression; 143 | 144 | xs_start_assign_call(jsb, assign); 145 | 146 | char* identifier; 147 | switch(assign->value->type) { 148 | case EXPRESSION_IDENTIFIER: { 149 | char* identifier = ((IdentifierExpression*)assign->value)->name; 150 | js_builder_add_str(jsb, identifier); 151 | break; 152 | } 153 | case EXPRESSION_SPAWN: { 154 | SpawnExpression* spawn_expression = (SpawnExpression*)assign->value; 155 | xs_add_spawn_call(state, jsb, spawn_expression); 156 | break; 157 | } 158 | case EXPRESSION_SYMBOL: { 159 | SymbolExpression* symbol_expression = (SymbolExpression*)assign->value; 160 | js_builder_add_str(jsb, "assigns."); 161 | js_builder_add_str(jsb, symbol_expression->name); 162 | break; 163 | } 164 | } 165 | 166 | xs_end_assign_call(jsb); 167 | break; 168 | } 169 | case EXPRESSION_SEND: { 170 | SendExpression* send_expression = (SendExpression*)expression; 171 | xs_add_send_call(jsb, send_expression); 172 | break; 173 | } 174 | case EXPRESSION_IDENTIFIER: { 175 | IdentifierExpression* identifier_expression = (IdentifierExpression*)expression; 176 | js_builder_add_str(jsb, identifier_expression->name); 177 | break; 178 | } 179 | case EXPRESSION_SYMBOL: { 180 | SymbolExpression* symbol_expression = (SymbolExpression*)expression; 181 | js_builder_add_str(jsb, "actions."); 182 | js_builder_add_str(jsb, symbol_expression->name); 183 | break; 184 | } 185 | default: { 186 | printf("This type of expression is not currently supported.\n"); 187 | break; 188 | } 189 | } 190 | 191 | ref = ref->next; 192 | } 193 | 194 | js_builder_end_object(jsb); 195 | } 196 | 197 | if(has_delay) { 198 | js_builder_start_prop(jsb, "delays"); 199 | js_builder_start_object(jsb); 200 | 201 | Ref* ref = state->delay; 202 | while(ref != NULL) { 203 | DelayExpression* expression = (DelayExpression*)ref->value; 204 | if(expression->ref->type == EXPRESSION_IDENTIFIER) { 205 | IdentifierExpression* identifier_expression = (IdentifierExpression*)expression->ref; 206 | js_builder_start_prop(jsb, identifier_expression->name); 207 | js_builder_add_str(jsb, identifier_expression->name); 208 | } else if(expression->ref->type == EXPRESSION_SYMBOL) { 209 | SymbolExpression* symbol_expression = (SymbolExpression*)expression->ref; 210 | js_builder_start_prop(jsb, symbol_expression->name); 211 | js_builder_add_str(jsb, "delays."); 212 | js_builder_add_str(jsb, symbol_expression->name); 213 | } 214 | 215 | ref = ref->next; 216 | } 217 | 218 | js_builder_end_object(jsb); 219 | } 220 | 221 | if(has_service) { 222 | js_builder_start_prop(jsb, "services"); 223 | js_builder_start_object(jsb); 224 | 225 | Ref* ref = state->service; 226 | while(ref != NULL) { 227 | switch(ref->value->type) { 228 | case EXPRESSION_INVOKE: { 229 | InvokeExpression* invoke_expression = (InvokeExpression*)ref->value; 230 | switch(invoke_expression->ref->type) { 231 | case EXPRESSION_IDENTIFIER: { 232 | fprintf(stderr, "Currently not supported: TODO"); 233 | break; 234 | } 235 | case EXPRESSION_SYMBOL: { 236 | SymbolExpression* symbol_expression = (SymbolExpression*)invoke_expression->ref; 237 | js_builder_start_prop(jsb, symbol_expression->name); 238 | js_builder_add_str(jsb, "services."); 239 | js_builder_add_str(jsb, symbol_expression->name); 240 | break; 241 | } 242 | } 243 | break; 244 | } 245 | } 246 | 247 | ref = ref->next; 248 | } 249 | 250 | js_builder_end_object(jsb); 251 | } 252 | 253 | js_builder_end_object(jsb); 254 | } else if(!is_nested) { 255 | js_builder_end_object(jsb); 256 | } 257 | 258 | if(!is_nested) { 259 | js_builder_end_call(jsb); 260 | js_builder_add_str(jsb, ";\n}"); 261 | js_builder_decrease_indent(jsb); 262 | } 263 | 264 | if(state->flags & XS_FLAG_DTS) { 265 | ts_printer_figure_out_entry_events(state->tsprinter); 266 | ts_printer_create_machine(state->tsprinter); 267 | } 268 | 269 | set_clear(state->guard_names); 270 | set_clear(state->action_names); 271 | set_clear(state->delay_names); 272 | set_clear(state->service_names); 273 | xs_destroy_state_refs(state); 274 | } --------------------------------------------------------------------------------