Compare commits

...

288 Commits

Author SHA1 Message Date
Daniel Kolesa 2eebfe8ba1 ci: disable libc++ for clang for now
There is something wrong with the ubuntu env.
2022-04-20 00:53:13 +02:00
Daniel Kolesa e34186b9f6 add libc++ into the ci env 2022-04-20 00:50:55 +02:00
Daniel Kolesa a35cf1a1fd release 1.0.0 alpha1
No functional changes since the last commit. I decided that
I have nothing significant to actually change and am pretty
happy with how it is, so just ship it.
2022-04-20 00:42:07 +02:00
Daniel Kolesa d748b0e671 remove state::get_idents
this was never going to be good from API standpoint, and is no
longer necessary (can use ident_count + get_ident by index with
a loop, for example)
2021-05-16 00:51:26 +02:00
Daniel Kolesa 4fb6b9a0bc add ident getter by index, + ident count method on state 2021-05-16 00:50:14 +02:00
Daniel Kolesa 8086c23a77 memory safe error stack state 2021-05-15 23:27:34 +02:00
Daniel Kolesa 3189d87ac9 use a reference for the ident in stack node 2021-05-15 04:23:47 +02:00
Daniel Kolesa b5127c52bf simplify exec_alias more 2021-05-14 23:31:27 +02:00
Daniel Kolesa 72c3ecedb1 move cs thread call stack off real thread call stack 2021-05-14 23:14:48 +02:00
Daniel Kolesa 203e0bd5e4 exec_alias cleanups 2021-05-14 22:42:19 +02:00
Daniel Kolesa 263b12c1c4 stop using reinterpret_cast 2021-05-14 22:10:16 +02:00
Daniel Kolesa 6ffdc7fa91 various vm cleanups 2021-05-14 19:18:11 +02:00
Daniel Kolesa 036362683c kill call_with_args template 2021-05-14 02:54:29 +02:00
Daniel Kolesa 8b04bd1488 refactor vm scope guard 2021-05-14 02:36:16 +02:00
Daniel Kolesa 36fb06425b do not use lambdas with captures in vm 2021-05-13 04:48:55 +02:00
Daniel Kolesa 523586e3a6 mask vm loop always by opcode (fewer switch cases) 2021-05-13 00:43:44 +02:00
Daniel Kolesa a5536d6974 more vm type handling cleanups 2021-05-12 04:09:18 +02:00
Daniel Kolesa 775d69cf1c minor vm cleanups 2021-05-12 03:55:20 +02:00
Daniel Kolesa 497502bdb0 drop util::print_stack 2021-05-10 01:49:14 +02:00
Daniel Kolesa 43898b4798 make pcall not collect the entire bt, call body per level instead 2021-05-10 01:48:09 +02:00
Daniel Kolesa a8d2bfc442 don't use print_stack in repl 2021-05-10 01:16:27 +02:00
Daniel Kolesa 1739cbed6e remove separate stack_state 2021-05-09 20:21:35 +02:00
Daniel Kolesa fdcc8a09e9 remove gap property from stack_state 2021-05-09 20:01:47 +02:00
Daniel Kolesa 6e779f827f cap default recursion limit at 1024 2021-05-08 21:41:13 +02:00
Daniel Kolesa 201d6031b0 add missing file 2021-05-08 17:26:23 +02:00
Daniel Kolesa 54a7b4b7f1 add assert in lib_base, remove formatting public error api 2021-05-08 17:20:56 +02:00
Daniel Kolesa dec7e844e7 use a regular union for any_value 2021-05-08 06:42:47 +02:00
Daniel Kolesa 7f74602b7e add convenience constructors and assignment ops to any_value 2021-05-07 01:56:43 +02:00
Daniel Kolesa 1c56ea36ff remove save_val 2021-05-07 01:13:02 +02:00
Daniel Kolesa 9437e0951b eliminate type separation between IVAR/FVAR/SVAR 2021-05-07 00:07:13 +02:00
Daniel Kolesa 6218adb78a remove public type specializations for vars 2021-05-06 23:13:48 +02:00
Daniel Kolesa 6fede13b97 remove set_value() specialization per-vartype 2021-05-06 04:13:11 +02:00
Daniel Kolesa fa5e38afad remove the call() specializations per-vartype 2021-05-06 04:07:45 +02:00
Daniel Kolesa 6a892b4b2c move set_raw_value to builtin_var 2021-05-06 04:01:29 +02:00
Daniel Kolesa 74f437f851 s/global_var/builtin_var/ 2021-05-06 03:47:38 +02:00
Daniel Kolesa 139ead5d72 fold BC_INST_IVAR/FVAR/SVAR into one instruction 2021-05-06 03:42:58 +02:00
Daniel Kolesa a57072fb73 use any_value for var storage and return it from value() 2021-05-06 03:34:25 +02:00
Daniel Kolesa 344bba07f3 remove is_ helpers for ident types (except is_var) 2021-05-05 03:24:41 +02:00
Daniel Kolesa bd1e7825d8 be consistent with usage of get_/set_ prefixes 2021-05-05 03:16:32 +02:00
Daniel Kolesa 03325af1e6 pass old and new values to var_changed 2021-05-05 00:37:12 +02:00
Daniel Kolesa 7febb8f5b1 remove internal_error 2021-05-03 00:39:00 +02:00
Daniel Kolesa 5b54c74f2a remove call() methods from state 2021-05-02 22:44:38 +02:00
Daniel Kolesa 4dd1518f6c s/run/call/ 2021-04-30 02:55:20 +02:00
Daniel Kolesa 9c14e6ca65 rename r type to v 2021-04-29 20:02:04 +02:00
Daniel Kolesa 5d4bcaf797 use an ellipsis for variadics instead of V 2021-04-29 19:56:48 +02:00
Daniel Kolesa e14d5c4aa3 remove automatic concatenating variadics
these were pretty much just pointless sugar for something that
can be better achieved with ordinary variadics and an extra call
(which is what it did, except in VM)
2021-04-29 19:29:51 +02:00
Daniel Kolesa e52720ebb2 rename N in arglist to # 2021-04-29 04:15:16 +02:00
Daniel Kolesa da7548664c remove b and F arg types + renames + set default args to none
setting default args to none rather than whatever default
value allows for easily checking whether the arg was set,
without losing anything (since e.g. calling get_integer on
a none value still returns a 0)

'b' and 'F' were kinda ugly and handled special niches, which
are no longer a thing now that we're defaulting to none
2021-04-29 01:40:55 +02:00
Daniel Kolesa a9afa89af6 rework API for by-name lookups/assignments, simplify VM for that 2021-04-28 03:23:17 +02:00
Daniel Kolesa acdb9a4a0e remove unused prototype 2021-04-26 02:44:31 +02:00
Daniel Kolesa 92281e88c1 remove state::get_alias(), make get_ident() return an optional 2021-04-26 02:42:08 +02:00
Daniel Kolesa fadee6ab6f alias_local belongs in ident.hh, not util.hh 2021-04-25 00:48:04 +02:00
Daniel Kolesa 43e6dc9341 remove get_ utilities from ident
this is not a good api (it prevents extensibility, requires
handling errors twice to be safe - once on user side, once
internally - and so on); just cast it
2021-04-25 00:44:45 +02:00
Daniel Kolesa f19fd57549 get rid of some more pointers in API 2021-04-24 23:50:06 +02:00
Daniel Kolesa 3e8db1042a fix ivar and fvar lookup code 2021-04-24 23:43:34 +02:00
Daniel Kolesa f4b8d077bb alias_local and get_ident() memory safety (always return refs) 2021-04-24 23:34:44 +02:00
Daniel Kolesa 960f463259 complete the public API documentation 2021-04-23 03:20:53 +02:00
Daniel Kolesa 0a432d2f19 document values 2021-04-22 05:20:59 +02:00
Daniel Kolesa e7578f7e17 document error, ident 2021-04-20 02:52:27 +02:00
Daniel Kolesa a1b5575a9a fix running tests in wine 2021-04-15 21:21:34 +02:00
Daniel Kolesa 37ad7e7589 update compiler compat note + lowercase windows.h in linenoise 2021-04-15 20:59:30 +02:00
Daniel Kolesa 9a6c3798b3 CI: use bash for CI scripts 2021-04-15 20:54:24 +02:00
Daniel Kolesa 86f9cbdc07 try enabling mingw CI 2021-04-15 20:49:36 +02:00
Daniel Kolesa 816d6e521d support user conf file 2021-04-15 20:40:34 +02:00
Daniel Kolesa 84f6d1f0d6 make std::span user replaceable
this will allow usage on some older compilers for those who want it
2021-04-15 20:27:24 +02:00
Daniel Kolesa 8b1aaa63fe add initial doxygen documentation infra 2021-04-15 02:42:06 +02:00
Daniel Kolesa c4e311d71a fix msvc warning 2021-04-13 02:53:32 +02:00
Daniel Kolesa c42e58b868 add initial test infrastructure 2021-04-13 02:49:58 +02:00
Daniel Kolesa de4a0c65c7 update readme 2021-04-12 20:46:58 +02:00
Daniel Kolesa 36bf0e368f drop readline support in repl
this library is bad, and its documentation is even worse

and our linenoise now supports every non-boomer platform already
anyway
2021-04-12 20:43:19 +02:00
Daniel Kolesa ad1c345401 reenable macos in CI 2021-04-12 20:02:05 +02:00
Daniel Kolesa a5003678da fix build with libc++, and try using it in CI with clang 2021-04-12 19:53:24 +02:00
Daniel Kolesa 5e9aefb52a disable mac and mingw cross CI builds for now
apparently macos c++ stdlib is missing c++20 features (particularly
string_view doesn't seem to accept two pointers in a constructor)
and mingw is only present as gcc9 in ubuntu 20.04 (too old)
2021-04-12 03:59:01 +02:00
Daniel Kolesa 8bdb68b298 use gcc10 for linux, fix windows build script typo 2021-04-12 03:45:22 +02:00
Daniel Kolesa f31a4f00f9 add CI infra 2021-04-12 03:36:36 +02:00
Daniel Kolesa 8114279816 fix warning with msvc 2021-04-12 00:17:07 +02:00
Daniel Kolesa 8207ef0ce3 use overloads for new var methods 2021-04-11 20:49:26 +02:00
Daniel Kolesa b26bae3ec5 move standard library init outside state, make it never error 2021-04-11 19:36:41 +02:00
Daniel Kolesa a2c50744da remove public state::destroy() 2021-04-11 18:56:08 +02:00
Daniel Kolesa e644674724 hide internal_state from all public headers 2021-04-11 18:46:45 +02:00
Daniel Kolesa 3ed7b59d0b only store trivial types in any_value (manage strrefs manually) 2021-04-11 03:55:16 +02:00
Daniel Kolesa d358dd83e7 halve the size of any_value by removing state pointer from it
this brings more advantages too like actually being default
constructible, so it can be used more easily in data structures etc
2021-04-11 03:32:33 +02:00
Daniel Kolesa 7229672f56 clean up command call parsing 2021-04-11 00:13:10 +02:00
Daniel Kolesa 0ee0ebfb42 clean up the main parser path 2021-04-10 19:32:01 +02:00
Daniel Kolesa 51f09c3dfb clean up some more parser routines 2021-04-10 07:32:44 +02:00
Daniel Kolesa a6426c60ee rewrite compilearg 2021-04-10 05:53:17 +02:00
Daniel Kolesa e5cf9452f2 rewirte compileblockmain 2021-04-10 05:32:07 +02:00
Daniel Kolesa eac137e3c8 rewrite compileblocksub 2021-04-10 04:51:11 +02:00
Daniel Kolesa c4d39caa61 rewrite compilelookup 2021-04-10 04:40:58 +02:00
Daniel Kolesa 416c6ba8fb do not access parser state from error 2021-04-10 03:51:08 +02:00
Daniel Kolesa 83aa8bfd07 hide bcode pointers from public api 2021-04-10 03:37:59 +02:00
Daniel Kolesa 6d6a115d0c remove leftover junk 2021-04-10 03:01:41 +02:00
Daniel Kolesa d2b53b174e move all codegen stuff, make instruction vector private 2021-04-10 02:56:49 +02:00
Daniel Kolesa 05661662d2 more codegen conversions 2021-04-10 02:15:06 +02:00
Daniel Kolesa 63023f8c5e remove parser_state::gen_main 2021-04-10 00:54:28 +02:00
Daniel Kolesa 1bc94bf5af move some more codegen into proper place 2021-04-10 00:42:01 +02:00
Daniel Kolesa 295c905c32 more codegen utils 2021-04-09 03:34:48 +02:00
Daniel Kolesa 3790435c34 add more codegen utils 2021-04-09 03:22:34 +02:00
Daniel Kolesa 51da22be06 add ivar/fvar/svar lookup funcs in codegen 2021-04-09 03:14:34 +02:00
Daniel Kolesa bac186a0c7 move compileblockstr/compileunescapestr logic into gen 2021-04-09 02:54:36 +02:00
Daniel Kolesa c0fc1aa452 abstract away ugly bcode reference creation 2021-04-09 01:12:52 +02:00
Daniel Kolesa dcd5f61748 fewer direct accesses to code vector 2021-04-09 01:03:29 +02:00
Daniel Kolesa e5a05a3199 remove parser_state::done() 2021-04-09 00:56:58 +02:00
Daniel Kolesa 283f23394d add more codegen utilities 2021-04-09 00:50:13 +02:00
Daniel Kolesa 5f67b205df initial bits for separation of codegen from parser 2021-04-09 00:41:55 +02:00
Daniel Kolesa ccb0c09d59 rename codegen_state to parser_state and move it
later commits will rewrite the parser and separate the codegen
bits into their own API
2021-04-08 23:35:30 +02:00
Daniel Kolesa 9a2de6ffd3 move include install logic outside src/ 2021-04-07 02:03:41 +02:00
Daniel Kolesa f03407400c remove pic: true in the build
since meson will always default to pic for static libraries unless
overridden with -Db_staticpic=false, we don't want to mess with
this
2021-04-07 02:01:05 +02:00
Daniel Kolesa 34959f44f3 support -Ddefault_library=both for windows 2021-04-07 01:58:58 +02:00
Daniel Kolesa c09613c5ea fill in missing headers to install 2021-04-07 01:44:40 +02:00
Daniel Kolesa 3124f8a487 add a thingy in meson to guess c++ std
since meson doesn't yet support cpp_std=c++2a or c++20 for msvc++,
only gcc (and the older version we support doesn't have c++20
either), default to none and add some minimal logic which will
guess it for whatever compiler we're using

people can still override the standard manually, in which case our
guess logic will not be used
2021-04-07 01:39:18 +02:00
Daniel Kolesa e254000f5f readme rewrite 2021-04-07 01:25:28 +02:00
Daniel Kolesa 2de2c9b46c remove get_raw_type from public interface
users of the library never need to know this
2021-04-07 00:30:39 +02:00
Daniel Kolesa 4da0cb54a8 use format contants in default ivar/fvar/svar handlers 2021-04-06 21:23:17 +02:00
Daniel Kolesa f93042d7b9 add more utility methods for string_ref 2021-04-06 01:10:49 +02:00
Daniel Kolesa e531ab3434 clean up any_value APIs for consistency 2021-04-06 00:54:46 +02:00
Daniel Kolesa 0b4f7573d2 no need to handle null result in alloc_buf 2021-04-05 20:07:25 +02:00
Daniel Kolesa 3d06157dbc guarantee valid result from new_command 2021-04-05 20:05:41 +02:00
Daniel Kolesa 8c8aa26c20 return refs for new_*var and new_ident (guarantee valid result) 2021-04-05 19:52:13 +02:00
Daniel Kolesa 1a68dd1c25 move callable into internal namespace 2021-04-05 19:22:40 +02:00
Daniel Kolesa 194d5f960b drop ret-by-reference run APIs 2021-04-05 18:32:45 +02:00
Daniel Kolesa f64df83dd8 add getter/setter api on alias, drop old broken api 2021-04-05 18:20:07 +02:00
Daniel Kolesa a8cfa08bd8 split up cubescript.hh into individual files 2021-04-05 04:40:52 +02:00
Daniel Kolesa 06b1661afd hide thread_state, remove state::thread_pointer() 2021-04-05 04:10:39 +02:00
Daniel Kolesa 59c0d16d50 make threads work
this means ensuring all required fields are set when creating
new threads. as well as respecting the "global" ident flags when
an alias is not pushed (in order to erase the UNKNOWN flag for
all threads)
2021-04-05 00:55:31 +02:00
Daniel Kolesa c2de63be25 re-expose new_ident in a restricted way 2021-04-04 19:53:59 +02:00
Daniel Kolesa 1624938c0b remove public is_in_loop api 2021-04-04 19:43:28 +02:00
Daniel Kolesa 7158a6979b wipe thread state on destroy to prevent dangling pointer use 2021-04-04 19:40:15 +02:00
Daniel Kolesa 9c2d375471 remove overrides for fvar and svar handlers in repl 2021-04-04 19:35:45 +02:00
Daniel Kolesa 04441f206d remove the 255 run-depth limitation
also add optional per-thread API that sets the maximum run depth
(0 means no limit, default) if you want it; since we no longer
store stuff on the stack it should not be necessary though
2021-04-04 19:31:29 +02:00
Daniel Kolesa c177013ea2 remove reliance on hardcoded indexes entirely 2021-04-04 06:52:02 +02:00
Daniel Kolesa e65e141741 sanitize var names, provide cached builtins for var handlers 2021-04-04 06:47:17 +02:00
Daniel Kolesa a789659387 minor cleanups 2021-04-04 05:36:19 +02:00
Daniel Kolesa 529e34d268 add a variable value change trigger callback 2021-04-04 04:30:36 +02:00
Daniel Kolesa bab8633a05 add proper var value setting helpers 2021-04-04 02:58:04 +02:00
Daniel Kolesa e9fc023daa implement override value saving 2021-04-04 02:44:35 +02:00
Daniel Kolesa 3d91cf2ee7 rea-add proper handling of variable overrides 2021-04-04 02:33:28 +02:00
Daniel Kolesa 6777eb73d5 re-add some support for persistent/overridable flags + fix unknown 2021-04-04 01:08:30 +02:00
Daniel Kolesa f0bb6b1410 make more api private 2021-04-03 06:16:43 +02:00
Daniel Kolesa ddb0799213 minor cleanups 2021-04-03 05:46:05 +02:00
Daniel Kolesa 238e5a6ac5 unexpose get_flags from ident, replace with specific methods 2021-04-03 05:39:19 +02:00
Daniel Kolesa 2a8d2103b8 allow alias_local for arg aliases 2021-04-03 05:26:59 +02:00
Daniel Kolesa b2b83a8e5b clean up some leftover cruft 2021-04-03 03:40:19 +02:00
Daniel Kolesa b00a08ea88 remove most variable code and leave it to user-defined handlers
the user can now define commands '//ivar', '//fvar' and '//svar'
which take identity (which is the variable) as the first argument
followed by any number of user defined arguments with types (one
can use the N argument type to check argument count, and when no
value arguments are provided, print the variable); this allows us
to implement different styles of variable setting without the
interpreter itself knowing about it, as well as ditch all the
stuff with overridden vars and hex vars and whatnot since this is
all specific to the engine and has no place in here

there is still leftover code remaining, which will get cleaned up
afterwards...
2021-04-03 03:16:57 +02:00
Daniel Kolesa 95e7ae320c simplify alias_stack push/pop a bit 2021-04-02 23:50:35 +02:00
Daniel Kolesa 52b305954f per-thread alias stack (the vm should be mostly resumable now) 2021-04-02 19:13:34 +02:00
Daniel Kolesa 881ba4bce9 prepare codebase for per-thread alias stack 2021-04-02 05:47:49 +02:00
Daniel Kolesa e8856f8f9d rename alias_stack to alias_local 2021-04-02 04:43:57 +02:00
Daniel Kolesa 99ec1a8583 make push_alias/alias_stack responsible for unsetting unknown flag 2021-04-02 04:34:14 +02:00
Daniel Kolesa 9d7853a840 store cached compiled code on alias stack
this will reduce needless discards on push and simplify the code
2021-04-02 02:12:31 +02:00
Daniel Kolesa 2b0392e27e remove some more raw bcode usage 2021-04-01 05:25:21 +02:00
Daniel Kolesa b2caade276 remove all raw bcode passing in public api 2021-04-01 05:06:26 +02:00
Daniel Kolesa 039dec8ec5 always define LIBCUBESCRIPT_BUILD for build 2021-04-01 04:39:13 +02:00
Daniel Kolesa e2b65000d5 hide value getter from public alias interface
since this will become thread-specific, this API makes no sense
2021-04-01 04:19:00 +02:00
Daniel Kolesa 6bb3dc2a57 avoid recompiling code when undo/redoing arguments 2021-04-01 03:54:34 +02:00
Daniel Kolesa d8736a059b greatly simplify internal alias stack handling
this should be more efficient too (no value moves)
2021-04-01 03:41:40 +02:00
Daniel Kolesa b36ef94b80 update notes on linenoise 2021-03-31 23:55:40 +02:00
Daniel Kolesa 6068a7259b switch to modified cpp-linenoise, use it on windows 2021-03-31 23:48:22 +02:00
Daniel Kolesa b9c74d86b5 msvc warning fixes 2021-03-31 02:21:32 +02:00
Daniel Kolesa e4c4c51055 remove more problematic public members 2021-03-31 01:48:56 +02:00
Daniel Kolesa 2312961cdc remove string_view from exported members of struct error
msvc doesn't like it
2021-03-31 01:43:29 +02:00
Daniel Kolesa 2270d8e41f up the default warning level, turn off rtti by default 2021-03-31 01:35:02 +02:00
Daniel Kolesa 13d46881e8 windows fixes and other correctness/portability stuff 2021-03-31 01:13:35 +02:00
Daniel Kolesa eddbf64c87 get rid of call_with_cleanup 2021-03-30 23:49:50 +02:00
Daniel Kolesa 19f0ff379a move callable into its own header 2021-03-30 23:37:55 +02:00
Daniel Kolesa 89e5f5f004 move everything to alias_stack and remove stacked_value 2021-03-30 20:55:50 +02:00
Daniel Kolesa 1def48cf76 redo alias_stack for robustness (guarantee order of push/pop) 2021-03-30 20:37:50 +02:00
Daniel Kolesa 970e37f318 make alias pushing separate from setting value 2021-03-30 20:24:46 +02:00
Daniel Kolesa d1243c5cc3 add better api to deal with alias stack 2021-03-30 03:57:43 +02:00
Daniel Kolesa 3dc6ad866f make command argcount unlimited, remove maxargs checks from codegen
also adjust the bytecode appropriately, now you can call commands
with an unlimited number of args and aliases with max 32 args,
extra args will be ignored
2021-03-30 02:08:25 +02:00
Daniel Kolesa c2bb8e4a3d move ident_stack out of public interface 2021-03-30 00:31:11 +02:00
Daniel Kolesa 4154944726 remove fixed-size argument stacks and reliance on argument ids 2021-03-30 00:28:06 +02:00
Daniel Kolesa 9e82dc0d2f get rid of cycle check from push_arg 2021-03-29 23:42:08 +02:00
Daniel Kolesa 47f24d6e2e fix typo 2021-03-29 00:46:13 +02:00
Daniel Kolesa 8385ab01e7 use bitset to store usedargs and increase argcount to 32 2021-03-28 23:46:08 +02:00
Daniel Kolesa 96592fb6ac eliminate MAX_ARGUMENTS from lib_base 2021-03-28 22:56:17 +02:00
Daniel Kolesa f7ba70b8b2 fix conc instruction 2021-03-28 22:49:46 +02:00
Daniel Kolesa 2a017a336c reorder vm a bit 2021-03-28 17:11:35 +02:00
Daniel Kolesa 064a7ea99b document the instructions and drop more now useless vm code 2021-03-28 17:07:29 +02:00
Daniel Kolesa d11e50f5b5 remove the now useless MAX_RESULTS constant 2021-03-28 01:33:31 +01:00
Daniel Kolesa d7700a9337 remove now unused BC_INST_CONC_M 2021-03-28 01:31:59 +01:00
Daniel Kolesa 3f513180f1 drop prevargs in codegen
since we now have a single, contiguous, unlimited VM stack, this
does not make any sense anymore (it was a limitation of the static
array)
2021-03-28 01:28:31 +01:00
Daniel Kolesa c62dd07adb update linenoise to latest git 2021-03-28 01:05:09 +01:00
Daniel Kolesa 371ef9e912 add build option to disable repl, use feature objects 2021-03-28 00:56:11 +01:00
Daniel Kolesa 71f45b2f07 fix up usage of IDENT_FLAG_UNKNOWN 2021-03-28 00:38:41 +01:00
Daniel Kolesa 99f227bfd9 unify arg and non-arg opcodes 2021-03-28 00:19:51 +01:00
Daniel Kolesa d1e131dbf6 don't rely on MAX_ARGUMENTS where not necessary 2021-03-27 23:43:55 +01:00
Daniel Kolesa 54c0f2ae20 remove more dead bits 2021-03-27 00:35:20 +01:00
Daniel Kolesa a24f7c9385 remove leftover dead code 2021-03-27 00:32:27 +01:00
Daniel Kolesa a37eb25d1c move public state methods where they belong 2021-03-27 00:26:59 +01:00
Daniel Kolesa b27d4fa7e6 make p_tstate actually private 2021-03-26 03:05:14 +01:00
Daniel Kolesa bd9a6cbf7c eliminate more future private accesses 2021-03-26 02:59:42 +01:00
Daniel Kolesa 0b2c4c7038 pass thread_state in more places 2021-03-26 02:29:54 +01:00
Daniel Kolesa 865bac73f3 remove pointless friends 2021-03-25 01:57:47 +01:00
Daniel Kolesa c854e9d189 move the owner tag inside 2021-03-25 01:55:47 +01:00
Daniel Kolesa 468c3a07d4 get rid of some indirections 2021-03-25 01:52:03 +01:00
Daniel Kolesa d80af7b159 hide internal_state from public state struct 2021-03-25 01:37:42 +01:00
Daniel Kolesa 14cd90116d use the value stack in more places 2021-03-24 23:30:30 +01:00
Daniel Kolesa 4e8c3ee40f move the VM stack off the real stack + std cleanups 2021-03-24 22:38:29 +01:00
Daniel Kolesa 3d37ea22c3 put force_ident and force_code in any_value 2021-03-24 21:01:01 +01:00
Daniel Kolesa 00b8312ad5 move most thread state into an internal structure 2021-03-24 20:33:20 +01:00
Daniel Kolesa 4ded59ce70 launder aligned_storage pointers 2021-03-24 02:42:33 +01:00
Daniel Kolesa d774db675a separate cs_gen.hh, various refactors 2021-03-24 02:21:32 +01:00
Daniel Kolesa 21aec59ad3 rename namespace to cubescript 2021-03-23 23:32:25 +01:00
Daniel Kolesa b9b344cba6 remove cs_ namespace in all API 2021-03-23 23:29:32 +01:00
Daniel Kolesa b3b4624af1 drop cubescript.cc, move parsing stuff from cs_std to cs_parser 2021-03-23 22:17:25 +01:00
Daniel Kolesa 7139370990 move belonging stuff into cs_ident 2021-03-23 21:57:38 +01:00
Daniel Kolesa d06396f88f move more stuff into cs_state.cc 2021-03-23 21:55:19 +01:00
Daniel Kolesa 95e0da1fff move some more stuff, don't use both_libraries() in meson 2021-03-23 02:48:14 +01:00
Daniel Kolesa b5b0d0adf5 move some public state api into cs_state, separate lib_base 2021-03-23 02:43:18 +01:00
Daniel Kolesa 87e141ab36 move public ident interfaces to cs_ident.cc 2021-03-23 02:35:31 +01:00
Daniel Kolesa 3d9f73c7b2 drop removed arg types in repl 2021-03-23 02:23:24 +01:00
Daniel Kolesa 4b766a380f drop the util namespace 2021-03-23 02:02:43 +01:00
Daniel Kolesa 4b6c284ea1 separate cs_parser.cc, drop cs_util.cc 2021-03-23 02:00:23 +01:00
Daniel Kolesa 68b66c0b28 move strref implementation to strman 2021-03-23 01:49:29 +01:00
Daniel Kolesa 2cc1b0e271 add cs_std.cc, drop cs_util.hh 2021-03-23 01:46:56 +01:00
Daniel Kolesa f52aeead32 eliminate the strref friend kludge 2021-03-23 01:35:04 +01:00
Daniel Kolesa 6821260c37 separate header/impl for strman 2021-03-23 01:25:47 +01:00
Daniel Kolesa 4588ae2373 separate cs_state, cs_std 2021-03-23 01:11:21 +01:00
Daniel Kolesa 702dca0809 separate ident and error impls into their own files 2021-03-22 22:33:15 +01:00
Daniel Kolesa f9a49ffba7 get rid of emptyblock 2021-03-22 22:01:49 +01:00
Daniel Kolesa 83e92e6881 ditch a bunch of reinterpret_casts 2021-03-22 21:37:13 +01:00
Daniel Kolesa 3d2f115de5 separate bytecode bits into its own file 2021-03-22 21:26:05 +01:00
Daniel Kolesa 63251f0eac add cs_callable to replace std::function
now function data can be allocated with our own allocator as
well, plus we get guarantees on small storage; this type is
move-only, and it's also more memory efficient than std::function,
at least the libstdc++ implementation
2021-03-22 01:01:51 +01:00
Daniel Kolesa f72a39f656 manage bytecode memory using the state allocator
this means *all* heap allocated memory is now handled through
the state's allocator (well, except things using std::function),
which allows for precise memory tracking and control
2021-03-21 19:23:23 +01:00
Daniel Kolesa 666f487a92 don't need full bcode_ref/unref in this case 2021-03-21 18:53:22 +01:00
Daniel Kolesa 3f866baccb strict reference counting for bytecode references 2021-03-21 18:45:59 +01:00
Daniel Kolesa 45bf6ed7ef fix crash on quit where interfaces are destroyed instead of impls 2021-03-21 06:20:56 +01:00
Daniel Kolesa a591384afd use our allocator for more buffers 2021-03-21 06:13:45 +01:00
Daniel Kolesa a5b0ce5d72 drop cs_cmd_internal 2021-03-21 03:07:11 +01:00
Daniel Kolesa 436098cc38 drop the whole cs_alias_internal nonsense 2021-03-21 02:59:37 +01:00
Daniel Kolesa f8eb07ee2b hide implementation details of idents 2021-03-21 02:47:02 +01:00
Daniel Kolesa 3905ff0e9d fix/clean up compileblockstr 2021-03-21 00:00:20 +01:00
Daniel Kolesa be71d3a4b9 rework allocator errors and error message storage buffer 2021-03-20 21:06:26 +01:00
Daniel Kolesa d7c93fa8b9 make format errors internal errors 2021-03-20 19:54:46 +01:00
Daniel Kolesa 2f3d5ea938 further clean up list parser api 2021-03-20 19:34:51 +01:00
Daniel Kolesa 82d366366e drop libostd requirement entirely 2021-03-20 08:23:51 +01:00
Daniel Kolesa cb926a5750 get rid of ostd requirements inside library entirely 2021-03-20 07:24:25 +01:00
Daniel Kolesa 68669413cc get rid of most remaining ostd usage 2021-03-20 06:52:10 +01:00
Daniel Kolesa 10b2a81cec redo (un)escape_string around output iterators 2021-03-20 06:18:31 +01:00
Daniel Kolesa 320fdbaefd use c++20 std::span 2021-03-20 05:41:25 +01:00
Daniel Kolesa 4068c96ec2 remove the auxiliary run_<type> APIs, they are fairly useless now 2021-03-20 05:14:14 +01:00
Daniel Kolesa d626fba537 remove file exec api
this is something each thing will want to do on its own...

but add a new set of calls that take code source for debug info
2021-03-20 05:10:19 +01:00
Daniel Kolesa 55d5397f4f implement move behavior for cs_value
this prevents things from going horribly wrong when people
do move the values (since the implicit implementations don't
take care of handling the refcounts)
2021-03-20 04:49:47 +01:00
Daniel Kolesa 5648c1a757 use string_view everywhere that returns or takes a string 2021-03-20 04:10:27 +01:00
Daniel Kolesa 70d7e15ed5 eliminate cs_vector/cs_map, use our allocator for all state 2021-03-19 23:05:19 +01:00
Daniel Kolesa ac1af69d96 move cs_valbuf/charbuf before cs_shared_state 2021-03-19 22:54:22 +01:00
Daniel Kolesa f41462deaf move cs_allocator out of cs_shared_state 2021-03-19 22:53:42 +01:00
Daniel Kolesa b81e419db6 get rid of cs_vector in most places 2021-03-19 22:49:52 +01:00
Daniel Kolesa 0ca8561d5d add cs_valbuf 2021-03-19 22:40:11 +01:00
Daniel Kolesa 2e725771e4 eliminate remaining cs_string 2021-03-19 22:35:39 +01:00
Daniel Kolesa fece7ce769 get rid of most remaining cs_string 2021-03-19 22:32:28 +01:00
Daniel Kolesa 4e5e0f5de8 get rid of cs_string in codegen 2021-03-19 22:25:38 +01:00
Daniel Kolesa b27f8dee91 use state's allocator to manage cs_strman resources 2021-03-19 03:10:02 +01:00
Daniel Kolesa 47b87ab936 get rid of cs_string in lib_list 2021-03-19 02:55:59 +01:00
Daniel Kolesa e74d8b5bef get rid of cs_string in lib_str 2021-03-19 02:50:48 +01:00
Daniel Kolesa 5ee7e27839 add cs_charbuf (mutable buffer controlled by our allocator)
also use it in a few places
2021-03-19 02:44:29 +01:00
Daniel Kolesa 5a4cccf194 move strref impl to cs_util 2021-03-19 02:28:30 +01:00
Daniel Kolesa 08212df80f move shared state into cs_util 2021-03-19 02:27:36 +01:00
Daniel Kolesa 126d6ab2b6 use a stolen buffer for strupper/lower 2021-03-19 01:31:34 +01:00
Daniel Kolesa b42da1e56f add custom buffer allocation into string manager 2021-03-19 01:00:11 +01:00
Daniel Kolesa af1a85fb91 get rid of public format_int/float (use cs_value) 2021-03-19 00:34:12 +01:00
Daniel Kolesa 26bcc43578 get rid of tvals_concat in favor of simpler api 2021-03-19 00:31:18 +01:00
Daniel Kolesa 64d9c0e749 make intstr/floatstr internal to cs_value 2021-03-19 00:12:47 +01:00
Daniel Kolesa b534a6d9af get rid of some unnecessary string allocs 2021-03-19 00:01:57 +01:00
Daniel Kolesa d3ec4a47dd ditch cs_string from public headers + style fix 2021-03-18 23:56:25 +01:00
Daniel Kolesa 9d0494a9da replace list_parser with a simpler api 2021-03-18 23:53:16 +01:00
Daniel Kolesa 704f9217f7 unify enum style 2021-03-18 20:55:14 +01:00
Daniel Kolesa 7023e09049 don't use memcpy for non-trivial structures 2021-03-18 01:37:38 +01:00
Daniel Kolesa 6ed9ae1235 get rid of standard i/o inside vm 2021-03-18 00:03:30 +01:00
Daniel Kolesa fb8668a508 use more stringrefs 2021-03-17 23:32:38 +01:00
Daniel Kolesa 7eaf3679a4 get rid of cs_value::get_strr 2021-03-17 21:59:37 +01:00
Daniel Kolesa 2f5bb697eb use string refs more instead of allocated strings 2021-03-17 21:46:37 +01:00
Daniel Kolesa 8b7f36319d eliminate length field in cs_value 2021-03-17 21:16:25 +01:00
Daniel Kolesa e3cff816c7 reduce copying strings around (ensure refs are not erased) 2021-03-17 21:00:19 +01:00
Daniel Kolesa 64130a12bf remove handling of constants from codegen, bytecode and cs_value
also drop move semantics on cs_value
2021-03-17 20:41:08 +01:00
Daniel Kolesa 8def7ce85c intern all strings in cs_value 2021-03-17 02:47:34 +01:00
Daniel Kolesa c004db42c6 refactor cs_value and related structures to contain state
this will allow us to intern strings inside cs_value
2021-03-17 01:26:30 +01:00
Daniel Kolesa 8c77724f88 return alias value by value 2021-03-17 00:32:25 +01:00
Daniel Kolesa 9a1b47196a require meson 0.50 2021-03-16 00:56:19 +01:00
Daniel Kolesa 5b104da85b more generic handling of warning args and default to debugoptimized 2021-03-16 00:52:23 +01:00
Daniel Kolesa 45b1535685 configure build for c++20 2021-03-16 00:50:16 +01:00
Daniel Kolesa dcae5b30b2 initial implementation of unique/interned string manager 2021-03-16 00:45:09 +01:00
T7g 6018f6b6c2
Merge pull request #1 from Croydon/readme
Update readme, .gitignore and add .gitattributes
2020-09-28 04:48:19 -04:00
Croydon 321b90937d Update .gitignore and add .gitattributes
.gitattributes is a portable solution to normalize line endings when working cross-platform
2020-09-28 07:20:23 +02:00
Croydon 4e7b7159a0 Update readme
It uses now Meson instead of a GNU Makefile
2020-09-28 07:15:04 +02:00
Daniel Kolesa 3224a33645 fix warnings 2020-09-11 23:41:09 +02:00
Daniel Kolesa d5158bdd47 switch license back to zlib 2020-04-27 14:13:38 +02:00
64 changed files with 15275 additions and 8896 deletions

5
.ci/README.md 100644
View File

@ -0,0 +1,5 @@
# libcubescript CI scripts
These CI scripts are meant to provide a helper environment for continuous
integration. They are written to be used only in the CI environment, not
general purpose environments.

186
.ci/build-cs 100755
View File

@ -0,0 +1,186 @@
#!/usr/bin/env bash
expected_triplet=$TARGET
if [ -z "$expected_triplet" ]; then
echo "ERROR: target triplet not provided!"
exit 1
fi
current_triplet=`$CC -dumpmachine`
if [ "$CC" = "clang" ]; then
export CXX="clang++"
# clang is busted in CI for some reason
#export CXXFLAGS="-stdlib=libc++"
elif [ "$CC" = "gcc" ]; then
export CXX="g++"
else
export CXX="g++-10"
fi
if [ "$TARGET" != "darwin" -a "$CC" != "clang" -a "$expected_triplet" != "$current_triplet" ]; then
cross=yes
export CC="${expected_triplet}-${CC}"
export CXX="${expected_triplet}-${CXX}"
export STRIP="${expected_triplet}-strip"
export AR="${expected_triplet}-ar"
export AS="${expected_triplet}-as"
else
export STRIP="strip"
export AR="ar"
fi
meson_system="linux"
case "${expected_triplet}" in
darwin)
# special case here
meson_system="darwin"
;;
x86_64*)
meson_cpu_family="x86_64"
meson_cpu="x86_64"
meson_endian="little"
case "${expected_triplet}" in
*w64*)
meson_system="windows"
;;
*)
qemu_cpu="x86_64"
;;
esac
;;
i686*)
meson_cpu_family="x86"
meson_cpu="i686"
meson_endian="little"
qemu_cpu="i386"
;;
powerpc64le*)
meson_cpu_family="ppc64"
meson_cpu="ppc64le"
meson_endian="little"
qemu_cpu="ppc64le"
;;
powerpc64*)
meson_cpu_family="ppc64"
meson_cpu="ppc64"
meson_endian="big"
qemu_cpu="ppc64"
;;
powerpcle*)
echo "ERROR: ppcle not supported in qemu"
exit 1
;;
powerpc*)
meson_cpu_family="ppc"
meson_cpu="ppc"
meson_endian="big"
qemu_cpu="ppc"
;;
aarch64-*)
meson_cpu_family="aarch64"
meson_cpu="aarch64"
meson_endian="little"
qemu_cpu="aarch64"
;;
arm-*)
meson_cpu_family="arm"
meson_cpu="armv6l"
meson_endian="little"
qemu_cpu="arm"
;;
riscv64-*)
meson_cpu_family="riscv64"
meson_cpu="riscv64"
meson_endian="little"
qemu_cpu="riscv64"
;;
s390x*)
meson_cpu_family="s390x"
meson_cpu="s390x"
meson_endian="big"
qemu_cpu="s390x"
;;
mips-*)
meson_cpu_family="mips"
meson_cpu="mips"
meson_endian="big"
qemu_cpu="mips"
;;
m68k*)
meson_cpu_family="m68k"
meson_cpu="m68k"
meson_endian="big"
qemu_cpu="m68k"
;;
*)
echo "ERROR: Cross CPU unspecified"
exit 1
;;
esac
export PATH="$(pwd)/host_tools:$PATH"
if [ -n "$qemu_cpu" -a -n "$cross" ]; then
echo ">> Preparing qemu..."
# work around glibc being dumb
# the cache format is not endian agnostic, so unless a dummy file exists
# here, qemu will try to use host's and it will crash guest glibc on BE
sudo mkdir -p /usr/${expected_triplet}/etc
sudo touch /usr/${expected_triplet}/etc/ld.so.cache
fi
echo ">> Building and testing cubescript..."
mkdir -p build
cd build
args=""
if [ -n "${cross}" ]; then
if [ "${meson_system}" = "windows" ]; then
# avoid having to look the dlls for these up
export CXXFLAGS+=" -static-libgcc -static-libstdc++"
# quiet wine exe wrapper
cat << EOF > meson-exewrapper
#!/bin/sh
export WINEDEBUG=-all
export WINEPREFIX="$(pwd)/.wine"
export DISPLAY=
wine "\$@"
EOF
else
cat << EOF > meson-exewrapper
#!/bin/sh
qemu-${qemu_cpu} -L /usr/${expected_triplet} "\$@"
EOF
fi
chmod +x meson-exewrapper
cat << EOF > crossfile
[binaries]
c = '${CC}'
cpp = '${CXX}'
ar = '${AR}'
as = '${AS}'
strip = '${STRIP}'
exe_wrapper = '$(pwd)/meson-exewrapper'
[host_machine]
system = '${meson_system}'
cpu_family = '${meson_cpu_family}'
cpu = '${meson_cpu}'
endian = '${meson_endian}'
EOF
args="${args} --cross-file=crossfile"
fi
if [ -n "$BUILDTYPE" ]; then
args="${args} --buildtype=$BUILDTYPE"
fi
meson .. -Dtests_cross=true ${args} || exit 1
ninja all || exit 1
ninja test || exit 1
cd ..
exit 0

View File

@ -0,0 +1,23 @@
#!/bin/bash
unset CC CXX CC_FOR_BUILD CXX_FOR_BUILD
export PATH="$(pwd)/host_tools:$PATH"
echo ">> Building and testing cubescript..."
args=""
if [ -n "$BUILDTYPE" ]; then
args="${args} --buildtype=$BUILDTYPE"
fi
mkdir -p build
cd build
cmd.exe //C 'C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat' amd64 '&&' \
meson .. ${args} '&&' \
ninja all '&&' ninja test || exit 1
cd ..
exit 0

106
.ci/install-env 100755
View File

@ -0,0 +1,106 @@
#!/usr/bin/env bash
expected_triplet=$TARGET
if [ -z "$expected_triplet" ]; then
echo "ERROR: target triplet not provided!"
exit 1
fi
ensure_tool() {
command -v "$1" > /dev/null
if [ $? -ne 0 ]; then
echo "ERROR: Missing tool: $1"
exit 1
fi
}
if [ "$(uname -s)" = "Linux" ]; then
is_linux=yes
fi
echo ">> Checking tools..."
ensure_tool gcc
ensure_tool g++
ensure_tool clang
ensure_tool clang++
mkdir -p host_tools
echo ">> Updating package database..."
[ -n "$is_linux" ] && sudo apt-get update
echo ">> Installing meson..."
if [ -n "$is_linux" ]; then
sudo apt-get install ninja-build
else
ninja_version=1.10.2
cd host_tools
wget "https://github.com/ninja-build/ninja/releases/download/v${ninja_version}/ninja-mac.zip" || exit 1
tar xf ninja-mac.zip || exit 1
rm ninja-mac.zip
cd ..
export PATH="$(pwd)/host_tools:$PATH"
fi
if [ -n "$(command -v pip3)" ]; then
sudo pip3 install meson || exit 1
elif [ -n "$(command -v pip)" ]; then
sudo pip install meson || exit 1
else
echo "ERROR: pip not found"
exit 1
fi
ensure_tool meson
ensure_tool ninja
if [ "$(uname -s)" != "Linux" ]; then
exit 0
fi
if [ "${CC/gcc}" != "${CC}" ]; then
sudo apt-get install ${CC} ${CC/gcc/g++} || exit 1
fi
current_triplet=`gcc -dumpmachine`
if [ "$expected_triplet" = "$current_triplet" ]; then
exit 0
fi
echo ">> Installing toolchain..."
if [ "$expected_triplet" = "x86_64-w64-mingw32" ]; then
gcc_suffix="mingw-w64"
extra_packages="wine"
need_span=yes
else
gcc_suffix="${expected_triplet}"
extra_packages="qemu-user"
fi
sudo apt-get install ${CC}-${gcc_suffix} ${CC/gcc/g++}-${gcc_suffix} ${extra_packages} || exit 1
# gcc9 too old to provide its own span...
if [ -n "$need_span" ]; then
wget -O include/cubescript/span.hpp \
https://raw.githubusercontent.com/tcbrindle/span/master/include/tcb/span.hpp
# custom config
cat << EOF > include/cubescript/cubescript_conf_user.hh
#include "span.hpp"
namespace cubescript {
template<typename T>
using span_type = tcb::span<T>;
}
#define LIBCUBESCRIPT_CONF_USER_SPAN
EOF
fi
exit $?

View File

@ -0,0 +1,15 @@
#!/bin/bash
ninja_version=1.10.2
echo ">> Installing meson..."
mkdir -p host_tools
curl -L -o ninja.zip https://github.com/ninja-build/ninja/releases/download/v${ninja_version}/ninja-win.zip || exit 1
7z x ninja.zip || exit 1
mv ninja.exe host_tools
pip3 install meson
exit 0

12
.gitattributes vendored 100644
View File

@ -0,0 +1,12 @@
*.py text eol=lf
*.yml text eol=lf
*.diff text eol=lf
*.conf text eol=lf
*.md text eol=lf
*.cc text eol=lf
*.hh text eol=lf
*.txt text eol=lf
*.build text eol=lf
*.gitattributes text eol=lf
*.gitignores text eol=lf
*.wrap text eol=lf

86
.github/workflows/build.yaml vendored 100644
View File

@ -0,0 +1,86 @@
name: CI
on: push
jobs:
linux:
name: Linux
runs-on: ubuntu-20.04
env:
TARGET: '${{ matrix.config.target }}'
CC: '${{ matrix.config.cc }}'
BUILDTYPE: '${{ matrix.config.buildtype }}'
strategy:
matrix:
config:
# x86_64: test gcc, clang, + release mode to catch assert bugs
- { target: x86_64-linux-gnu, cc: gcc-10, buildtype: debugoptimized }
- { target: x86_64-linux-gnu, cc: gcc-10, buildtype: release }
- { target: x86_64-linux-gnu, cc: clang, buildtype: debugoptimized }
# 32-bit x86
- { target: i686-linux-gnu, cc: gcc-10, buildtype: debugoptimized }
# all powerpc
- { target: powerpc64le-linux-gnu, cc: gcc-10, buildtype: debugoptimized }
- { target: powerpc64-linux-gnu, cc: gcc-10, buildtype: debugoptimized }
- { target: powerpc-linux-gnu, cc: gcc-10, buildtype: debugoptimized }
# aarch64 and arm
- { target: aarch64-linux-gnu, cc: gcc-10, buildtype: debugoptimized }
- { target: arm-linux-gnueabi, cc: gcc-10, buildtype: debugoptimized }
# riscv64 and s390x
- { target: riscv64-linux-gnu, cc: gcc-10, buildtype: debugoptimized }
- { target: s390x-linux-gnu, cc: gcc-10, buildtype: debugoptimized }
# mips, m68k
- { target: mips-linux-gnu, cc: gcc-10, buildtype: debugoptimized }
- { target: m68k-linux-gnu, cc: gcc-10, buildtype: debug }
# x86_64 windows cross, release mode
- { target: x86_64-w64-mingw32, cc: gcc, buildtype: release }
steps:
- name: Checkout
uses: actions/checkout@v2
with:
persist-credentials: false
- name: Prepare environment
run: bash ./.ci/install-env
- name: Build and test cubescript
run: bash ./.ci/build-cs
windows:
name: Windows
runs-on: windows-2019
steps:
- name: Checkout
uses: actions/checkout@v2
with:
persist-credentials: false
- name: Prepare environment
run: bash ./.ci/install-env-windows
- name: Build and test cubescript
run: bash ./.ci/build-cs-windows
mac:
name: MacOS
runs-on: macos-10.15
env:
TARGET: 'darwin'
CC: 'clang'
steps:
- name: Checkout
uses: actions/checkout@v2
with:
persist-credentials: false
- name: Prepare environment
run: bash ./.ci/install-env
- name: Build and test cubescript
run: bash ./.ci/build-cs

5
.gitignore vendored
View File

@ -1,4 +1,7 @@
subprojects/libostd
*.o
*.core
*.so
build/
.idea/
.vscode/
doc/output

View File

@ -1,54 +1,35 @@
# License
Libcubescript is licensed under the University of Illinois/NCSA Open Source License,
a permissive, non-copyleft, BSD style license. The license text goes as follows:
Libcubescript is provided to you under the terms of the zlib license, just
like the source it was originally derived from.
The software is originally based on the CubeScript implementation in the Cube 2
game/engine, which by now serves mostly as an inspiration, as the code has been
largely rewritten (though isolated bits of the original source may remain).
For copyright holders beyond just CubeScript, please refer to Cube 2's original
license file.
Copyright (c):
* 2001-2015 Wouter "aardappel" van Oortmerssen and Lee "eihrul" Salzman
* 2016 Daniel "q66" Kolesa
* 2016-2020 Daniel "q66" Kolesa
All rights reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal with
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimers.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimers in the
documentation and/or other materials provided with the distribution.
* Neither the names of libcubescript developers nor any contributors may be
used to endorse or promote products derived from this Software without
specific prior written permission.
**THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
SOFTWARE.**
# Original copyright
The software is originally based on the CubeScript implementation in the Cube 2
game/engine, which by now serves mostly as an inspiration, as the code has been
largely rewritten (though isolated bits of the original source may remain); it
was provided under the zlib license and was:
Copyright (c) 2001-2015 Wouter "aardappel" van Oortmerssen and Lee "eihrul" Salzman
For copyright holders beyond just CubeScript, please refer to Cube 2's original
license file. Permission was given by the original copyright holders to re-license
the source code under the same license as the rest of OctaForge projects and this
note now serves to credit the original authors.
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
# Bundled source

187
README.md
View File

@ -1,91 +1,152 @@
# libcubescript
# libcubescript 1.0.0 alpha1
[![Build Status](https://github.com/octaforge/libcubescript/actions/workflows/build.yaml/badge.svg)](https://github.com/octaforge/libcubescript/actions)
![CubeScript REPL](https://ftp.octaforge.org/q66/random/libcs_repl.gif)
## Overview
Libcubescript is an embeddable implementation of the CubeScript scripting
language. CubeScript is the console/config language of the Cube engines/games
(and derived engines/games). It's a simplistic language defined around the
idea of everything being a string, with Lisp-like syntax (allowing various
control structures to be defined as commands).
Cubescript is a minimal scripting language first introduced in the Cube FPS
and carried over into derived games and game engines such as Sauerbraten.
Originally being little more than a few hundred lines of code, serving
primarily as the console and configuration file format of the game, it
grew more advanced features as well as a bytecode VM.
## Benefits and use cases
Nowadays, it is a minimal but relatively fully featured scripting language
based around the concept that everything can be interpreted as a string.
It excels at its original purpose as well as things like text preprocessing.
It comes with a Lisp-like syntax and a variety of standard library functions.
CubeScript is suitable for any use that calls for a simple scripting language
that is easy to embed. It's particularly strong at macro processing, so it can
be used as a preprocessor, or for any string-heavy use. Since it has descended
from a console language for a video game, it can still be used for that very
purpose, as well as a configuration file language.
Libcubescript is a project that aims to provide an independent, improved,
separate implementation of the language, available as a library, intended to
satisfy the needs of the OctaForge project. It was originally forked from
Cubescript as present in the Tesseract game/engine and gradually rewritten;
right now, very little of the original code remains. At language level it is
mostly compatible with the other implementations (although with a stricter
parser and extra features), while the standard library does not aim to be
fully compatible. Some features are also left up to the user to customize,
so that it is not tied to game engines feature-wise.
Its thread-friendliness allows for usage in any context that requires parallel
processing and involvement of the scripting system in it.
Like the codebase it is derived from, it is available under the permissive
zlib license, and therefore compatible with just about anything.
As far as benefits over the original implementation go, while it is based on
the original implementation, it's largely rewritten; thus, it's gained many
advantages, including:
## Benefits and differences
* Independent implementation (can be embedded in any project)
* No global state (multiple CubeScripts in a single program)
* Modern C++17 API (no macros, use of strongly typed enums, lambdas, ranges etc.)
* C++17 lambdas can be used as commands (including captures and type inference)
* Error handling including recovery (protected call system similar to Lua)
* Stricter parsing (strings cannot be left unfinished etc.)
* Loop control statements (`break` and `continue`)
* No manual memory mangement, values manage themselves
* Clean codebase that is easy to read and contribute to
* Support for arbitrary size integers and floats (can be set at compile time)
* Allows building into a static or shared library, supports `-fvisibility=hidden`
There's a variety of things that set this implementation apart:
There are some features that are a work in progress and will come later:
* It's independent and can be embedded in any project
* There is no global state, so you can have as many Cubescripts as you want,
in one program
* Written in C++20, following modern language conventions, both internally
and at API level
* That means the ability to use lambdas as commands, including captures,
type inference and so on
* There is a robust allocator system in place, and all memory the library
uses is allocated through it; that gives you complete control over its
memory (for tracking, sandboxing, limits, etc.)
* A large degree of memory safety, with no manual management
* Strings are interned, with a single reference counted instance of any
string existing at a time, which lowers memory usage and simplifies its
management
* Minimal stack memory usage, which means no artificial limits on recursion
depth as well as safe usage from threads and coroutines with small stacks
* Errors will no longer cause the interpreter to march on, instead acting
like real errors
* Protected calls allow you to catch errors in a similar way to exceptions,
and nearly every error can be caught
* Stricter parsing, with things like unfinished strings being caught
* Loops now have `break` and `continue` statements
* Customizable integer and floating point types
* Full support for symbol visibility in API
* Highly portable and cross-platform, no dependencies other than a compiler
* Clean codebase that is easy to pick up and contribute to
* More helpful debug information (proper line infos at both parse and run time)
* A degree of thread safety (see below)
* Custom allocator support (control over how heap memory is allocated)
* Coroutines
More features and enhancements are planned, such as:
The API is currently very unstable, as is the actual codebase. Therefore you
should not use the project in production environments just yet, but you're
also free to experiment - feedback is welcome.
* Improved support for debugging information (line information tracking
at runtime rather than just compile-time)
* Thread safety
**The project is also open for contributions.** You can use pull requests on
GitHub and there is also a discussion channel `#octaforge` on FreeNode; this
project is a part of the larger OctaForge umbrella.
Right now, the codebase is unstable, but quickly approaching production
readiness. You are encouraged to test things and report bugs; contributions
of any kind are also welcome (you can use pull requests in our Gitea instance
as well as the GitHub mirror).
## Threads and coroutines
Our primary means of communication is the `#octaforge` IRC channel on OFTC.
*(In progress)*
### Threads
Libcubescript supports integration with coroutines and threads by providing a
concept of threads itself. You can create a thread (child state) using the
main state and it will share global data with the main state, but it also
has its own call stack.
The API provides a concept of threads. The first created thread is the main
thread, which owns all variables and most state. Based on the main thread
you can create side threads, which share a lot of state with the main thread
but have their own call stack.
The "global" state is thread safe, allowing concurrent access from multiple
threads. The "local" state can be yielded as a part of the coroutine without
affecting any other threads.
In the future, accesses to "global" state (the state shared between threads)
will be made thread safe.
This functionality is not exposed into the language itself, but it can be
utilized in the outside native code.
That means you will be able to use the library in multithreaded contexts, as
long as you make sure to only use any Cubescript thread from at most one
real thread at a time (accesses to thread state will not be thread-safe).
Right now, this at least means the library is coroutine-safe. You can call
into a Cubescript thread inside a coroutine, yield somewhere mid-command,
and still be able to access the state safely through other Cubescript
threads. Once you resume the coroutine, it will continue where it left
off, without anything being wrong.
Since strings are interned and reference counted, this is also geared
towards thread safety - any API returning a string will give you your own
reference, which means nothing can free it while you are still using it.
Similarly, things taking string references will generally increment the
count for their own purposes. This all happens automatically thanks to
C++'s scoped value handling.
## Building and usage
The only dependency is libostd:
The library has absolutely no dependencies other than a C++20 compiler,
similarly there are no dependencies on system or architecture specific
things, so it should work on any OS and any CPU.
https://git.octaforge.org/tools/libostd.git/
https://github.com/OctaForge/libostd
The C++20 support does not have to be complete. These are the baselines
(which are ensured by the CI):
If libostd can work on your system, so can libcubescript.
* GCC 10
* Clang 10 (with libstdc++ or libc++)
* Microsoft Visual C++ 2019
The supplied Makefile builds a static library on Unix-like OSes. Link this
library together with your application and everything should just work. It also
builds the REPL.
Older compilers generally do not work out of box (but for example, GCC 9
may work if you provide an `std::span` implementation; see the docs for
how, but keep in mind that the resulting library will have incompatible
ABI with newer standard library versions that do provide it).
The project also bundles the linenoise line editing library which has been modified
to compile cleanly as C++ (with the same flags as libcubescript). It's used strictly
for the REPL only (you don't need it to build libcubescript itself). The version
in the repository tracks Git revision https://github.com/antirez/linenoise/commit/c894b9e59f02203dbe4e2be657572cf88c4230c3.
You will need [Meson](https://mesonbuild.com/) to build the project. Most
Unix-like systems have it in their package management, on Windows there is
an installer available on their website. Being written in Python, you can
also use `pip` to get an up to date version on any OS.
## Licensing
Once you have it, compiling is simple, e.g. on Unix-likes you can do:
See COPYING.md for licensing information.
~~~
mkdir build && cd build
meson ..
ninja all
~~~
Refer to Meson's manual for how to customize whether you want a shared or
static library and so on. By default, you will get a shared library plus
a REPL (interactive interpreter). The REPL also serves as an example of
how to use the API.
If you don't want the REPL, use `-Drepl=disabled`. When compiled, it can
have support for line editing and command history. This is provided through
`linenoise` (which is a minimal single-file line editing library bundled
with the project, and is the default). In case you're on a platform that
`linenoise` does not support (highly unlikely), there is a fallback without
any line editing as well. Pass `-Dlinenoise=disabled` to use the fallback.
The version of `linenoise` bundled with the project is `cpp-linenoise`, available
at https://github.com/yhirose/cpp-linenoise. Our version is modified, so that
it builds cleanly with our flags, and so that it supports the "hints" feature
available in original `linenoise`. Other than the modifications, it is baseed
on upstream git revision `a927043cdd5bfe203560802e56a7e7ed43156ed3`. The reason
we use this instead of upstream `linenoise` is Windows support.

2619
doc/Doxyfile 100644

File diff suppressed because it is too large Load Diff

107
doc/main_page.md 100644
View File

@ -0,0 +1,107 @@
# Libcubescript Documentation {#index}
## What is Cubescript?
ubescript is a minimal scripting language first introduced in the Cube FPS
and carried over into derived games and game engines such as Sauerbraten.
Originally being little more than a few hundred lines of code, serving
primarily as the console and configuration file format of the game, it
grew more advanced features as well as a bytecode VM.
Nowadays, it is a minimal but relatively fully featured scripting language
based around the concept that everything can be interpreted as a string.
It excels at its original purpose as well as things like text preprocessing.
It comes with a Lisp-like syntax and a variety of standard library functions.
## What is Libcubescript?
Libcubescript is a project that aims to provide an independent, improved,
separate implementation of the language, available as a library, intended to
satisfy the needs of the OctaForge project. It was originally forked from
Cubescript as present in the Tesseract game/engine and gradually rewritten;
right now, very little of the original code remains. At language level it is
mostly compatible with the other implementations (although with a stricter
parser and extra features), while the standard library does not aim to be
fully compatible. Some features are also left up to the user to customize,
so that it is not tied to game engines feature-wise.
Like the codebase it is derived from, it is available under the permissive
zlib license, and therefore compatible with just about anything.
## Benefits and differences
There's a variety of things that set this implementation apart:
* It's independent and can be embedded in any project
* There is no global state, so you can have as many Cubescripts as you want,
in one program
* Written in C++20, following modern language conventions, both internally
and at API level
* That means the ability to use lambdas as commands, including captures,
type inference and so on
* There is a robust allocator system in place, and all memory the library
uses is allocated through it; that gives you complete control over its
memory (for tracking, sandboxing, limits, etc.)
* A large degree of memory safety, with no manual management
* Strings are interned, with a single reference counted instance of any
string existing at a time, which lowers memory usage and simplifies its
management
* Minimal stack memory usage, which means no artificial limits on recursion
depth as well as safe usage from threads and coroutines with small stacks
* Errors will no longer cause the interpreter to march on, instead acting
like real errors
* Protected calls allow you to catch errors in a similar way to exceptions,
and nearly every error can be caught
* Stricter parsing, with things like unfinished strings being caught
* Loops now have `break` and `continue` statements
* Customizable integer and floating point types
* Full support for symbol visibility in API
* Highly portable and cross-platform, no dependencies other than a compiler
* Clean codebase that is easy to pick up and contribute to
## Building and usage
The library has absolutely no dependencies other than a C++20 compiler,
similarly there are no dependencies on system or architecture specific
things, so it should work on any OS and any CPU.
The C++20 support does not have to be complete. These are the baselines
(which are ensured by the CI):
* GCC 10
* Clang 10 (with libstdc++ or libc++)
* Microsoft Visual C++ 2019
Older versions of either of these are known not to work.
You will need [Meson](https://mesonbuild.com/) to build the project. Most
Unix-like systems have it in their package management, on Windows there is
an installer available on their website. Being written in Python, you can
also use `pip` to get an up to date version on any OS.
Once you have it, compiling is simple, e.g. on Unix-likes you can do:
~~~
mkdir build && cd build
meson ..
ninja all
~~~
Refer to Meson's manual for how to customize whether you want a shared or
static library and so on. By default, you will get a shared library plus
a REPL (interactive interpreter). The REPL also serves as an example of
how to use the API.
If you don't want the REPL, use `-Drepl=disabled`. When compiled, it can
have support for line editing and command history. This is provided through
`linenoise` (which is a minimal single-file line editing library bundled
with the project, and is the default). In case you're on a platform that
`linenoise` does not support (highly unlikely), there is a fallback without
any line editing as well. Pass `-Dlinenoise=disabled` to use the fallback.
The version of `linenoise` bundled with the project is `cpp-linenoise`, available
at https://github.com/yhirose/cpp-linenoise. Our version is modified, so that
it builds cleanly with our flags, and so that it supports the "hints" feature
available in original `linenoise`. Other than the modifications, it is baseed
on upstream git revision `a927043cdd5bfe203560802e56a7e7ed43156ed3`. The reason
we use this instead of upstream `linenoise` is Windows support.

View File

@ -1,869 +1,23 @@
/** @file cubescript.hh
*
* @brief The main include file for the library.
*
* Include this file (like `#include <cubescript/cubescript.hh>`) to access
* the API. You should generally not include the individual sub-files.
*
* @copyright See COPYING.md in the project tree for further information.
*/
#ifndef LIBCUBESCRIPT_CUBESCRIPT_HH
#define LIBCUBESCRIPT_CUBESCRIPT_HH
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <optional>
#include <functional>
#include <type_traits>
#include <utility>
#include "cubescript_conf.hh"
#include <ostd/platform.hh>
#include <ostd/string.hh>
#include <ostd/range.hh>
#include <ostd/io.hh>
#include <ostd/format.hh>
namespace cscript {
using cs_string = std::string;
static_assert(std::is_integral_v<cs_int>, "cs_int must be integral");
static_assert(std::is_signed_v<cs_int>, "cs_int must be signed");
static_assert(std::is_floating_point_v<cs_float>, "cs_float must be floating point");
struct cs_internal_error: std::runtime_error {
using std::runtime_error::runtime_error;
};
enum {
CS_IDF_PERSIST = 1 << 0,
CS_IDF_OVERRIDE = 1 << 1,
CS_IDF_HEX = 1 << 2,
CS_IDF_READONLY = 1 << 3,
CS_IDF_OVERRIDDEN = 1 << 4,
CS_IDF_UNKNOWN = 1 << 5,
CS_IDF_ARG = 1 << 6
};
struct cs_bcode;
struct OSTD_EXPORT cs_bcode_ref {
cs_bcode_ref():
p_code(nullptr)
{}
cs_bcode_ref(cs_bcode *v);
cs_bcode_ref(cs_bcode_ref const &v);
cs_bcode_ref(cs_bcode_ref &&v):
p_code(v.p_code)
{
v.p_code = nullptr;
}
~cs_bcode_ref();
cs_bcode_ref &operator=(cs_bcode_ref const &v);
cs_bcode_ref &operator=(cs_bcode_ref &&v);
operator bool() const { return p_code != nullptr; }
operator cs_bcode *() const { return p_code; }
private:
cs_bcode *p_code;
};
OSTD_EXPORT bool cs_code_is_empty(cs_bcode *code);
enum class cs_value_type {
Null = 0, Int, Float, String, Cstring, Code, Macro, Ident
};
struct OSTD_EXPORT cs_value {
cs_value();
~cs_value();
cs_value(cs_value const &);
cs_value(cs_value &&);
cs_value &operator=(cs_value const &v);
cs_value &operator=(cs_value &&v);
cs_value_type get_type() const;
void set_int(cs_int val);
void set_float(cs_float val);
void set_str(cs_string val);
void set_null();
void set_code(cs_bcode *val);
void set_cstr(ostd::string_range val);
void set_ident(cs_ident *val);
void set_macro(ostd::string_range val);
cs_string get_str() const;
ostd::string_range get_strr() const;
cs_int get_int() const;
cs_float get_float() const;
cs_bcode *get_code() const;
cs_ident *get_ident() const;
void get_val(cs_value &r) const;
bool get_bool() const;
void force_null();
cs_float force_float();
cs_int force_int();
ostd::string_range force_str();
bool code_is_empty() const;
private:
std::aligned_union_t<1, cs_int, cs_float, void *> p_stor;
size_t p_len;
cs_value_type p_type;
};
struct cs_ident_stack {
cs_value val_s;
cs_ident_stack *next;
};
struct cs_shared_state;
struct cs_error;
struct cs_gen_state;
enum class cs_ident_type {
Ivar = 0, Fvar, Svar, Command, Alias, Special
};
struct cs_var;
struct cs_ivar;
struct cs_fvar;
struct cs_svar;
struct cs_alias;
struct cs_command;
struct OSTD_EXPORT cs_ident {
friend struct cs_state;
friend struct cs_shared_state;
cs_ident() = delete;
cs_ident(cs_ident const &) = delete;
cs_ident(cs_ident &&) = delete;
/* trigger destructors for all inherited members properly */
virtual ~cs_ident() {};
cs_ident &operator=(cs_ident const &) = delete;
cs_ident &operator=(cs_ident &&) = delete;
cs_ident_type get_type() const;
ostd::string_range get_name() const;
int get_flags() const;
int get_index() const;
bool is_alias() const;
cs_alias *get_alias();
cs_alias const *get_alias() const;
bool is_command() const;
cs_command *get_command();
cs_command const *get_command() const;
bool is_special() const;
bool is_var() const;
cs_var *get_var();
cs_var const *get_var() const;
bool is_ivar() const;
cs_ivar *get_ivar();
cs_ivar const *get_ivar() const;
bool is_fvar() const;
cs_fvar *get_fvar();
cs_fvar const *get_fvar() const;
bool is_svar() const;
cs_svar *get_svar();
cs_svar const *get_svar() const;
int get_type_raw() const {
return p_type;
}
protected:
cs_ident(cs_ident_type tp, ostd::string_range name, int flags = 0);
cs_string p_name;
/* represents the cs_ident_type above, but internally it has a wider variety
* of values, so it's an int here (maps to an internal enum)
*/
int p_type, p_flags;
private:
int p_index = -1;
};
struct OSTD_EXPORT cs_var: cs_ident {
friend struct cs_state;
friend struct cs_shared_state;
protected:
cs_var(cs_ident_type tp, ostd::string_range name, cs_var_cb func, int flags = 0);
private:
cs_var_cb cb_var;
virtual cs_string to_printable() const = 0;
void changed(cs_state &cs) {
if (cb_var) {
cb_var(cs, *this);
}
}
};
struct OSTD_EXPORT cs_ivar: cs_var {
friend struct cs_state;
friend struct cs_shared_state;
cs_int get_val_min() const;
cs_int get_val_max() const;
cs_int get_value() const;
void set_value(cs_int val);
cs_string to_printable() const final;
private:
cs_ivar(
ostd::string_range n, cs_int m, cs_int x, cs_int v, cs_var_cb f, int flags
);
cs_int p_storage, p_minval, p_maxval, p_overrideval;
};
struct OSTD_EXPORT cs_fvar: cs_var {
friend struct cs_state;
friend struct cs_shared_state;
cs_float get_val_min() const;
cs_float get_val_max() const;
cs_float get_value() const;
void set_value(cs_float val);
cs_string to_printable() const final;
private:
cs_fvar(
ostd::string_range n, cs_float m, cs_float x, cs_float v,
cs_var_cb f, int flags
);
cs_float p_storage, p_minval, p_maxval, p_overrideval;
};
struct OSTD_EXPORT cs_svar: cs_var {
friend struct cs_state;
friend struct cs_shared_state;
ostd::string_range get_value() const;
void set_value(cs_string val);
cs_string to_printable() const final;
private:
cs_svar(ostd::string_range n, cs_string v, cs_var_cb f, int flags);
cs_string p_storage, p_overrideval;
};
struct OSTD_EXPORT cs_alias: cs_ident {
friend struct cs_state;
friend struct cs_shared_state;
friend struct cs_alias_internal;
cs_value const &get_value() const {
return p_val;
}
cs_value &get_value() {
return p_val;
}
void get_cstr(cs_value &v) const;
void get_cval(cs_value &v) const;
private:
cs_alias(ostd::string_range n, cs_string a, int flags);
cs_alias(ostd::string_range n, cs_int a, int flags);
cs_alias(ostd::string_range n, cs_float a, int flags);
cs_alias(ostd::string_range n, int flags);
cs_alias(ostd::string_range n, cs_value v, int flags);
cs_bcode *p_acode;
cs_ident_stack *p_astack;
cs_value p_val;
};
struct cs_command: cs_ident {
friend struct cs_state;
friend struct cs_shared_state;
friend struct cs_cmd_internal;
ostd::string_range get_args() const;
int get_num_args() const;
private:
cs_command(
ostd::string_range name, ostd::string_range args,
int numargs, cs_command_cb func
);
cs_string p_cargs;
cs_command_cb p_cb_cftv;
int p_numargs;
};
struct cs_identLink;
enum {
CsLibMath = 1 << 0,
CsLibString = 1 << 1,
CsLibList = 1 << 2,
CsLibAll = 0b111
};
enum class CsLoopState {
Normal = 0, Break, Continue
};
static inline void *cs_default_alloc(void *, void *p, size_t, size_t ns) {
if (!ns) {
delete[] static_cast<unsigned char *>(p);
return nullptr;
}
return new unsigned char[ns];
}
struct OSTD_EXPORT cs_state {
friend struct cs_error;
friend struct cs_gen_state;
cs_shared_state *p_state;
cs_identLink *p_callstack = nullptr;
int identflags = 0;
cs_state(cs_alloc_cb func = cs_default_alloc, void *data = nullptr);
virtual ~cs_state();
cs_state(cs_state const &) = delete;
cs_state(cs_state &&s) {
swap(s);
}
cs_state &operator=(cs_state const &) = delete;
cs_state &operator=(cs_state &&s) {
swap(s);
s.destroy();
return *this;
}
void destroy();
void swap(cs_state &s) {
std::swap(p_state, s.p_state);
std::swap(p_callstack, s.p_callstack);
std::swap(identflags, s.identflags);
std::swap(p_pstate, s.p_pstate);
std::swap(p_inloop, s.p_inloop);
std::swap(p_owner, s.p_owner);
std::swap(p_callhook, s.p_callhook);
}
cs_state new_thread();
cs_hook_cb set_call_hook(cs_hook_cb func);
cs_hook_cb const &get_call_hook() const;
cs_hook_cb &get_call_hook();
void init_libs(int libs = CsLibAll);
void clear_override(cs_ident &id);
void clear_overrides();
cs_ident *new_ident(ostd::string_range name, int flags = CS_IDF_UNKNOWN);
cs_ident *force_ident(cs_value &v);
cs_ivar *new_ivar(
ostd::string_range n, cs_int m, cs_int x, cs_int v,
cs_var_cb f = cs_var_cb(), int flags = 0
);
cs_fvar *new_fvar(
ostd::string_range n, cs_float m, cs_float x, cs_float v,
cs_var_cb f = cs_var_cb(), int flags = 0
);
cs_svar *new_svar(
ostd::string_range n, cs_string v,
cs_var_cb f = cs_var_cb(), int flags = 0
);
cs_command *new_command(
ostd::string_range name, ostd::string_range args, cs_command_cb func
);
cs_ident *get_ident(ostd::string_range name);
cs_alias *get_alias(ostd::string_range name);
bool have_ident(ostd::string_range name);
cs_ident_r get_idents();
cs_const_ident_r get_idents() const;
void reset_var(ostd::string_range name);
void touch_var(ostd::string_range name);
cs_string run_str(cs_bcode *code);
cs_string run_str(ostd::string_range code);
cs_string run_str(cs_ident *id, cs_value_r args);
cs_int run_int(cs_bcode *code);
cs_int run_int(ostd::string_range code);
cs_int run_int(cs_ident *id, cs_value_r args);
cs_float run_float(cs_bcode *code);
cs_float run_float(ostd::string_range code);
cs_float run_float(cs_ident *id, cs_value_r args);
bool run_bool(cs_bcode *code);
bool run_bool(ostd::string_range code);
bool run_bool(cs_ident *id, cs_value_r args);
void run(cs_bcode *code, cs_value &ret);
void run(ostd::string_range code, cs_value &ret);
void run(cs_ident *id, cs_value_r args, cs_value &ret);
void run(cs_bcode *code);
void run(ostd::string_range code);
void run(cs_ident *id, cs_value_r args);
CsLoopState run_loop(cs_bcode *code, cs_value &ret);
CsLoopState run_loop(cs_bcode *code);
bool is_in_loop() const {
return p_inloop;
}
std::optional<cs_string> run_file_str(ostd::string_range fname);
std::optional<cs_int> run_file_int(ostd::string_range fname);
std::optional<cs_float> run_file_float(ostd::string_range fname);
std::optional<bool> run_file_bool(ostd::string_range fname);
bool run_file(ostd::string_range fname, cs_value &ret);
bool run_file(ostd::string_range fname);
void set_alias(ostd::string_range name, cs_value v);
void set_var_int(
ostd::string_range name, cs_int v,
bool dofunc = true, bool doclamp = true
);
void set_var_float(
ostd::string_range name, cs_float v,
bool dofunc = true, bool doclamp = true
);
void set_var_str(
ostd::string_range name, ostd::string_range v, bool dofunc = true
);
void set_var_int_checked(cs_ivar *iv, cs_int v);
void set_var_int_checked(cs_ivar *iv, cs_value_r args);
void set_var_float_checked(cs_fvar *fv, cs_float v);
void set_var_str_checked(cs_svar *fv, ostd::string_range v);
std::optional<cs_int> get_var_int(ostd::string_range name);
std::optional<cs_float> get_var_float(ostd::string_range name);
std::optional<cs_string> get_var_str(ostd::string_range name);
std::optional<cs_int> get_var_min_int(ostd::string_range name);
std::optional<cs_int> get_var_max_int(ostd::string_range name);
std::optional<cs_float> get_var_min_float(ostd::string_range name);
std::optional<cs_float> get_var_max_float(ostd::string_range name);
std::optional<cs_string> get_alias_val(ostd::string_range name);
virtual void print_var(cs_var *v);
private:
OSTD_LOCAL cs_state(cs_shared_state *s);
cs_ident *add_ident(cs_ident *id);
OSTD_LOCAL void *alloc(void *ptr, size_t olds, size_t news);
cs_gen_state *p_pstate = nullptr;
int p_inloop = 0;
bool p_owner = false;
char p_errbuf[512];
cs_hook_cb p_callhook;
};
struct cs_stack_state_node {
cs_stack_state_node const *next;
cs_ident const *id;
int index;
};
struct cs_stack_state {
cs_stack_state() = delete;
cs_stack_state(cs_state &cs, cs_stack_state_node *nd = nullptr, bool gap = false);
cs_stack_state(cs_stack_state const &) = delete;
cs_stack_state(cs_stack_state &&st);
~cs_stack_state();
cs_stack_state &operator=(cs_stack_state const &) = delete;
cs_stack_state &operator=(cs_stack_state &&);
cs_stack_state_node const *get() const;
bool gap() const;
private:
cs_state &p_state;
cs_stack_state_node *p_node;
bool p_gap;
};
struct cs_error {
friend struct cs_state;
cs_error() = delete;
cs_error(cs_error const &) = delete;
cs_error(cs_error &&v):
p_errmsg(v.p_errmsg), p_stack(std::move(v.p_stack))
{}
ostd::string_range what() const {
return p_errmsg;
}
cs_stack_state &get_stack() {
return p_stack;
}
cs_stack_state const &get_stack() const {
return p_stack;
}
cs_error(cs_state &cs, ostd::string_range msg):
p_errmsg(), p_stack(cs)
{
p_errmsg = save_msg(cs, msg);
p_stack = save_stack(cs);
}
template<typename ...A>
cs_error(cs_state &cs, ostd::string_range msg, A &&...args):
p_errmsg(), p_stack(cs)
{
try {
char fbuf[512];
auto ret = ostd::format(
ostd::counting_sink(ostd::char_range(fbuf, fbuf + sizeof(fbuf))),
msg, std::forward<A>(args)...
).get_written();
p_errmsg = save_msg(cs, ostd::char_range(fbuf, fbuf + ret));
} catch (...) {
p_errmsg = save_msg(cs, msg);
}
p_stack = save_stack(cs);
}
private:
cs_stack_state save_stack(cs_state &cs);
ostd::string_range save_msg(cs_state &cs, ostd::string_range v);
ostd::string_range p_errmsg;
cs_stack_state p_stack;
};
struct OSTD_EXPORT cs_stacked_value: cs_value {
cs_stacked_value(cs_ident *id = nullptr);
~cs_stacked_value();
cs_stacked_value(cs_stacked_value const &) = delete;
cs_stacked_value(cs_stacked_value &&) = delete;
cs_stacked_value &operator=(cs_stacked_value const &) = delete;
cs_stacked_value &operator=(cs_stacked_value &&v) = delete;
cs_stacked_value &operator=(cs_value const &v);
cs_stacked_value &operator=(cs_value &&v);
bool set_alias(cs_ident *id);
cs_alias *get_alias() const;
bool has_alias() const;
bool push();
bool pop();
private:
cs_alias *p_a;
cs_ident_stack p_stack;
bool p_pushed;
};
namespace util {
template<typename R>
inline R &&escape_string(R &&writer, ostd::string_range str) {
using namespace ostd::string_literals;
writer.put('"');
for (; !str.empty(); str.pop_front()) {
switch (str.front()) {
case '\n':
ostd::range_put_all(writer, "^n"_sr);
break;
case '\t':
ostd::range_put_all(writer, "^t"_sr);
break;
case '\f':
ostd::range_put_all(writer, "^f"_sr);
break;
case '"':
ostd::range_put_all(writer, "^\""_sr);
break;
case '^':
ostd::range_put_all(writer, "^^"_sr);
break;
default:
writer.put(str.front());
break;
}
}
writer.put('"');
return std::forward<R>(writer);
}
template<typename R>
inline R &&unescape_string(R &&writer, ostd::string_range str) {
for (; !str.empty(); str.pop_front()) {
if (str.front() == '^') {
str.pop_front();
if (str.empty()) {
break;
}
switch (str.front()) {
case 'n':
writer.put('\n');
break;
case 't':
writer.put('\r');
break;
case 'f':
writer.put('\f');
break;
case '"':
writer.put('"');
break;
case '^':
writer.put('^');
break;
default:
writer.put(str.front());
break;
}
} else if (str.front() == '\\') {
str.pop_front();
if (str.empty()) {
break;
}
char c = str.front();
if ((c == '\r') || (c == '\n')) {
if (!str.empty() && (c == '\r') && (str.front() == '\n')) {
str.pop_front();
}
continue;
}
writer.put('\\');
} else {
writer.put(str.front());
}
}
return std::forward<R>(writer);
}
OSTD_EXPORT ostd::string_range parse_string(
cs_state &cs, ostd::string_range str, size_t &nlines
);
inline ostd::string_range parse_string(
cs_state &cs, ostd::string_range str
) {
size_t nlines;
return parse_string(cs, str, nlines);
}
OSTD_EXPORT ostd::string_range parse_word(
cs_state &cs, ostd::string_range str
);
struct list_range;
struct OSTD_EXPORT list_parser {
list_parser() = delete;
list_parser(cs_state &cs, ostd::string_range src):
p_state(cs), p_input(src)
{}
void skip();
bool parse();
size_t count();
template<typename R>
R &&get_item(R &&writer) const {
if (!p_quote.empty() && (*p_quote == '"')) {
return unescape_string(std::forward<R>(writer), p_item);
} else {
ostd::range_put_all(writer, p_item);
return std::forward<R>(writer);
}
}
cs_string get_item() const {
return std::move(get_item(ostd::appender<cs_string>()).get());
}
ostd::string_range &get_raw_item(bool quoted = false) {
return quoted ? p_quote : p_item;
}
ostd::string_range const &get_raw_item(bool quoted = false) const {
return quoted ? p_quote : p_item;
}
ostd::string_range &get_input() {
return p_input;
}
list_range iter() noexcept;
private:
ostd::string_range p_quote = ostd::string_range();
ostd::string_range p_item = ostd::string_range();
cs_state &p_state;
ostd::string_range p_input;
};
struct list_range: ostd::input_range<list_range> {
using range_category = ostd::forward_range_tag;
using value_type = ostd::string_range;
using reference = ostd::string_range;
using size_type = std::size_t;
list_range() = delete;
list_range(list_parser &p) noexcept: p_parser(&p) {
pop_front();
}
bool empty() const noexcept {
return !bool(p_item);
}
void pop_front() noexcept {
if (p_parser->parse()) {
p_item = p_parser->get_item();
} else {
p_item.reset();
}
}
ostd::string_range front() const noexcept {
return *p_item;
}
private:
list_parser *p_parser;
std::optional<cs_string> p_item{};
};
inline list_range list_parser::iter() noexcept {
return list_range{*this};
}
template<typename R>
inline void format_int(R &&writer, cs_int val) {
try {
ostd::format(std::forward<R>(writer), IntFormat, val);
} catch (ostd::format_error const &e) {
throw cs_internal_error{e.what()};
}
}
template<typename R>
inline void format_float(R &&writer, cs_float val) {
try {
ostd::format(
std::forward<R>(writer),
(val == cs_int(val)) ? RoundFloatFormat : FloatFormat, val
);
} catch (ostd::format_error const &e) {
throw cs_internal_error{e.what()};
}
}
template<typename R>
inline void tvals_concat(
R &&writer, cs_value_r vals,
ostd::string_range sep = ostd::string_range()
) {
for (size_t i = 0; i < vals.size(); ++i) {
switch (vals[i].get_type()) {
case cs_value_type::Int: {
format_int(
std::forward<R>(writer), vals[i].get_int()
);
break;
}
case cs_value_type::Float: {
format_float(
std::forward<R>(writer), vals[i].get_float()
);
break;
}
case cs_value_type::String:
case cs_value_type::Cstring:
case cs_value_type::Macro: {
ostd::range_put_all(writer, vals[i].get_strr());
break;
}
default:
break;
}
if (i == (vals.size() - 1)) {
break;
}
ostd::range_put_all(writer, sep);
}
}
template<typename R>
inline void print_stack(R &&writer, cs_stack_state const &st) {
auto nd = st.get();
while (nd) {
try {
ostd::format(
std::forward<R>(writer),
((nd->index == 1) && st.gap())
? " ..%d) %s" : " %d) %s",
nd->index, nd->id->get_name()
);
} catch (ostd::format_error const &e) {
throw cs_internal_error{e.what()};
}
nd = nd->next;
if (nd) {
writer.put('\n');
}
}
}
} /* namespace util */
} /* namespace cscript */
#include "cubescript/platform.hh"
#include "cubescript/error.hh"
#include "cubescript/value.hh"
#include "cubescript/ident.hh"
#include "cubescript/state.hh"
#include "cubescript/util.hh"
#endif /* LIBCUBESCRIPT_CUBESCRIPT_HH */

View File

@ -0,0 +1,223 @@
/** @file callable.hh
*
* @brief Internal callable data structure.
*
* There is no public API in this file.
*
* @copyright See COPYING.md in the project tree for further information.
*/
#ifndef LIBCUBESCRIPT_CUBESCRIPT_CALLABLE_HH
#define LIBCUBESCRIPT_CUBESCRIPT_CALLABLE_HH
#include <cstring>
#include <cstddef>
#include <utility>
#include <type_traits>
#include <functional>
#include <memory>
namespace cubescript {
namespace internal {
/** @private */
template<typename R, typename ...A>
struct callable {
private:
struct base {
base(base const &);
base &operator=(base const &);
public:
base() {}
virtual ~base() {}
virtual void move_to(base *) = 0;
virtual R operator()(A &&...args) const = 0;
};
template<typename F>
struct store: base {
explicit store(F &&f): p_stor{std::move(f)} {}
virtual void move_to(base *p) {
::new (p) store{std::move(p_stor)};
}
virtual R operator()(A &&...args) const {
return std::invoke(*std::launder(
reinterpret_cast<F const *>(&p_stor)
), std::forward<A>(args)...);
}
private:
F p_stor;
};
using alloc_f = void *(*)(void *, void *, std::size_t, std::size_t);
struct f_alloc {
alloc_f af;
void *ud;
size_t asize;
};
std::aligned_storage_t<sizeof(void *) * 4> p_stor;
base *p_func;
static inline base *as_base(void *p) {
return static_cast<base *>(p);
}
template<typename T>
static inline bool f_not_null(T const &) { return true; }
template<typename T>
static inline bool f_not_null(T *p) { return !!p; }
template<typename CR, typename C>
static inline bool f_not_null(CR C::*p) { return !!p; }
template<typename T>
static inline bool f_not_null(callable<T> const &f) { return !!f; }
bool small_storage() {
return (static_cast<void *>(p_func) == &p_stor);
}
void cleanup() {
if (!p_func) {
return;
}
p_func->~base();
if (!small_storage()) {
auto &ad = *std::launder(reinterpret_cast<f_alloc *>(&p_stor));
ad.af(ad.ud, p_func, ad.asize, 0);
}
}
public:
callable() noexcept: p_func{nullptr} {}
callable(std::nullptr_t) noexcept: p_func{nullptr} {}
callable(std::nullptr_t, alloc_f, void *) noexcept: p_func{nullptr} {}
callable(callable &&f) noexcept {
if (!f.p_func) {
p_func = nullptr;
} else if (f.small_storage()) {
p_func = as_base(&p_stor);
f.p_func->move_to(p_func);
} else {
p_func = f.p_func;
f.p_func = nullptr;
}
}
template<typename F>
callable(F func, alloc_f af, void *ud) {
if (!f_not_null(func)) {
return;
}
if constexpr (sizeof(store<F>) <= sizeof(p_stor)) {
auto *p = static_cast<void *>(&p_stor);
p_func = ::new (p) store<F>{std::move(func)};
} else {
auto &ad = *std::launder(reinterpret_cast<f_alloc *>(&p_stor));
ad.af = af;
ad.ud = ud;
ad.asize = sizeof(store<F>);
p_func = static_cast<store<F> *>(
af(ud, nullptr, 0, sizeof(store<F>))
);
try {
new (p_func) store<F>{std::move(func)};
} catch (...) {
af(ud, p_func, sizeof(store<F>), 0);
throw;
}
}
}
callable &operator=(callable const &) = delete;
callable &operator=(callable &&f) noexcept {
cleanup();
if (f.p_func == nullptr) {
p_func = nullptr;
} else if (f.small_storage()) {
p_func = as_base(&p_stor);
f.p_func->move_to(p_func);
} else {
p_func = f.p_func;
f.p_func = nullptr;
}
return *this;
}
callable &operator=(std::nullptr_t) noexcept {
cleanup();
p_func = nullptr;
return *this;
}
template<typename F>
callable &operator=(F &&func) {
callable{std::forward<F>(func)}.swap(*this);
return *this;
}
~callable() {
cleanup();
}
void swap(callable &f) noexcept {
std::aligned_storage_t<sizeof(p_stor)> tmp_stor;
if (small_storage() && f.small_storage()) {
auto *t = as_base(&tmp_stor);
p_func->move_to(t);
p_func->~base();
p_func = nullptr;
f.p_func->move_to(as_base(&p_stor));
f.p_func->~base();
f.p_func = nullptr;
p_func = as_base(&p_stor);
t->move_to(as_base(&f.p_stor));
t->~base();
f.p_func = as_base(&f.p_stor);
} else if (small_storage()) {
/* copy allocator address/size */
std::memcpy(&tmp_stor, &f.p_stor, sizeof(tmp_stor));
p_func->move_to(as_base(&f.p_stor));
p_func->~base();
p_func = f.p_func;
f.p_func = as_base(&f.p_stor);
std::memcpy(&p_stor, &tmp_stor, sizeof(tmp_stor));
} else if (f.small_storage()) {
/* copy allocator address/size */
std::memcpy(&tmp_stor, &p_stor, sizeof(tmp_stor));
f.p_func->move_to(as_base(&p_stor));
f.p_func->~base();
f.p_func = p_func;
p_func = as_base(&p_stor);
std::memcpy(&f.p_stor, &tmp_stor, sizeof(tmp_stor));
} else {
/* copy allocator address/size */
std::memcpy(&tmp_stor, &p_stor, sizeof(tmp_stor));
std::memcpy(&p_stor, &f.p_stor, sizeof(tmp_stor));
std::memcpy(&f.p_stor, &tmp_stor, sizeof(tmp_stor));
std::swap(p_func, f.p_func);
}
}
explicit operator bool() const noexcept {
return !!p_func;
}
R operator()(A ...args) const {
return (*p_func)(std::forward<A>(args)...);
}
};
} /* namespace internal */
} /* namespace cubescript */
#endif /* LIBCUBESCRIPT_CUBESCRIPT_CALLABLE_HH */

View File

@ -0,0 +1,88 @@
/** @file error.hh
*
* @brief Error handling API.
*
* Defines structures and methods used for error handling in the library.
*
* @copyright See COPYING.md in the project tree for further information.
*/
#ifndef LIBCUBESCRIPT_CUBESCRIPT_ERROR_HH
#define LIBCUBESCRIPT_CUBESCRIPT_ERROR_HH
#include <string_view>
#include <stdexcept>
#include <utility>
namespace cubescript {
struct state;
/** @brief Represents a Cubescript error.
*
* This is a standard error that can be thrown by either the Cubescript APIs
* or from the language itself (either by the user, or by incorrect use of
* the API).
*
* It has a message attached, as well as the current state of the call stack,
* represented as cubescript::stack_state.
*
* Each Cubescript thread internally stores a buffer for the error message,
* which is reused for each error raised from that thread.
*/
struct LIBCUBESCRIPT_EXPORT error {
/** @brief A node in the call stack.
*
* The nodes are indexed. The bottommost node has index 1, the topmost
* node has index N (where N is the number of levels the call stack has).
*
* There can be a gap in the stack (i.e. the bottommost node will have
* index 1 and the one above it greater than 2). The gap is controlled
* by the value of the `dbgalias` cubescript variable at the time of
* creation of the error (the stack list will contain at most N nodes).
*
* When getting the stack state, it will be represented as a span with
* the first element being the topmost node and the last element being
* the bottommost (index 1).
*/
struct stack_node {
struct ident const &id; /**< @brief The ident of this level. */
std::size_t index; /**< @brief The level index. */
};
error() = delete;
error(error const &) = delete;
/** @brief Errors are move constructible. */
error(error &&v);
error &operator=(error const &) = delete;
/** @brief Errors are move assignable. */
error &operator=(error &&v);
/** @brief Construct an error using a string. */
error(state &cs, std::string_view msg);
/** @brief Destroy the error. */
~error();
/** @brief Get a view of the error message. */
std::string_view what() const;
/** @brief Get the call stack state at the point of error. */
span_type<stack_node const> stack() const;
private:
friend struct error_p;
error(state &cs, char const *errbeg, char const *errend);
char const *p_errbeg, *p_errend;
stack_node *p_sbeg, *p_send;
state *p_state;
};
} /* namespace cubescript */
#endif /* LIBCUBESCRIPT_CUBESCRIPT_ERROR_HH */

View File

@ -0,0 +1,356 @@
/** @file ident.hh
*
* @brief Identifier management.
*
* Identifiers in `libcubescript` represent variables, aliases, commands
* and so on. This file contains the handles for those and everything you
* need to interface with them.
*
* @copyright See COPYING.md in the project tree for further information.
*/
#ifndef LIBCUBESCRIPT_CUBESCRIPT_IDENT_HH
#define LIBCUBESCRIPT_CUBESCRIPT_IDENT_HH
#include <string_view>
#include "value.hh"
namespace cubescript {
/** @brief The type of the ident.
*
* Cubescript has a selection of idents. This represents the type of each.
*/
enum class ident_type {
VAR = 0, /**< @brief Builtin variable. */
COMMAND, /**< @brief Builtin command. */
ALIAS, /**< @brief User assigned variable. */
SPECIAL /**< @brief Other (internal unexposed type). */
};
/** @brief The ident structure.
*
* Every object within the Cubescript language is represented with an ident.
* This is the generic base interface. There are some operations that are
* available on any ident.
*
* You can also check the actual type with it (cubescript::ident_type) and
* decide to cast it to its appropriate specific type, or use the helpers.
*
* An ident always has a valid name. A valid name is pretty much any
* valid Cubescript word (see cubescript::parse_word()) which does not
* begin with a number (a digit, a `+` or `-` followed by a digit or a
* period followed by a digit, or a period followed by a digit).
*/
struct LIBCUBESCRIPT_EXPORT ident {
/** @brief Get the cubescript::ident_type of this ident. */
ident_type type() const;
/** @brief Get a view to the name of the ident. */
std::string_view name() const;
/** @brief Get the index of the ident.
*
* Idents are internally indexed. There is no guarantee of what index
* the ident will have, but you can still use it to identify the object
* with an integer (it is guaranteed that once created, it will stay the
* same for the whole lifetime of the main thread).
*/
int index() const;
/** @brief Check if the idents are the same. */
bool operator==(ident &other) const;
/** @brief Check if the idents are not the same. */
bool operator!=(ident &other) const;
/** @brief Get if the ident is overridden.
*
* This can be true for aliases or builtins. When an alias or a builtin
* is assigned to and the VM is in override mode or the builtin is
* var_type::OVERRIDABLE, they are marked as overridden (and builtins
* have their value saved beforehand).
*
* This can be cleared later, which will erase the value (for aliases)
* or restore the saved one (for builtins). For aliases, this can be
* specific to the Cubescript thread.
*/
bool is_overridden(state &cs) const;
/** @brief Get if the ident is persistent.
*
* This can be true in two cases. Either it's a builtin and it has the
* var_type::PERSISTENT flag, or it's an alias that is assigned to while
* the VM is in persist mode. The latter can be thread specific (when the
* alias is currently pushed).
*/
bool is_persistent(state &cs) const;
/** @brief Call the ident.
*
* The default implementation just throws a cubescript::error, since it
* is not callable. It can be overridden as needed.
*
* If a command, it will simply be executed with the given arguments,
* ensuring that missing ones are filled in and types are set properly.
* If a builtin variable, the appropriate handler will be called. If
* an alias, the value of it will be compiled and executed. Any other
* ident type will simply do nothing.
*
* @return the return value
*/
virtual any_value call(span_type<any_value> args, state &cs);
protected:
friend struct ident_p;
ident() = default;
virtual ~ident();
struct ident_impl *p_impl{};
};
/** @brief An additional cubescript::builtin_var type.
*
* Global vars can have no additional type, or they can be persistent, or
* they can be overridable. Persistent variables are meant to be saved and
* loaded later (the actual logic is up to the user of the library).
*
* Overridable variables are overridden when assigned to (this can also
* happen to normal variables when the VM is in override mode), which saves
* their old value (which can be restored later when un-overridden). This
* is mutually exclusive; overridable variables cannot be persistent, and
* attempting to assign to a persistent variable while the VM is in override
* mode will raise an error.
*/
enum class var_type {
DEFAULT = 0, /**< @brief The default type. */
PERSISTENT, /**< @brief Persistent variable. */
OVERRIDABLE /**< @brief Overridable variable. */
};
/** @brief A builtin variable.
*
* This represents a strictly typed variable (integer, float or string,
* depending on the value it is created with) that is not subject to
* usual rules like aliases (e.g. scoping). It can have additional
* inherent properties such as being read-only or peresistent, and
* can be monitored via a trigger callback.
*/
struct LIBCUBESCRIPT_EXPORT builtin_var: ident {
/** @brief Get whether the variable is read only.
*
* Variables can be set as read only during their creation (but not
* later). This will prevent assignments to them from within the language
* or using checked APIs, but it is still possible to assign to them
* using raw APIs. The raw APIs will not invoke value triggers, however.
*/
bool is_read_only() const;
/** @brief Get whether the variable is overridable.
*
* Equivalent to `variable_type() == var_type::OVERRIDABLE`.
*/
bool is_overridable() const;
/** @brief Get the cubescript::var_type of the variable. */
var_type variable_type() const;
/** @brief Save the variable.
*
* This is mainly intended for variable assignment triggers. If the
* variable is overridable or the given thread is in override mode,
* this will save the current value of the variable (if not already
* overridden). Otherwise, it will clear any existing overridden flag.
*
* @throw cubescript::error if the thread is in override mode and the
* variable is persistent.
*/
void save(state &cs);
/** @brief Call the variable.
*
* While variables are not callable by themselves, this acts like
* if calling the variable in the language. By default, that means
* doing it with zero arguments retrieves its value, while passing
* arguments will set its value. The actual semantics depend on how
* the handler is set up for each variable type.
*/
any_value call(span_type<any_value> args, state &cs);
/** @brief Get the value of the variable. */
any_value value() const;
/** @brief Set the value of the variable in a raw manner.
*
* This will always set the value and ignore any kinds of checks. It will
* not invoke any triggers either, nor it will save the the value. However,
* it will make sure to preserve the type (integer, float or string).
*/
void set_raw_value(state &cs, any_value val);
/** @brief Set the value of the variable.
*
* If read only, an error is raised. If `do_write` is `false`, nothing
* will be performed other than the read-only checking. If `trigger` is
* `false`, a potential variable change trigger command will not be
* invoked. The value is saved with save(), assuming `do_write` is `true`.
* After that, set_raw_value() is invoked, and then the trigger.
*
* @throw cubescript::error if read only or if the changed trigger throws.
*/
void set_value(
state &cs, any_value val, bool do_write = true, bool trigger = true
);
protected:
builtin_var() = default;
};
/** @brief An alias.
*
* An alias is an ident that is created inside the language, for example
* by assignment. Any variable that you can assign to or look up and is not
* a builtin is an alias. Aliases don't have special assignment syntax nor
* they have changed triggers nor value saving. They technically always
* represent a string within the language, though on C++ side they can
* have float or integer values too.
*/
struct LIBCUBESCRIPT_EXPORT alias: ident {
/** @brief Get if this alias represents a function argument.
*
* This is true for `argN` aliases representing the arguments passed to
* the current function.
*/
bool is_arg() const;
/** @brief Get the value of the alias for the given thread. */
any_value value(state &cs) const;
/** @brief Set the value of the alias for the given thread. */
void set_value(state &cs, any_value v);
/** @brief Call an alias.
*
* The alias will be called like if it was called in the language.
*/
any_value call(span_type<any_value> args, state &cs);
protected:
alias() = default;
};
/** @brief A command.
*
* Commands are builtins that can be invoked from the language and have a
* native implementation registered from C++. Once registered, a command
* cannot be unregistered or otherwise changed.
*/
struct LIBCUBESCRIPT_EXPORT command: ident {
/** @brief Get the argument list. */
std::string_view args() const;
/** @brief Get the number of arguments the command expects.
*
* Only non-variadic arguments count here (i.e. no repeated arguments,
* no `C`, no `V`; everything else counts as one argument).
*/
int arg_count() const;
/** @brief Call a command.
*
* The command will be called like if it was called in the language.
*/
any_value call(span_type<any_value> args, state &cs);
protected:
command() = default;
};
/** @brief A safe alias handler for commands
*
* In general, when dealing with aliases in commands, you do not want to
* set them directly, since this would set the alias globally. Instead, you
* can use this to make aliases local to the command.
*
* Internally, each Cubescript thread has a mapping for alias state within
* the thread. This mapping is stack based - which means you can push an
* alias, and then anything affecting the value of the alias in that thread
* will only be visible until the stack is popped. This structure provides
* a safe means of handling the alias stack; constructing it will push the
* alias, destroying it will pop it.
*
* Therefore, what you can do is something like this:
*
* ```
* {
* alias_local s{my_thread, "test"};
* // branch taken when the alias was successfully pushed
* // setting the alias will only be visible within this scope
* s.set(some_value); // a convenient setter
* my_thread.run(...);
* }
* ```
*
* If the provided input is not an alias, a cubescript::error will be thrown.
* Often you don't have to catch it (since this is primarily intended for use
* within commands, the error will propagate outside your command).
*
* Since the goal is to interact tightly with RAII and ensure consistency at
* all times, it is not possible to copy or move this object. That means you
* should also not be storing it; it should be used purely as a scope based
* alias stack manager.
*/
struct LIBCUBESCRIPT_EXPORT alias_local {
/** @brief Construct the local handler */
alias_local(state &cs, ident &a);
/** @brief Construct the local handler
*
* The ident will be retrieved using state::new_ident().
*/
alias_local(state &cs, std::string_view name);
/** @brief Construct the local handler
*
* The ident will be retrieved from the value. If the contained value
* is not an ident, it will be treated as a name.
*/
alias_local(state &cs, any_value const &val);
/** @brief Destroy the local handler */
~alias_local();
/** @brief Local handlers are not copyable */
alias_local(alias_local const &) = delete;
/** @brief Local handlers are not movable */
alias_local(alias_local &&) = delete;
/** @brief Local handlers are not copy assignable */
alias_local &operator=(alias_local const &) = delete;
/** @brief Local handlers are not move assignable */
alias_local &operator=(alias_local &&v) = delete;
/** @brief Get the contained alias */
alias &get_alias() noexcept { return *p_alias; }
/** @brief Get the contained alias */
alias const &get_alias() const noexcept { return *p_alias; }
/** @brief Set the contained alias's value
*
* @return `true` if the alias is valid, `false` otherwise
*/
bool set(any_value val);
private:
alias *p_alias;
void *p_sp;
};
} /* namespace cubescript */
#endif /* LIBCUBESCRIPT_CUBESCRIPT_IDENT_HH */

View File

@ -0,0 +1,73 @@
/** @file platform.hh
*
* @brief Utility macros and platform abstraction.
*
* Defines utility macros that you are not supposed to use yourself.
*
* @copyright See COPYING.md in the project tree for further information.
*/
#ifndef LIBCUBESCRIPT_CUBESCRIPT_PLATFORM_HH
#define LIBCUBESCRIPT_CUBESCRIPT_PLATFORM_HH
namespace cubescript {
#ifdef LIBCS_GENERATING_DOC
/** @brief Public API tag.
*
* All public API of the library is tagged like this.
*
* On Windows, the behavior of this is conditional. If `LIBCUBESCRIPT_DLL` is
* not defined, it expands to no value (that means we're either building or
* using a static library). If it is defined, it will tag the API with either
* `dllexport` (when building the lib, defined with `LIBCUBESCRIPT_BUILD`)
* or `dllimport` (when using the lib).
*
* On Unix-like systems with GCC-style compilers, this will mark the API as
* externally visible. The library is by default built so that symbols are
* normally hidden, so any external API needs to be tagged.
*
* @see LIBCUBESCRIPT_LOCAL
*/
#define LIBCUBESCRIPT_EXPORT
/** @brief Private API tag.
*
* Since symbols are private by default, this usually has no purpose. However,
* when marking entire structures exported, this affects all methods inside;
* in those cases this can be used to mark specific methods as for use only
* inside of the library (private methods not called in any public header).
*
* @see LIBCUBESCRIPT_EXPORT
*/
#define LIBCUBESCRIPT_LOCAL
#else
#if defined(__CYGWIN__) || (defined(_WIN32) && !defined(_XBOX_VER))
# ifdef LIBCUBESCRIPT_DLL
# ifdef LIBCUBESCRIPT_BUILD
# define LIBCUBESCRIPT_EXPORT __declspec(dllexport)
# else
# define LIBCUBESCRIPT_EXPORT __declspec(dllimport)
# endif
# else
# define LIBCUBESCRIPT_EXPORT
# endif
# define LIBCUBESCRIPT_LOCAL
#else
# if defined(__GNUC__) && (__GNUC__ >= 4)
# define LIBCUBESCRIPT_EXPORT __attribute__((visibility("default")))
# define LIBCUBESCRIPT_LOCAL __attribute__((visibility("hidden")))
# else
# define LIBCUBESCRIPT_EXPORT
# define LIBCUBESCRIPT_LOCAL
# endif
#endif
#endif /* LIBCS_GENERATING_DOC */
} /* namespace cubescript */
#endif /* LIBCUBESCRIPT_CUBESCRIPT_PLATFORM_HH */

View File

@ -0,0 +1,555 @@
/** @file state.hh
*
* @brief State API.
*
* The state is the main handle using which you interact with the language
* from C++. It represents a single Cubescript thread.
*
* @copyright See COPYING.md in the project tree for further information.
*/
#ifndef LIBCUBESCRIPT_CUBESCRIPT_STATE_HH
#define LIBCUBESCRIPT_CUBESCRIPT_STATE_HH
#include <cstddef>
#include <utility>
#include <optional>
#include <functional>
#include <string_view>
#include "callable.hh"
#include "ident.hh"
#include "value.hh"
namespace cubescript {
struct state;
/** @brief The allocator function signature
*
* This is the signature of the function pointer passed to do allocations.
*
* The first argument is the user data, followed by the old pointer (which
* is `nullptr` for new allocations and a valid pointer for reallocations
* and frees). Then follows the original size of the object (zero for new
* allocations, a valid value for reallocations and frees) and the new
* size of the object (zero for frees, a valid value for reallocations
* and new allocations).
*
* It must return the new pointer (`nullptr` when freeing) and does not have
* to throw (the library will throw `std::bad_alloc` itself if it receives
* a `nullptr` upon allocation).
*
* A typical allocation function will look like this:
*
* ```
* void *my_alloc(void *, void *p, std::size_t, std::size_t ns) {
* if (!ns) {
* std::free(p);
* return nullptr;
* }
* return std::realloc(p, ns);
* }
* ```
*/
using alloc_func = void *(*)(void *, void *, size_t, size_t);
/** @brief A call hook function
*
* It is possible to set up a call hook for each thread, which is called
* upon entering the VM. The hook returns nothing and receives the thread
* reference.
*/
using hook_func = internal::callable<void, state &>;
/** @brief A command function
*
* This is how every command looks. It returns nothing and takes the thread
* reference, a span of input arguments, and a reference to return value.
*/
using command_func = internal::callable<
void, state &, span_type<any_value>, any_value &
>;
/** @brief The Cubescript thread
*
* Represents a Cubescript thread, either the main thread or a side thread
* depending on how it's created. The state is what you create first and
* also what you should always destroy last.
*/
struct LIBCUBESCRIPT_EXPORT state {
/** @brief Create a new Cubescript main thread
*
* This creates a main thread without specifying an allocation function,
* using a simple, builtin implementation. Otherwise it is the same.
*/
state();
/** @brief Create a new Cubescript main thread
*
* For this variant you have to specify a function used to allocate memory.
* The optional data will be passed to allocation every time and is your
* only way to pass custom data to it, since unlike other kinds of hooks,
* the allocation function is a plain function pointer to ensure it never
* allocates by itself.
*/
state(alloc_func func, void *data = nullptr);
/** @brief Destroy the thread
*
* If the thread is a main thread, all state is destroyed. That means
* main threads should always be destroyed last.
*/
virtual ~state();
/** @brief Cubescript threads are not copyable */
state(state const &) = delete;
/** @brief Move-construct the Cubescript thread
*
* Keep in mind that you should never use `s` after this is done.
*/
state(state &&s);
/** @brief Cubescript threads are not copy assignable */
state &operator=(state const &) = delete;
/** @brief Move-assign the Cubescript thread
*
* Keep in mind that you should never use `s` after this is done.
* The original `this` is destroyed in the process.
*/
state &operator=(state &&s);
/** @brief Swap two Cubescript threads */
void swap(state &s);
/** @brief Create a non-main thread
*
* This creates a non-main thread. You can also create non-main threads
* using other non-main threads, but they will always all be dependent
* on the main thread they originally came from.
*
* @return the thread
*/
state new_thread();
/** @brief Attach a call hook to the thread
*
* The call hook is called every time the VM is entered. You can use
* this for debugging and other tracking, or say, as a means of
* interrupting execution from the side in an interactive interpreter.
*/
template<typename F>
hook_func call_hook(F &&f) {
return call_hook(
hook_func{std::forward<F>(f), callable_alloc, this}
);
}
/** @brief Get a reference to the call hook */
hook_func const &call_hook() const;
/** @brief Get a reference to the call hook */
hook_func &call_hook();
/** @brief Clear override state for the given ident
*
* If the ident is overridden, clear the flag. Global variables will have
* their value restored to the original, and the changed hook will be
* triggered. Aliases will be set to an empty string.
*
* Other ident types will do nothing.
*/
void clear_override(ident &id);
/** @brief Clear override state for all idents.
*
* @see clear_override()
*/
void clear_overrides();
/** @brief Create a new integer var
*
* @param n the name
* @param v the default value
* @throw cubescript::error in case of redefinition or invalid name
*/
builtin_var &new_var(
std::string_view n, integer_type v, bool read_only = false,
var_type vtp = var_type::DEFAULT
);
/** @brief Create a new float var
*
* @param n the name
* @param v the default value
* @throw cubescript::error in case of redefinition or invalid name
*/
builtin_var &new_var(
std::string_view n, float_type v, bool read_only = false,
var_type vtp = var_type::DEFAULT
);
/** @brief Create a new string var
*
* @param n the name
* @param v the default value
* @throw cubescript::error in case of redefinition or invalid name
*/
builtin_var &new_var(
std::string_view n, std::string_view v, bool read_only = false,
var_type vtp = var_type::DEFAULT
);
/** @brief Create a new ident
*
* If such ident already exists, nothing will be done and a reference
* will be returned. Otherwise, a new alias will be created and this
* alias will be returned, however it will not be visible from the
* language until actually assigned (it does not exist to the language
* just as is).
*
* @param n the name
* @throw cubescript::error in case of invalid name
*/
ident &new_ident(std::string_view n);
/** @brief Get the number of idents in the state
*
* This returns the number of idents that the main state has stored. It
* does not matter which thread you call this on.
*/
std::size_t ident_count() const;
/** @brief Get a specific cubescript::ident */
std::optional<std::reference_wrapper<ident>> get_ident(
std::string_view name
);
/** @brief Get a specific cubescript::ident */
std::optional<std::reference_wrapper<ident const>> get_ident(
std::string_view name
) const;
/** @brief Get a specific cubescript::ident by index
*
* Keep in mind that no bounds checking is performed, so the index must
* be within range.
*/
ident &get_ident(std::size_t index);
/** @brief Get a specific cubescript::ident by index
*
* Keep in mind that no bounds checking is performed, so the index must
* be within range.
*/
ident const &get_ident(std::size_t index) const;
/** @brief Assign a value to a name
*
* This will set something of the given name to the given value. The
* something may be a variable or an alias.
*
* If no ident of such name exists, a new alias will be created and
* set.
*
* @throw cubescript::error if `name` is a builtin ident (a registered
* command or similar) or if it is invalid
*
* @see lookup_value()
* @see reset_value()
* @see touch_value()
*/
void assign_value(std::string_view name, any_value v);
/** @brief Lookup a value by name
*
* This will lookup an ident of the given name and return its value.
* The ident may be a variable or an alias.
*
* @throw cubescript::error if `name` does not exist or belongs to an
* ident that doesn't support lookups
*
* @see assign_value()
* @see reset_value()
* @see touch_value()
*/
any_value lookup_value(std::string_view name);
/** @brief Reset a value by name
*
* This is like clear_override() except it works by name and performs
* extra checks.
*
* @throw cubescript::error if non-existent or read only
*
* @see assign_value()
* @see lookup_value()
* @see touch_value()
*/
void reset_value(std::string_view name);
/** @brief Touch a value by name
*
* If an ident with the given name exists and is a global variable,
* a changed hook will be triggered with it, acting like if a new
* value was set, but without actually setting it.
*
* @see assign_value()
* @see lookup_value()
* @see reset_value()
*/
void touch_value(std::string_view name);
/** @brief Register a command
*
* This registers a builtin command. A command consists of a valid name,
* a valid argument list, and a function to call.
*
* The argument list is a simple list of types. Currently the following
* simple types are recognized:
*
* * `s` - a string
* * `i` - an integer
* * `f` - a float
* * `a` - any (passed as is)
* * `b` - bytecode/block
* * `c` - condition (see below)
* * `v` - ident (variable/alias/any kind)
* * `#` - number of real arguments passed up until now
* * `$` - self ident (the command, except for special hooks)
*
* For condition types, the type of the value is generally kept as is,
* except for non-empty strings, which are compiled as bytecode. Therefore,
* to check condition types, you first try to get their bytecode; if it is
* valid, you run it and use its result, otherwise use the value as is,
* and then evaluate it as a boolean.
*
* When an argument is not provided by the caller, it is assigned to none
* type. Using the appropriate getters on the value structure will get
* you fallback defaults (e.g. 0 for integer and so on) but you can still
* check the type explicitly for whether it was actually provided.
*
* Commands also support variadics. Variadic commands have their type
* list suffixed with `...`.
*
* If `...` is used alone, the inputs are any arbitrary values. However,
* they can also be used with repetition. Repetition works for example
* like `if2...`. The `2` is the number of types to repeat; it must be at
* most the number of simple types preceeding it. It must be followed by
* `...`. This specific example means that the variadic arguments are a
* sequence of integer, float, integer, float, integer, float and so on.
*
* The resulting command stores the number of arguments it takes. The
* variadic part is not a part of it (neither is the part subject to
* repetition), while all simple types are a part of it (including
* 'fake' ones like argument count).
*
* It is also possible to register special commands. Special commands work
* like normal ones but are special-purpose. The currently allowed special
* commands are `//ivar`, `//fvar`, `//svar` and `//var_changed`. These
* are the only commands where the name can be in this format.
*
* The first three are handlers for for global variables, used when either
* printing or setting them using syntax `varname optional_vals` or using
* `varname = value`. Their type signature must always start with `$`
* and can be followed by any user types, generally you will also want
* to terminate the list with `#` to find out whether any values were
* passed.
*
* This way you can have custom handlers for printing as well as custom
* syntaxes for setting (e.g. your custom integer var handler may want to
* take up to 4 values to allow setting of RGBA color channels). When no
* arguments are passed (checked using `#`) you will want to print the
* value using a format you want. When using the `=` assignment syntax,
* one value is passed.
*
* There are builtin default handlers that take at most one arg (`i`, `f`
* and `s`) which also print to standard output (`name = value`).
*
* For `//var_changed`, there is no default handler. The arg list must be
* `$aa`. This will be called whenever the value of a builtin variable of
* any type changes, and will be passed the variable as its first argument,
* the previous value as the second argument and the new value as the third
* argument (mainly for convenience).
*
* For these builtins, `$` will refer to the variable ident, not to the
* builtin command.
*
* @throw cubescript::error upon redefinition, invalid name or arg list
*/
template<typename F>
command &new_command(
std::string_view name, std::string_view args, F &&f
) {
return new_command(
name, args,
command_func{std::forward<F>(f), callable_alloc, this}
);
}
/** @brief Compile a string.
*
* This compiles the given string, optionally using `source` as a filename
* for debug information (useful when implementing file I/O functions).
*
* @return a bytecode reference
* @throw cubescript::error on compilation failure
*/
bcode_ref compile(
std::string_view v, std::string_view source = std::string_view{}
);
/** @brief Get if the thread is in override mode
*
* If the thread is in override mode, any assigned alias or variable will
* be given the overridden flag, with variables also saving their old
* value. Upon clearing the flag (using clear_override() or similar)
* the old value will be restored (aliases will be set to an empty
* string).
*
* Overridable variables will always act like if the thread is in override
* mode, even if it's not.
*
* Keep in mind that if an alias is pushed, its flags will be cleared once
* popped.
*
* @see override_mode()
*/
bool override_mode() const;
/** @brief Set the thread's override mode */
bool override_mode(bool v);
/** @brief Get if the thread is in persist most
*
* In persist mode, newly assigned aliases will have the persist flag
* set on them, which is an indicator that they should be saved to disk
* like persistent variables. The library does no saving, so by default
* it works as an indicator for the user.
*
* Keep in mind that if an alias is pushed, its flags will be cleared once
* popped.
*/
bool persist_mode() const;
/** @brief Set the thread's persist mode */
bool persist_mode(bool v);
/** @brief Get the maximum call depth of the VM
*
* If zero, it is unlimited, otherwise it specifies how much the VM is
* allowed to recurse. By default, it is 1024.
*/
std::size_t max_call_depth() const;
/** @brief Set the maximum call depth ov the VM
*
* If zero, it is unlimited (the default is 1024). You can limit how much
* the VM is allowed to recurse if you have specific constraints to adhere
* to.
*
* @return the old value
*/
std::size_t max_call_depth(std::size_t v);
private:
friend struct state_p;
LIBCUBESCRIPT_LOCAL state(void *is);
hook_func call_hook(hook_func func);
command &new_command(
std::string_view name, std::string_view args, command_func func
);
static void *callable_alloc(
void *data, void *p, std::size_t os, std::size_t ns
) {
return static_cast<state *>(data)->alloc(p, os, ns);
}
void *alloc(void *ptr, size_t olds, size_t news);
struct thread_state *p_tstate = nullptr;
};
/** @brief Initialize the base library
*
* You can choose which parts of the standard library you include in your
* program. The base library contains core constructs for things such as
* error handling, conditionals, looping, and var/alias management.
*
* Calling this multiple times has no effect; commands will only be
* registered once.
*
* @see cubescript::std_init_math()
* @see cubescript::std_init_string()
* @see cubescript::std_init_list()
* @see cubescript::std_init_all()
*/
LIBCUBESCRIPT_EXPORT void std_init_base(state &cs);
/** @brief Initialize the math library
*
* You can choose which parts of the standard library you include in your
* program. The math library contains arithmetic and other math related
* functions.
*
* Calling this multiple times has no effect; commands will only be
* registered once.
*
* @see cubescript::std_init_base()
* @see cubescript::std_init_string()
* @see cubescript::std_init_list()
* @see cubescript::std_init_all()
*/
LIBCUBESCRIPT_EXPORT void std_init_math(state &cs);
/** @brief Initialize the string library
*
* You can choose which parts of the standard library you include in your
* program. The string library contains commands to manipulate strings.
*
* Calling this multiple times has no effect; commands will only be
* registered once.
*
* @see cubescript::std_init_base()
* @see cubescript::std_init_math()
* @see cubescript::std_init_list()
* @see cubescript::std_init_all()
*/
LIBCUBESCRIPT_EXPORT void std_init_string(state &cs);
/** @brief Initialize the list library
*
* You can choose which parts of the standard library you include in your
* program. The list library contains commands to manipulate lists.
*
* Calling this multiple times has no effect; commands will only be
* registered once.
*
* @see cubescript::std_init_base()
* @see cubescript::std_init_math()
* @see cubescript::std_init_string()
* @see cubescript::std_init_all()
*/
LIBCUBESCRIPT_EXPORT void std_init_list(state &cs);
/** @brief Initialize all standard libraries
*
* This is like calling each of the individual standard library init
* functions and exists mostly just for convenience.
* @see cubescript::std_init_base()
* @see cubescript::std_init_math()
* @see cubescript::std_init_string()
* @see cubescript::std_init_list()
*/
LIBCUBESCRIPT_EXPORT void std_init_all(state &cs);
} /* namespace cubescript */
#endif /* LIBCUBESCRIPT_CUBESCRIPT_STATE_HH */

View File

@ -0,0 +1,285 @@
/** @file util.hh
*
* @brief Utility API.
*
* This contains various utilities that don't quite fit within the other
* structures, but provide convenience; this includes things such as parsing
* of lists, strings and numbers.
*
* @copyright See COPYING.md in the project tree for further information.
*/
#ifndef LIBCUBESCRIPT_CUBESCRIPT_UTIL_HH
#define LIBCUBESCRIPT_CUBESCRIPT_UTIL_HH
#include <cstddef>
#include <string_view>
#include <algorithm>
#include "ident.hh"
namespace cubescript {
/** @brief A list parser
*
* Cubescript does not have data structures and everything is a string.
* However, you can represent lists as strings; there is a standard syntax
* to them.
*
* A list in Cubescript is simply a bunch of items separated by whitespace.
* The items can take the form of any literal value Cubescript has. That means
* they can be number literals, they can be words, and they can be strings.
* Strings can be quoted either with double quotes, square brackets or even
* parenthesis; basically any syntax representing a value.
*
* Comments (anything following two slashes, inclusive) are skipped. As far
* as allowed whitespace consisting an item delimiter goes, this is either
* regular spaces, horizontal tabs, or newlines.
*
* Keep in mind that it does not own the string it is parsing. Therefore,
* you have to make sure to keep it alive for as long as the parser is.
*
* The input string by itself should not be quoted.
*/
struct LIBCUBESCRIPT_EXPORT list_parser {
/** @brief Construct a list parser.
*
* Nothing is done until you actually start parsing.
*
* @param cs the thread
* @param s the string representing the list
*/
list_parser(state &cs, std::string_view s = std::string_view{}):
p_state{&cs}, p_input_beg{s.data()}, p_input_end{s.data() + s.size()}
{}
/** @brief Reset the input string for the list */
void set_input(std::string_view s) {
p_input_beg = s.data();
p_input_end = s.data() + s.size();
}
/** @brief Get the current input string in the parser
*
* The already read items will not be contained in the result.
*/
std::string_view input() const {
return std::string_view{
p_input_beg, std::size_t(p_input_end - p_input_beg)
};
}
/** @brief Attempt to parse an item
*
* This will first skip whitespace and then attempt to read an element.
*
* @return `true` if an element was found, `false` otherwise
*/
bool parse();
/** @brief Get the number of items in the current list
*
* This will not contain items that are already parsed out, and will
* parse the list itself, i.e. the final state will be an empty list.
*/
std::size_t count();
/** @brief Get the currently parsed item
*
* If the item was quoted with double quotes, the contents will be run
* through cubescript::unescape_string() first.
*
* @see raw_item()
* @see quoted_item()
*/
string_ref get_item() const;
/** @brief Get the currently parsed raw item
*
* Unlike get_item(), this will not unescape the string under any
* circumstances and represents simply a slice of the original input.
*
* @see get_item()
* @see quoted_item()
*/
std::string_view raw_item() const {
return std::string_view{p_ibeg, std::size_t(p_iend - p_ibeg)};
}
/** @brief Get the currently parsed raw item
*
* Like raw_item(), but contains the quotes too, if there were any.
* Likewise, the resulting view is just a slice of the original input.
*
* @see get_item()
* @see raw_item()
*/
std::string_view quoted_item() const {
return std::string_view{p_qbeg, std::size_t(p_qend - p_qbeg)};
}
/** @brief Skip whitespace in the input until a value is reached. */
void skip_until_item();
private:
state *p_state;
char const *p_input_beg, *p_input_end;
char const *p_ibeg{}, *p_iend{};
char const *p_qbeg{}, *p_qend{};
};
/** @brief Parse a double quoted Cubescript string
*
* This parses double quoted strings according to the Cubescript syntax. The
* string has to begin with a double quote; if it does not for any reason,
* `str.data()` is returned.
*
* Escape sequences are not expanded and have the syntax `^X` where X is the
* specific escape character (e.g. `^n` for newline). It is possible to make
* the string multiline; the line needs to end with `\\`.
*
* Strings must be terminated again with double quotes.
*
* @param cs the thread
* @param str the input string
* @param[out] nlines the number of lines in the string
*
* @return a pointer to the character after the last double quotes
* @throw cubescript::error if the string is started but not finished
*
* @see cubescript::parse_word()
*/
LIBCUBESCRIPT_EXPORT char const *parse_string(
state &cs, std::string_view str, size_t &nlines
);
/** @brief Parse a double quoted Cubescript string
*
* This overload has the same semantics but it does not return the number
* of lines.
*/
inline char const *parse_string(
state &cs, std::string_view str
) {
size_t nlines;
return parse_string(cs, str, nlines);
}
/** @brief Parse a Cubescript word.
*
* A Cubescript word is a sequence of any characters that are not whitespace
* (spaces, newlines, tabs) or a comment (two consecutive slashes). It is
* allowed to have parenthesis and square brackets as long a they are balanced.
*
* Examples of valid words: `foo`, `test123`, `125.4`, `[foo]`, `hi(bar)`.
*
* If a non-word character is encountered immediately, the resulting pointer
* will be `str.data()`.
*
* Keep in mind that a valid word may not be a valid ident name (e.g. numbers
* are valid words but not valid ident names).
*
* @return a pointer to the first character after the word
* @throw cubescript::error if there is unbalanced `[` or `(`
*/
LIBCUBESCRIPT_EXPORT char const *parse_word(
state &cs, std::string_view str
);
/** @brief Concatenate a span of values
*
* The input values are concatenated by `sep`. Non-integer/float/string
* input values are considered empty strings. Integers and floats are
* converted to strings. The input list is not affected, however.
*/
LIBCUBESCRIPT_EXPORT string_ref concat_values(
state &cs, span_type<any_value> vals,
std::string_view sep = std::string_view{}
);
/** @brief Escape a Cubescript string
*
* This reads and input string and writes it into `writer`, treating special
* characters as escape sequences. Newlines are turned into `^n`, tabs are
* turned into `^t`, vertical tabs into `^f`; double quotes are prefixed
* with a caret, carets are duplicated. All other characters are passed
* through.
*
* @return `writer` after writing into it
*
* @see cubescript::unescape_string()
*/
template<typename R>
inline R escape_string(R writer, std::string_view str) {
*writer++ = '"';
for (auto c: str) {
switch (c) {
case '\n': *writer++ = '^'; *writer++ = 'n'; break;
case '\t': *writer++ = '^'; *writer++ = 't'; break;
case '\f': *writer++ = '^'; *writer++ = 'f'; break;
case '"': *writer++ = '^'; *writer++ = '"'; break;
case '^': *writer++ = '^'; *writer++ = '^'; break;
default: *writer++ = c; break;
}
}
*writer++ = '"';
return writer;
}
/** @brief Unscape a Cubescript string
*
* If a caret is encountered, it is skipped. If the following character is `n`,
* it is turned into a newline; `t` is turned into a tab, `f` into a vertical
* tab, double quote is written as is, as is a second caret. Any others are
* written as they are.
*
* If a backslash is encountered and followed by a newline, the sequence is
* skipped, otherwise the backslash is written out. Any other character is
* written out as is.
*
* @return `writer` after writing into it
*
* @see cubescript::unescape_string()
*/
template<typename R>
inline R unescape_string(R writer, std::string_view str) {
for (auto it = str.begin(); it != str.end(); ++it) {
if (*it == '^') {
++it;
if (it == str.end()) {
break;
}
switch (*it) {
case 'n': *writer++ = '\n'; break;
case 't': *writer++ = '\t'; break;
case 'f': *writer++ = '\f'; break;
case '"': *writer++ = '"'; break;
case '^': *writer++ = '^'; break;
default: *writer++ = *it; break;
}
} else if (*it == '\\') {
++it;
if (it == str.end()) {
break;
}
char c = *it;
if ((c == '\r') || (c == '\n')) {
if ((c == '\r') && ((it + 1) != str.end())) {
if (it[1] == '\n') {
++it;
}
}
continue;
}
*writer++ = '\\';
} else {
*writer++ = *it;
}
}
return writer;
}
} /* namespace cubescript */
#endif /* LIBCUBESCRIPT_CUBESCRIPT_UTIL_HH */

View File

@ -0,0 +1,551 @@
/** @file value.hh
*
* @brief Value API.
*
* This file contains value handles. These include the main value handle,
* which represents any Cubescript value as a tagged union (and you use it
* for handling of things such as command arguments and return values), as
* well as string references and bytecode references.
*
* @copyright See COPYING.md in the project tree for further information.
*/
#ifndef LIBCUBESCRIPT_CUBESCRIPT_VALUE_HH
#define LIBCUBESCRIPT_CUBESCRIPT_VALUE_HH
#include <cstddef>
#include <string_view>
#include <new>
namespace cubescript {
struct ident;
struct any_value;
/** @brief The loop state
*
* This is returned by state::call_loop().
*/
enum class loop_state {
NORMAL = 0, /**< @brief The iteration ended normally. */
BREAK, /**< @brief The iteration was broken out of. */
CONTINUE /**< @brief The iteration ended early. */
};
/** @brief Bytecode reference.
*
* This is an object representing a bytecode reference. Bytecode references
* are executable in a Cubescript thread. The typical way to get a bytecode
* reference is as an argument to a command. You can also compile values
* explicitly.
*
* Bytecode references use refcounting to maintain their lifetime, so once
* you hold a reference, it will not be freed at very least until you are
* done with it and the object is destroyed.
*
* The API does not expose any specifics of the bytecode format either.
* This is an implementation detail; bytecode is also not meant to be
* serialized and stored on disk (it's not guaranteed to be portable).
*/
struct LIBCUBESCRIPT_EXPORT bcode_ref {
/** @brief Initialize a null reference.
*
* Null references can still be executed, but will not do anything.
*/
bcode_ref():
p_code(nullptr)
{}
/** @brief Copy a reference.
*
* A reference copy will increase the internal reference count and will
* point to the same bytecode.
*/
bcode_ref(bcode_ref const &v);
/** @brief Move a reference.
*
* A reference move will not change the internal reference count; the
* other reference will become a null reference.
*/
bcode_ref(bcode_ref &&v):
p_code(v.p_code)
{
v.p_code = nullptr;
}
/** @brief Destroy a reference.
*
* This will decrease the reference count, and if it's zero, free the
* bytecode.
*/
~bcode_ref();
/** @brief Copy-assign a reference.
*
* A reference copy will increase the internal reference count and will
* point to the same bytecode.
*/
bcode_ref &operator=(bcode_ref const &v);
/** @brief Move-assign a reference.
*
* A reference move will not change the internal reference count; the
* other reference will become a null reference.
*/
bcode_ref &operator=(bcode_ref &&v);
/** @brief Check if the bytecode is empty.
*
* Empty bytecode does not mean the same thing as null bytecode. While
* null bytecode is considered empty, there can be non-null empty
* bytecode; that is, a bytecode representing what an empty string
* would compile into.
*/
bool empty() const;
/** @brief Check if the bytecode is null.
*
* This only checks if the bytecode is null. In general, you will want
* to use bcode_ref::empty() instead.
*/
explicit operator bool() const;
/** @brief Execute the bytecode
*
* @return the return value
*/
any_value call(state &cs) const;
/** @brief Execute the bytecode as a loop body
*
* This exists to implement custom loop commands. A loop command will
* consist of your desired loop and will take a body as an argument
* (with bytecode type); this body will be run using this API. The
* return value can be used to check if the loop was broken out of
* or continued, and take steps accordingly.
*
* Some loops may evaluate to values, while others may not.
*/
loop_state call_loop(state &cs, any_value &ret) const;
/** @brief Execute the byctecode as a loop body
*
* This version ignores the return value of the body.
*/
loop_state call_loop(state &cs) const;
private:
friend struct bcode_p;
bcode_ref(struct bcode *v);
struct bcode *p_code;
};
/** @brief String reference.
*
* This Cubescript implementation uses interned strings everywhere in the
* language and the API. This means every string in the language exists in
* exactly one copy - if you try to create another string with the same
* contents, it will point to the same data. This structure represents
* a single reference to such string. By providing a reference counting
* mechanism, it is possible to manage strings in a memory-safe manner.
*
* There is also no such thing as a null reference in this case. If you
* have a string reference, it always points to a valid string no matter
* what.
*
* It is not safe to have string references still around after the main
* Cubescript thread is destroyed. Therefore, you should always make sure
* that all string references you are holding are gone by the time the
* main thread calls its destructor.
*
* For compatibility, all strings that are pointed to by string references
* are null terminated and therefore can be used with C-style APIs.
*/
struct LIBCUBESCRIPT_EXPORT string_ref {
friend struct any_value;
friend struct string_pool;
/** @brief String references are not default-constructible. */
string_ref() = delete;
/** @brief Create a new string reference.
*
* You need to provide a thread and a view of the string you wish
* to create a reference for. The implementation will then ensure
* that either a new string is allocated internally, or an existing
* string's reference count is incremented.
*/
string_ref(state &cs, std::string_view str);
/** @brief Copy a string reference.
*
* This will increase the reference count for the pointed-to string.
* There is explicitly no moving as this would create null references.
*/
string_ref(string_ref const &ref);
/** @brief Destroy a string reference.
*
* This will decrease the reference count. If it becomes zero, appropriate
* actions will be taken (the exact behavior is implementation-defined).
*
* It is not safe to call this after destruction of the main thread.
*/
~string_ref();
/** @brief Copy-assign a string reference.
*
* This will increase the reference count for the pointed-to string.
* There is explicitly no moving as this would create null references.
*/
string_ref &operator=(string_ref const &ref);
/** @brief Get a view to the pointed-to string.
*
* This creates a view of the string. The view is not its own reference,
* therefore it is possible it will get invalidated after the destructor
* of this reference is called.
*/
operator std::string_view() const;
/** @brief A convenience wrapper to get the size.
*
* Like `view().size()`.
*/
std::size_t size() const {
return view().size();
}
/** @brief A convenience wrapper to get the length.
*
* Like `view().length()`.
*/
std::size_t length() const {
return view().length();
}
/** @brief Get a C string pointer.
*
* The pointer may become dangling once the reference is destroyed.
* The string itself is always null terminated.
*/
char const *data() const;
/** @brief A convenience wrapper to get the view.
*
* Since instantiating a view can be ugly, this is a quick method
* to get a view of a random string reference.
*/
std::string_view view() const {
return std::string_view{*this};
}
/** @brief Check if the string is empty. */
bool empty() const {
return (size() == 0);
}
/** @brief Check if the string equals another.
*
* This is effectively a `data() == s.data()` address comparison, and
* therefore always has constant time complexity.
*/
bool operator==(string_ref const &s) const;
/** @brief Check if the string does not equal another.
*
* This is effectively a `data() != s.data()` address comparison, and
* therefore always has constant time complexity.
*/
bool operator!=(string_ref const &s) const;
private:
string_ref(char const *p);
char const *p_str;
};
/** @brief The type of a value.
*
* The cubescript::any_value structure can hold multiple types. Not all of
* them are representable in the language.
*/
enum class value_type {
NONE = 0, /**< @brief No value. */
INTEGER, /**< @brief Integer value (cubescript::integer_type). */
FLOAT, /**< @brief Floating point value (cubescript::float_type). */
STRING, /**< @brief String value (cubescript::string_ref). */
CODE, /**< @brief Bytecode value (cubescript::bcode_ref). */
IDENT /**< @brief Ident value (cubescript::ident). */
};
/** @brief A tagged union representing a value.
*
* This structure is used to represent argument and result types of commands
* as well as values of aliases. When assigned to an alias, the only value
* must not contain bytecode or an ident reference, as those cannot be
* represented as values in the language.
*
* Of course, to the language, every value looks like a string. It is however
* still possible to differentiate them on C++ side for better performance,
* more efficient storage and greater convenience.
*
* When the value contains a string or bytecode, it holds a reference like
* cubescript::string_ref or cubescript::bcode_ref would.
*
* Upon setting different types, the old type will get cleared, which may
* include a reference count decrease.
*/
struct LIBCUBESCRIPT_EXPORT any_value {
/** @brief Construct a value_type::NONE value. */
any_value();
/** @brief Construct a value_type::INTEGER value. */
any_value(integer_type val);
/** @brief Construct a value_type::FLOAT value. */
any_value(float_type val);
/** @brief Construct a value_type::STRING value. */
any_value(std::string_view val, state &cs);
/** @brief Construct a value_type::STRING value. */
any_value(string_ref const &val);
/** @brief Construct a value_type::CODE value. */
any_value(bcode_ref const &val);
/** @brief Construct a value_type::IDENT value. */
any_value(ident &val);
/** @brief Destroy the value.
*
* If holding a reference counted value, the refcount will be decreased
* and the value will be possibly freed.
*/
~any_value();
/** @brief Copy the value. */
any_value(any_value const &);
/** @brief Move the value.
*
* The other value becomes a value_type::NULL value.
*/
any_value(any_value &&v);
/** @brief Copy-assign the value. */
any_value &operator=(any_value const &);
/** @brief Move-assign the value.
*
* The other value becomes a value_type::NULL value.
*/
any_value &operator=(any_value &&);
/** @brief Assign an integer to the value. */
any_value &operator=(integer_type val);
/** @brief Assign a float to the value. */
any_value &operator=(float_type val);
/** @brief Assign a string reference to the value. */
any_value &operator=(string_ref const &val);
/** @brief Assign a bytecode reference to the value. */
any_value &operator=(bcode_ref const &val);
/** @brief Assign an ident to the value. */
any_value &operator=(ident &val);
/** @brief Get the type of the value. */
value_type type() const;
/** @brief Set the value to an integer.
*
* The type becomes value_type::INTEGER.
*/
void set_integer(integer_type val);
/** @brief Set the value to a float.
*
* The type becomes value_type::FLOAT.
*/
void set_float(float_type val);
/** @brief Set the value to a string.
*
* The type becomes value_type::STRING. The string will be allocated
* (if non-existent) like a cubescript::string_ref, and its reference
* count will be increased. This is why it is necessary to provide a state.
*/
void set_string(std::string_view val, state &cs);
/** @brief Set the value to a string reference.
*
* The type becomes value_type::STRING. The value will get copied
* (therefore, the reference count will be increased).
*/
void set_string(string_ref const &val);
/** @brief Set the value to a value_type::NONE. */
void set_none();
/** @brief Set the value to a bytecode reference.
*
* The type becomes value_type::CODE. The value will get copied
* (therefore, the reference count will be increased).
*/
void set_code(bcode_ref const &val);
/** @brief Set the value to an indent.
*
* The type becomes value_type::IDENT. No reference counting is
* performed, so after main thread destruction this may become
* dangling (and unsafe to use).
*/
void set_ident(ident &val);
/** @brief Get the value as a string reference.
*
* If the contained value is not a string, an appropriate conversion
* will occur. This will not affect the contained type, all conversions
* are only intermediate.
*
* If the type is not convertible, an empty string is used.
*/
string_ref get_string(state &cs) const;
/** @brief Get the value as an integer.
*
* If the contained value is not an integer, an appropriate conversion
* will occur. This will not affect the contained type, all conversions
* are only intermediate.
*
* Floating point values are rounded down and converted to integers.
*
* If the type is not convertible, 0 is returned.
*/
integer_type get_integer() const;
/** @brief Get the value as a float.
*
* If the contained value is not a float, an appropriate conversion
* will occur. This will not affect the contained type, all conversions
* are only intermediate.
*
* If the type is not convertible, 0 is returned.
*/
float_type get_float() const;
/** @brief Get the value as a bytecode.
*
* If the contained value is not bytecode, null bytecode is returned.
*/
bcode_ref get_code() const;
/** @brief Get the value as an ident.
*
* If the contained value is not an ident, a dummy is returned.
*/
ident &get_ident(state &cs) const;
/** @brief Get the value as representable inside the language.
*
* The returned value is the same value except if the original contents
* were bytecode or an ident - in those cases the returned type is
* value_type::NONE.
*/
any_value get_plain() const;
/** @brief Get the value converted to a boolean.
*
* For integer and float values, anything other than zero will become
* `true`, while zero becomes `false`. Empty strings are `false`; other
* strings first attempt conversion to an integer - if it's convertible
* (strong conversion rules apply), it's treated like an integer. If it
* is not, it's converted to a float (strong rules apply) and if it is
* convertible, it's treated like a float. Any non-integer non-float
* string is considered `true`.
*
* For any other type, `false` is returned.
*/
bool get_bool() const;
/** @brief Force the type to value_type::NONE.
*
* Like set_none().
*/
void force_none();
/** @brief Force the type to be representable in the language.
*
* Like `*this = get_plain()`.
*/
void force_plain();
/** @brief Force the type to value_type::FLOAT.
*
* Like `set_float(get_float())`.
*
* @return The value.
*/
float_type force_float();
/** @brief Force the type to value_type::INTEGER.
*
* Like `set_integer(get_integer())`.
*
* @return The value.
*/
integer_type force_integer();
/** @brief Force the type to value_type::STRING.
*
* Like `set_string(get_string(cs))`.
*
* @return A view to the string.
*/
std::string_view force_string(state &cs);
/** @brief Force the type to value_type::CODE.
*
* If the contained value is already bytecode, nothing happens. Otherwise
* the value is converted to a string (like get_string()) and this string
* is compiled as bytecode (as if using state::compile())
*
* @return A bytecode reference.
*/
bcode_ref force_code(
state &cs, std::string_view source = std::string_view{}
);
/** @brief Force the type to value_type::IDENT.
*
* If the contained value is already an ident, nothing happens. Otherwise
* the value is converted to a string (like get_string()) and this string
* is used as a name of the ident. If an ident of such name exists, it
* will be stored, otherwise a new alias is pre-created (it will not be
* visible to the language until a value is assigned to it though).
*
* @return An ident reference.
*/
ident &force_ident(state &cs);
private:
union {
integer_type i;
float_type f;
char const *s;
struct bcode *b;
ident *v;
} p_stor;
value_type p_type;
};
} /* namespace cubescript */
#endif /* LIBCUBESCRIPT_CUBESCRIPT_VALUE_HH */

View File

@ -1,51 +1,145 @@
/** @file cubescript_conf.hh
*
* @brief Library configuration.
*
* While you can technically modify this directly, it is better if you use
* a custom file `cubescript_conf_user.hh` in the same location. Most of the
* time you will not want to override anything, but should you need to change
* the integer, float or span types for a specific purpose, this allows you to.
*
* @copyright See COPYING.md in the project tree for further information.
*/
#ifndef LIBCUBESCRIPT_CUBESCRIPT_CONF_HH
#define LIBCUBESCRIPT_CUBESCRIPT_CONF_HH
#include <limits.h>
#include <functional>
#include <ostd/range.hh>
#include <type_traits>
/* do not modify */
namespace cscript {
struct cs_state;
struct cs_ident;
struct cs_value;
#if __has_include("cubescript_conf_user.hh")
# include "cubescript_conf_user.hh"
#endif
using cs_value_r = ostd::iterator_range<cs_value *>;
using cs_ident_r = ostd::iterator_range<cs_ident **>;
using cs_const_ident_r = ostd::iterator_range<cs_ident const **>;
}
#if !defined(LIBCUBESCRIPT_CONF_USER_SPAN)
# include <span>
#endif
/* configurable section */
namespace cscript {
using cs_int = int;
using cs_float = float;
/* probably don't want to change these, but if you use a custom allocation
* function for your state, keep in mind potential heap allocations in
* these are not handled by it (as std::function has no allocator support)
namespace cubescript {
#if !defined(LIBCUBESCRIPT_CONF_USER_INTEGER)
/** @brief The integer type used.
*
* normally std::function is optimized not to do allocations for small
* objects, so as long as you don't pass a lambda that captures by copy
* or move or something similar, you should be fine - but if you really
* need to make sure, override this with your own type
* While Cubescript is a stringly typed language, it uses integers and
* floats internally in a transparent manner where possible, and allows
* you to retrieve and pass integers and floats in commands and so on.
*
* This is the integer type used. By default, it's `int`, which is a
* 32-bit signed integer on most platforms. Keep in mind that is is
* necessary for this type to be a signed integer type.
*
* Define `LIBCUBESCRIPT_CONF_USER_INTEGER` in your custom conf file
* to disable the builtin.
*
* @see float_type
* @see INTEGER_FORMAT
*/
using cs_var_cb = std::function<void(cs_state &, cs_ident &)>;
using cs_command_cb = std::function<void(cs_state &, cs_value_r, cs_value &)>;
using cs_hook_cb = std::function<void(cs_state &)>;
using cs_alloc_cb = void *(*)(void *, void *, size_t, size_t);
using integer_type = int;
#endif
constexpr auto const IntFormat = "%d";
constexpr auto const FloatFormat = "%.7g";
constexpr auto const RoundFloatFormat = "%.1f";
#if !defined(LIBCUBESCRIPT_CONF_USER_FLOAT)
/** @brief The floating point type used.
*
* By default, this is `float`, which is on most platforms an IEEE754
* binary32 data type.
*
* Define `LIBCUBESCRIPT_CONF_USER_FLOAT` in your custom conf file
* to disable the builtin.
*
* @see integer_type
* @see FLOAT_FORMAT
* @see ROUND_FLOAT_FORMAT
*/
using float_type = float;
#endif
constexpr auto const IvarFormat = "%s = %d";
constexpr auto const IvarHexFormat = "%s = 0x%X";
constexpr auto const IvarHexColorFormat = "%s = 0x%.6X (%d, %d, %d)";
constexpr auto const FvarFormat = "%s = %.7g";
constexpr auto const FvarRoundFormat = "%s = %.1f";
constexpr auto const SvarFormat = "%s = \"%s\"";
constexpr auto const SvarQuotedFormat = "%s = [%s]";
} /* namespace cscript */
#if !defined(LIBCUBESCRIPT_CONF_USER_SPAN)
/** @brief The span type used.
*
* By default, this is `std::span`. You will almost never want to override
* this, but an alternative implementation can be supplied if your standard
* library does not support it.
*
* Define `LIBCUBESCRIPT_CONF_USER_SPAN` in your custom conf file to
* disable the builtin.
*/
template<typename T>
using span_type = std::span<T>;
#endif
#endif /* LIBCUBESCRIPT_CUBESCRIPT_CONF_HH */
#if !defined(LIBCUBESCRIPT_CONF_USER_INTEGER)
/** @brief The integer format used.
*
* This is a formatting specifier as in `printf`, corresponding to the
* `integer_type` used. It is used to handle conversions from the type
* to strings, as well as in the default integer variable handler when
* printing.
*
* There are no special restrictions imposed on the floating point type
* other than that it actually has to be floating point.
*
* Define `LIBCUBESCRIPT_CONF_USER_INTEGER` in your custom conf file
* to disable the builtin.
*
* @see integer_type
* @see FLOAT_FORMAT
*/
constexpr auto const INTEGER_FORMAT = "%d";
#endif
#if !defined(LIBCUBESCRIPT_CONF_USER_FLOAT)
/** @brief The float format used.
*
* This is a formatting specifier as in `printf`, corresponding to the
* `float_type` used. It is used to handle conversions from the type to
* strings, as well as in the default float variable handler when printing.
*
* When the floating point value is equivalent to its integer value (i.e.
* it has no decimal point), ROUND_FLOAT_FORMAT is used.
*
* Define `LIBCUBESCRIPT_CONF_USER_FLOAT` in your custom conf file
* to disable the builtin.
*
* @see float_type
* @see ROUND_FLOAT_FORMAT
* @see INTEGER_FORMAT
*/
constexpr auto const FLOAT_FORMAT = "%.7g";
/** @brief The round float format used.
*
* This is a formatting specifier as in `printf`, corresponding to the
* `float_type` used. It's like `FLOAT_FORMAT` but used when the value
* has no decimal point.
*
* @see float_type
* @see FLOAT_FORMAT
*/
constexpr auto const ROUND_FLOAT_FORMAT = "%.1f";
#endif
} /* namespace cubescript */
/* conf verification */
namespace cubescript {
static_assert(
std::is_integral_v<integer_type>, "integer_type must be integral"
);
static_assert(
std::is_signed_v<integer_type>, "integer_type must be signed"
);
static_assert(
std::is_floating_point_v<float_type>, "float_type must be floating point"
);
} /* namespace cubescript */
#endif /* LIBCUBESCRIPT_CUBESCRIPT_CONF_HH */

View File

@ -0,0 +1,13 @@
libcubescript_headers = [
'cubescript/cubescript.hh',
'cubescript/cubescript_conf.hh',
'cubescript/cubescript/callable.hh',
'cubescript/cubescript/error.hh',
'cubescript/cubescript/ident.hh',
'cubescript/cubescript/platform.hh',
'cubescript/cubescript/state.hh',
'cubescript/cubescript/util.hh',
'cubescript/cubescript/value.hh',
]
install_headers(libcubescript_headers, install_dir: dir_package_include)

View File

@ -1,7 +1,10 @@
project('libcubescript', ['cpp'],
version: '0.1.0',
default_options: ['buildtype=plain', 'cpp_std=c++17'],
meson_version: '>=0.46'
version: '1.0.0',
default_options: [
'buildtype=debugoptimized', 'warning_level=3', 'cpp_rtti=false',
'cpp_std=none'
],
meson_version: '>=0.50'
)
dir_prefix = get_option('prefix')
@ -13,20 +16,57 @@ dir_package_include = join_paths(dir_include, 'cubescript')
libcubescript_includes = [include_directories('include')]
tgt_compiler_id = meson.get_compiler('cpp').get_id()
if tgt_compiler_id == 'gcc' or tgt_compiler_id == 'clang'
extra_cxxflags = ['-Wextra', '-Wshadow', '-Wold-style-cast']
else
extra_cxxflags = []
cxx = meson.get_compiler('cpp')
extra_cxxflags = []
if get_option('buildtype') != 'plain'
if cxx.has_argument('-Wshadow')
extra_cxxflags += '-Wshadow'
endif
if cxx.has_argument('-Wold-style-cast')
extra_cxxflags += '-Wold-style-cast'
endif
endif
# Meson does not support C++20 std in a portable way in this version
# unless specified explicitly, have our own logic which will guess it
if get_option('cpp_std') == 'none'
if cxx.has_argument('-std=c++20')
# modern gcc/clang
extra_cxxflags += '-std=c++20'
elif cxx.has_argument('-std=c++2a')
# older gcc/clang
extra_cxxflags += '-std=c++2a'
elif cxx.has_argument('/std:c++20')
# future msvc++? not supported anywhere yet
extra_cxxflags += '/std:c++20'
elif cxx.has_argument('/std:c++latest')
# msvc++ 2019
extra_cxxflags += '/std:c++latest'
endif
endif
build_root = meson.current_build_dir()
subdir('include')
subdir('src')
subdir('tools')
if meson.is_cross_build() and get_option('tests')
build_tests = get_option('tests_cross')
else
build_tests = get_option('tests')
endif
if build_tests
subdir('tests')
endif
pkg = import('pkgconfig')
pkg.generate(
libraries: libcubescript_lib,
libraries: libcubescript_target,
version: meson.project_version(),
name: 'libcubescript',
filebase: 'libcubescript',

View File

@ -1,11 +1,23 @@
option('readline',
type: 'boolean',
value: false,
description: 'Use GNU readline for the REPL'
option('repl',
type: 'feature',
value: 'auto',
description: 'Enable the REPL (command line tool)'
)
option('linenoise',
type: 'boolean',
value: true,
type: 'feature',
value: 'auto',
description: 'Use linenoise for the REPL'
)
option('tests',
type: 'boolean',
value: 'true',
description: 'Whether to build tests'
)
option('tests_cross',
type: 'boolean',
value: 'false',
description: 'Whether to build tests when cross-compiling'
)

181
src/cs_bcode.cc 100644
View File

@ -0,0 +1,181 @@
#include "cs_bcode.hh"
#include "cs_state.hh"
#include "cs_vm.hh"
namespace cubescript {
/* public API impls */
LIBCUBESCRIPT_EXPORT bcode_ref::bcode_ref(bcode *v): p_code(v) {
bcode_addref(v->raw());
}
LIBCUBESCRIPT_EXPORT bcode_ref::bcode_ref(bcode_ref const &v):
p_code(v.p_code)
{
bcode_addref(p_code->raw());
}
LIBCUBESCRIPT_EXPORT bcode_ref::~bcode_ref() {
bcode_unref(p_code->raw());
}
LIBCUBESCRIPT_EXPORT bcode_ref &bcode_ref::operator=(
bcode_ref const &v
) {
bcode_unref(p_code->raw());
p_code = v.p_code;
bcode_addref(p_code->raw());
return *this;
}
LIBCUBESCRIPT_EXPORT bcode_ref &bcode_ref::operator=(bcode_ref &&v) {
bcode_unref(p_code->raw());
p_code = v.p_code;
v.p_code = nullptr;
return *this;
}
LIBCUBESCRIPT_EXPORT bool bcode_ref::empty() const {
if (!p_code) {
return true;
}
return (*p_code->raw() & BC_INST_OP_MASK) == BC_INST_EXIT;
}
LIBCUBESCRIPT_EXPORT bcode_ref::operator bool() const {
return p_code != nullptr;
}
LIBCUBESCRIPT_EXPORT any_value bcode_ref::call(state &cs) const {
any_value ret{};
vm_exec(state_p{cs}.ts(), p_code->raw(), ret);
return ret;
}
LIBCUBESCRIPT_EXPORT loop_state bcode_ref::call_loop(
state &cs, any_value &ret
) const {
auto &ts = state_p{cs}.ts();
++ts.loop_level;
try {
ret = call(cs);
} catch (break_exception) {
--ts.loop_level;
return loop_state::BREAK;
} catch (continue_exception) {
--ts.loop_level;
return loop_state::CONTINUE;
} catch (...) {
--ts.loop_level;
throw;
}
return loop_state::NORMAL;
}
LIBCUBESCRIPT_EXPORT loop_state bcode_ref::call_loop(state &cs) const {
any_value ret{};
return call_loop(cs, ret);
}
/* private funcs */
struct bcode_hdr {
internal_state *cs; /* needed to construct the allocator */
std::size_t asize; /* alloc size of the bytecode block */
bcode bc; /* BC_INST_START + refcount */
};
/* returned address is the 'init' member of the header */
std::uint32_t *bcode_alloc(internal_state *cs, std::size_t sz) {
auto a = std_allocator<std::uint32_t>{cs};
std::size_t hdrs = sizeof(bcode_hdr) / sizeof(std::uint32_t);
auto p = a.allocate(sz + hdrs - 1);
bcode_hdr *hdr;
std::memcpy(&hdr, &p, sizeof(hdr));
hdr->cs = cs;
hdr->asize = sz + hdrs - 1;
return p + hdrs - 1;
}
/* bc's address must be the 'init' member of the header */
static inline void bcode_free(std::uint32_t *bc) {
auto *rp = bc + 1 - (sizeof(bcode_hdr) / sizeof(std::uint32_t));
bcode_hdr *hdr;
std::memcpy(&hdr, &rp, sizeof(hdr));
std_allocator<std::uint32_t>{hdr->cs}.deallocate(rp, hdr->asize);
}
static inline void bcode_incr(std::uint32_t *bc) {
*bc += 0x100;
}
static inline void bcode_decr(std::uint32_t *bc) {
*bc -= 0x100;
if (std::int32_t(*bc) < 0x100) {
bcode_free(bc);
}
}
void bcode_addref(std::uint32_t *code) {
if (!code) {
return;
}
if ((*code & BC_INST_OP_MASK) == BC_INST_START) {
bcode_incr(code);
return;
}
switch (code[-1]&BC_INST_OP_MASK) {
case BC_INST_START:
bcode_incr(&code[-1]);
break;
case BC_INST_OFFSET:
code -= std::ptrdiff_t(code[-1] >> 8);
bcode_incr(code);
break;
}
}
void bcode_unref(std::uint32_t *code) {
if (!code) {
return;
}
if ((*code & BC_INST_OP_MASK) == BC_INST_START) {
bcode_decr(code);
return;
}
switch (code[-1]&BC_INST_OP_MASK) {
case BC_INST_START:
bcode_decr(&code[-1]);
break;
case BC_INST_OFFSET:
code -= std::ptrdiff_t(code[-1] >> 8);
bcode_decr(code);
break;
}
}
/* empty fallbacks */
static std::uint32_t emptyrets[VAL_ANY] = {
BC_RET_NULL, BC_RET_INT, BC_RET_FLOAT, BC_RET_STRING
};
empty_block *bcode_init_empty(internal_state *cs) {
auto a = std_allocator<empty_block>{cs};
auto *p = a.allocate(VAL_ANY);
for (std::size_t i = 0; i < VAL_ANY; ++i) {
p[i].init.init = BC_INST_START + 0x100;
p[i].code = BC_INST_EXIT | emptyrets[i];
}
return p;
}
void bcode_free_empty(internal_state *cs, empty_block *empty) {
std_allocator<empty_block>{cs}.deallocate(empty, VAL_ANY);
}
bcode *bcode_get_empty(empty_block *empty, std::size_t val) {
return &empty[val >> BC_INST_RET].init + 1;
}
} /* namespace cubescript */

219
src/cs_bcode.hh 100644
View File

@ -0,0 +1,219 @@
#ifndef LIBCUBESCRIPT_BCODE_HH
#define LIBCUBESCRIPT_BCODE_HH
#include <cubescript/cubescript.hh>
#include <cstdint>
#include <cstddef>
namespace cubescript {
struct internal_state;
struct bcode {
std::uint32_t init;
std::uint32_t *raw() {
return &init;
}
std::uint32_t const *raw() const {
return &init;
}
};
enum {
VAL_NULL = 0, VAL_INT, VAL_FLOAT, VAL_STRING,
VAL_ANY, VAL_CODE, VAL_IDENT, VAL_WORD,
VAL_POP, VAL_COND
};
template<typename T>
constexpr std::size_t bc_store_size = (
sizeof(T) - 1
) / sizeof(std::uint32_t) + 1;
/* instructions consist of:
*
* [D 24][M 2][O 6] == I
*
* I: instruction
* O: opcode
* M: type mask
* D: data
*
* also:
*
* R: result slot
*
* "force to M" means changing the type of the value as described by the
* type mask; this is generally string/integer/float, null in general
* preserves the type, except where mentioned
*/
enum {
/* noop */
BC_INST_START = 0,
BC_INST_OFFSET,
/* set R to null/true/false according to M */
BC_INST_NULL, BC_INST_TRUE, BC_INST_FALSE,
/* pop a value off the stack and set R to negated value according to M */
BC_INST_NOT,
/* pop a value off the stack */
BC_INST_POP,
/* recursively invoke VM from next instruction, push result on the stack */
BC_INST_ENTER,
/* recursively invoke VM from next instruction, result in R */
BC_INST_ENTER_RESULT,
/* exit VM, force R according to M */
BC_INST_EXIT,
/* pop a value off the stack and set R according to M */
BC_INST_RESULT,
/* push R on the stack according to M */
BC_INST_RESULT_ARG,
/* force top of the stack according to M */
BC_INST_FORCE,
/* duplicate top of the stack according to M */
BC_INST_DUP,
/* push value after I on the stack according to M (length D if string) */
BC_INST_VAL,
/* push value inside D on the stack according to M
*
* strings are at most 3 bytes long, integers and floats must be
* integral values between -0x800000 and 0x7FFFFF inclusive
*/
BC_INST_VAL_INT,
/* pop D aliases off the stack, push their values and recurse the VM
* pop their values afterwards (i.e. they are local to the execution)
*/
BC_INST_LOCAL,
/* pop a value off the stack, execute its bytecode,
* result in R according to M
*/
BC_INST_DO,
/* like above, except argument aliases are restored to the previous
* callstack level before calling (and restored back afterwards)
*/
BC_INST_DO_ARGS,
/* jump forward by D instructions */
BC_INST_JUMP,
/* conditional jump: pop a value off the stack, jump only if considered
* true or false (see BC_INST_FLAG_TRUE/FALSE)
*/
BC_INST_JUMP_B,
/* conditional jump: pop a value off the stack, if it's bytecode,
* eval it (saving the value into R), if it's not, save the value
* into R, then jump only if the value is considered true or false
* (see BC_INST_FLAG_TRUE/FALSE)
*/
BC_INST_JUMP_RESULT,
/* break or continue a loop; if no loop is currently running, raise
* an error, otherwise break (if BC_INST_FLAG_FALSE) or continue
* (if BC_INST_FLAG_TRUE)
*/
BC_INST_BREAK,
/* bytecode of length D follows, push on the stack as bytecode */
BC_INST_BLOCK,
/* push bytecode of (BC_INST_EXIT | M) on the stack */
BC_INST_EMPTY,
/* compile the value on top of the stack as if it was a string (null for
* non-string/integer/float values) */
BC_INST_COMPILE,
/* compile the value on top of the stack if string; if string is empty,
* force to null, if not string, keep as is
*/
BC_INST_COND,
/* push ident with index D on the stack; if arg, push val and mark used */
BC_INST_IDENT,
/* make value on top of stack an ident; if value is string, that is
* the ident name, otherwise dummy is used; ident is created if non
* existent, and if arg, push val and mark used
*/
BC_INST_IDENT_U,
/* lookup the alias with index D and push its value (error if unset) */
BC_INST_LOOKUP,
/* lookup an unknown ident with the name being given by the string on
* top of the stack; if a var or a set alias, update top of the stack
* to the ident's value (according to M), else raise error
*/
BC_INST_LOOKUP_U,
/* concatenate D values on top of the stack together, with topmost value
* being last; delimit with spaces; push the result according to M
*/
BC_INST_CONC,
/* like above but without delimiter */
BC_INST_CONC_W,
/* push the value of var with index D on the stack according to M */
BC_INST_VAR,
/* pop a value off the stack and set alias with index D to it */
BC_INST_ALIAS,
/* pop 2 values off the stack; top is value to set, below is alias name */
BC_INST_ALIAS_U,
/* call alias with index D and arg count following the instruction, pop
* the arguments off the stack (top being last); if unknown, raise error,
* store result in R according to M
*/
BC_INST_CALL,
/* given argument count D, pop the arguments off the stack (top being last)
* and then pop one more value (that being the ident name); look up the
* ident (raise error if non-existent) and then call according to its
* type (vars behave as in PRINT); store result in R according to M
*/
BC_INST_CALL_U,
/* call builtin command with index D; arguments are popped off the stack,
* last argument being topmost; result of the call goes in R according to M
*/
BC_INST_COM,
/* call builtin command with index D and arg count following the
* instruction, arguments are popped off the stack and passed as is
*/
BC_INST_COM_V,
/* opcode mask */
BC_INST_OP_MASK = 0x3F,
/* type mask shift */
BC_INST_RET = 6,
/* type mask, shifted */
BC_INST_RET_MASK = 0xC0,
/* type mask flags */
BC_RET_NULL = VAL_NULL << BC_INST_RET,
BC_RET_STRING = VAL_STRING << BC_INST_RET,
BC_RET_INT = VAL_INT << BC_INST_RET,
BC_RET_FLOAT = VAL_FLOAT << BC_INST_RET,
/* BC_INST_JUMP_B, BC_INST_JUMP_RESULT */
BC_INST_FLAG_TRUE = 1 << BC_INST_RET,
BC_INST_FLAG_FALSE = 0 << BC_INST_RET
};
std::uint32_t *bcode_alloc(internal_state *cs, std::size_t sz);
void bcode_addref(std::uint32_t *code);
void bcode_unref(std::uint32_t *code);
struct empty_block {
bcode init;
std::uint32_t code;
};
empty_block *bcode_init_empty(internal_state *cs);
void bcode_free_empty(internal_state *cs, empty_block *empty);
bcode *bcode_get_empty(empty_block *empty, std::size_t val);
struct bcode_p {
bcode_p(bcode_ref const &r): br{const_cast<bcode_ref *>(&r)} {}
bcode *get() {
return br->p_code;
}
static bcode_ref make_ref(bcode *v) {
return bcode_ref{v};
}
bcode_ref *br;
};
} /* namespace cubescript */
#endif

107
src/cs_error.cc 100644
View File

@ -0,0 +1,107 @@
#include <cubescript/cubescript.hh>
#include <cstdlib>
#include <algorithm>
#include <utility>
#include "cs_thread.hh"
#include "cs_error.hh"
namespace cubescript {
static void save_stack(
state &cs, typename error::stack_node *&sbeg,
typename error::stack_node *&send
) {
auto &ts = state_p{cs}.ts();
builtin_var *dalias = ts.istate->ivar_dbgalias;
auto dval = std::size_t(std::clamp(
dalias->value().get_integer(), integer_type(0), integer_type(1000)
));
if (!dval) {
sbeg = send = nullptr;
return;
}
std::size_t depth = 0;
std::size_t total = ts.callstack.size();
if (!total) {
sbeg = send = nullptr;
return;
}
auto slen = std::min(total, dval);
auto *st = static_cast<typename error::stack_node *>(ts.istate->alloc(
nullptr, 0, sizeof(typename error::stack_node) * slen
));
typename error::stack_node *ret = st, *nd = st;
++st;
for (std::size_t i = total - 1;; --i) {
auto &lev = ts.callstack[i];
++depth;
if (depth < dval) {
new (nd) typename error::stack_node{
lev.id, total - depth + 1
};
if (i == 0) {
break;
}
nd = st++;
} else if (i == 0) {
new (nd) typename error::stack_node{lev.id, 1};
break;
}
}
sbeg = ret;
send = ret + slen;
}
LIBCUBESCRIPT_EXPORT error::error(error &&v):
p_errbeg{v.p_errbeg}, p_errend{v.p_errend},
p_sbeg{v.p_sbeg}, p_send{v.p_send}, p_state{v.p_state}
{
v.p_sbeg = v.p_send = nullptr;
}
LIBCUBESCRIPT_EXPORT error &error::operator=(error &&v) {
std::swap(p_errbeg, v.p_errbeg);
std::swap(p_errend, v.p_errend);
std::swap(p_sbeg, v.p_sbeg);
std::swap(p_send, v.p_send);
std::swap(p_state, v.p_state);
return *this;
}
LIBCUBESCRIPT_EXPORT error::~error() {
state_p{*p_state}.ts().istate->destroy_array(
p_sbeg, std::size_t(p_send - p_sbeg)
);
}
LIBCUBESCRIPT_EXPORT error::error(state &cs, std::string_view msg):
p_errbeg{}, p_errend{}, p_state{&cs}
{
char *sp;
char *buf = state_p{cs}.ts().request_errbuf(msg.size(), sp);
std::memcpy(buf, msg.data(), msg.size());
buf[msg.size()] = '\0';
p_errbeg = sp;
p_errend = buf + msg.size();
save_stack(cs, p_sbeg, p_send);
}
LIBCUBESCRIPT_EXPORT error::error(
state &cs, char const *errbeg, char const *errend
): p_errbeg{errbeg}, p_errend{errend}, p_state{&cs} {
save_stack(cs, p_sbeg, p_send);
}
std::string_view error::what() const {
return std::string_view{p_errbeg, std::size_t(p_errend - p_errbeg)};
}
span_type<typename error::stack_node const> error::stack() const {
return span_type<typename error::stack_node const>{
p_sbeg, std::size_t(p_send - p_sbeg)
};
}
} /* namespace cubescript */

32
src/cs_error.hh 100644
View File

@ -0,0 +1,32 @@
#ifndef LIBCUBESCRIPT_ERROR_HH
#define LIBCUBESCRIPT_ERROR_HH
#include <cubescript/cubescript.hh>
#include <cstdio>
#include <cstddef>
namespace cubescript {
struct error_p {
template<typename ...A>
static error make(state &cs, std::string_view msg, A const &...args) {
std::size_t sz = msg.size() + 64;
char *buf, *sp;
for (;;) {
buf = state_p{cs}.ts().request_errbuf(sz, sp);
int written = std::snprintf(buf, sz, msg.data(), args...);
if (written <= 0) {
throw error{cs, "malformed format string"};
} else if (std::size_t(written) <= sz) {
break;
}
sz = std::size_t(written);
}
return error{cs, sp, buf + sz};
}
};
} /* namespace cubescript */
#endif

File diff suppressed because it is too large Load Diff

109
src/cs_gen.hh 100644
View File

@ -0,0 +1,109 @@
#ifndef LIBCUBESCRIPT_GEN_HH
#define LIBCUBESCRIPT_GEN_HH
#include <cstdint>
#include <string_view>
#include <utility>
#include <cubescript/cubescript.hh>
#include "cs_std.hh"
#include "cs_bcode.hh"
#include "cs_thread.hh"
namespace cubescript {
struct gen_state {
thread_state &ts;
gen_state() = delete;
gen_state(thread_state &tsr):
ts{tsr}, code{tsr.istate}
{}
std::size_t count() const;
std::uint32_t peek(std::size_t idx) const;
bcode_ref steal_ref();
void gen_pop();
void gen_dup(int ltype = 0);
void gen_result(int ltype = 0);
void gen_push_result(int ltype = 0);
void gen_force(int ltype);
void gen_not(int ltype = 0);
bool gen_if(std::size_t tpos, std::size_t fpos, int ltype = 0);
void gen_and_or(bool is_or, std::size_t start, int ltype = 0);
void gen_val_null();
void gen_result_null(int ltype = 0);
void gen_result_true(int ltype = 0);
void gen_result_false(int ltype = 0);
void gen_val_integer(integer_type v = 0);
void gen_val_integer(std::string_view v);
void gen_val_float(float_type v = 0);
void gen_val_float(std::string_view v);
void gen_val_string(std::string_view v = std::string_view{});
void gen_val_string_unescape(std::string_view str);
void gen_val_block(std::string_view str);
void gen_val_ident();
void gen_val_ident(ident &i);
void gen_val_ident(std::string_view v);
void gen_val(
int val_type, std::string_view v = std::string_view{},
std::size_t line = 0
);
void gen_lookup_var(ident &id, int ltype = 0);
void gen_lookup_alias(ident &id, int ltype = 0, int dtype = 0);
void gen_lookup_ident(int ltype = 0);
void gen_assign_alias(ident &id);
void gen_assign();
void gen_compile(bool cond = false);
void gen_ident_lookup();
void gen_concat(std::size_t concs, bool space, int ltype = 0);
void gen_command_call(
ident &id, int comt, int ltype = 0, std::uint32_t nargs = 0
);
void gen_alias_call(ident &id, std::uint32_t nargs = 0);
void gen_call(std::uint32_t nargs = 0);
void gen_local(std::uint32_t nargs);
void gen_do(bool args, int ltype = 0);
void gen_break();
void gen_continue();
void gen_main(
std::string_view s, std::string_view src = std::string_view{}
);
void gen_main_null();
void gen_main_integer(integer_type v);
void gen_main_float(float_type v);
bool is_block(std::size_t idx, std::size_t epos = 0) const;
void gen_block();
std::pair<std::size_t, std::string_view> gen_block(
std::string_view v, std::size_t line,
int ltype = VAL_NULL, int term = '\0'
);
private:
valbuf<std::uint32_t> code;
};
} /* namespace cubescript */
#endif /* LIBCUBESCRIPT_GEN_HH */

420
src/cs_ident.cc 100644
View File

@ -0,0 +1,420 @@
#include "cs_ident.hh"
#include "cs_bcode.hh"
#include "cs_thread.hh"
#include "cs_vm.hh"
#include "cs_error.hh"
namespace cubescript {
ident_impl::ident_impl(ident_type tp, string_ref nm, int fl):
p_name{nm}, p_type{int(tp)}, p_flags{fl}
{}
bool ident_is_callable(ident const *id) {
auto tp = id->type();
if ((tp != ident_type::COMMAND) && (tp != ident_type::SPECIAL)) {
return false;
}
return !!static_cast<command_impl const *>(id)->p_cb_cftv;
}
var_impl::var_impl(string_ref name, int fl):
ident_impl{ident_type::VAR, name, fl}
{}
alias_impl::alias_impl(
state &, string_ref name, string_ref a, int fl
):
ident_impl{ident_type::ALIAS, name, fl}, p_initial{}
{
p_initial.val_s.set_string(a);
}
alias_impl::alias_impl(
state &cs, string_ref name, std::string_view a, int fl
):
ident_impl{ident_type::ALIAS, name, fl}, p_initial{}
{
p_initial.val_s.set_string(a, cs);
}
alias_impl::alias_impl(state &, string_ref name, integer_type a, int fl):
ident_impl{ident_type::ALIAS, name, fl}, p_initial{}
{
p_initial.val_s.set_integer(a);
}
alias_impl::alias_impl(state &, string_ref name, float_type a, int fl):
ident_impl{ident_type::ALIAS, name, fl}, p_initial{}
{
p_initial.val_s.set_float(a);
}
alias_impl::alias_impl(state &, string_ref name, int fl):
ident_impl{ident_type::ALIAS, name, fl}, p_initial{}
{
p_initial.val_s.set_none();
}
alias_impl::alias_impl(state &, string_ref name, any_value v, int fl):
ident_impl{ident_type::ALIAS, name, fl}, p_initial{}
{
p_initial.val_s = v.get_plain();
}
command_impl::command_impl(
string_ref name, string_ref args, int nargs, command_func f
):
ident_impl{ident_type::COMMAND, name, 0},
p_cargs{args}, p_cb_cftv{std::move(f)}, p_numargs{nargs}
{}
void var_changed(thread_state &ts, builtin_var &id, any_value &oldval) {
auto *cid = ts.istate->cmd_var_changed;
if (!cid) {
return;
}
auto *cimp = static_cast<command_impl *>(cid);
any_value val[3] = {};
val[0].set_ident(id);
val[1] = std::move(oldval);
val[2] = id.value();
cimp->call(ts, span_type<any_value>{
static_cast<any_value *>(val), 3
}, val[0]);
}
command *var_impl::get_setter(thread_state &ts) const {
switch (p_storage.type()) {
case value_type::INTEGER:
return ts.istate->cmd_ivar;
case value_type::FLOAT:
return ts.istate->cmd_fvar;
case value_type::STRING:
return ts.istate->cmd_svar;
default:
break;
}
abort();
return nullptr; /* not reached */
}
void command_impl::call(
thread_state &ts, span_type<any_value> args, any_value &ret
) const {
auto idstsz = ts.idstack.size();
try {
p_cb_cftv(*ts.pstate, args, ret);
} catch (...) {
ts.idstack.resize(idstsz);
throw;
}
ts.idstack.resize(idstsz);
}
bool ident_is_used_arg(ident const *id, thread_state &ts) {
if (ts.callstack.empty()) {
return true;
}
return ts.callstack.back().usedargs[id->index()];
}
void alias_stack::push(ident_stack &st) {
st.next = node;
node = &st;
}
void alias_stack::pop() {
node = node->next;
}
void alias_stack::set_arg(alias *a, thread_state &ts, any_value &v) {
if (ident_is_used_arg(a, ts)) {
node->code = bcode_ref{};
} else {
push(ts.idstack.emplace_back());
ts.callstack.back().usedargs[a->index()] = true;
}
node->val_s = std::move(v);
}
void alias_stack::set_alias(alias *a, thread_state &ts, any_value &v) {
node->val_s = std::move(v);
node->code = bcode_ref{};
flags = ts.ident_flags;
auto *imp = static_cast<alias_impl *>(a);
if (node == &imp->p_initial) {
imp->p_flags = flags;
}
}
/* public interface */
LIBCUBESCRIPT_EXPORT ident::~ident() {}
LIBCUBESCRIPT_EXPORT ident_type ident::type() const {
if (p_impl->p_type > ID_ALIAS) {
return ident_type::SPECIAL;
}
return ident_type(p_impl->p_type);
}
LIBCUBESCRIPT_EXPORT std::string_view ident::name() const {
return p_impl->p_name;
}
LIBCUBESCRIPT_EXPORT int ident::index() const {
return p_impl->p_index;
}
LIBCUBESCRIPT_EXPORT bool ident::operator==(ident &other) const {
return this == &other;
}
LIBCUBESCRIPT_EXPORT bool ident::operator!=(ident &other) const {
return this != &other;
}
LIBCUBESCRIPT_EXPORT bool ident::is_overridden(state &cs) const {
switch (type()) {
case ident_type::VAR:
return (p_impl->p_flags & IDENT_FLAG_OVERRIDDEN);
case ident_type::ALIAS:
return (state_p{cs}.ts().get_astack(
static_cast<alias const *>(this)
).flags & IDENT_FLAG_OVERRIDDEN);
default:
break;
}
return false;
}
LIBCUBESCRIPT_EXPORT bool ident::is_persistent(state &cs) const {
switch (type()) {
case ident_type::VAR:
return (p_impl->p_flags & IDENT_FLAG_PERSIST);
case ident_type::ALIAS:
return (state_p{cs}.ts().get_astack(
static_cast<alias const *>(this)
).flags & IDENT_FLAG_PERSIST);
default:
break;
}
return false;
}
LIBCUBESCRIPT_EXPORT any_value ident::call(span_type<any_value>, state &cs) {
throw error{cs, "this ident type is not callable"};
}
LIBCUBESCRIPT_EXPORT bool builtin_var::is_read_only() const {
return (p_impl->p_flags & IDENT_FLAG_READONLY);
}
LIBCUBESCRIPT_EXPORT bool builtin_var::is_overridable() const {
return (p_impl->p_flags & IDENT_FLAG_OVERRIDE);
}
LIBCUBESCRIPT_EXPORT var_type builtin_var::variable_type() const {
if (p_impl->p_flags & IDENT_FLAG_OVERRIDE) {
return var_type::OVERRIDABLE;
} else if (p_impl->p_flags & IDENT_FLAG_PERSIST) {
return var_type::PERSISTENT;
} else {
return var_type::DEFAULT;
}
}
LIBCUBESCRIPT_EXPORT void builtin_var::save(state &cs) {
auto &ts = state_p{cs}.ts();
if ((ts.ident_flags & IDENT_FLAG_OVERRIDDEN) || is_overridable()) {
if (p_impl->p_flags & IDENT_FLAG_PERSIST) {
throw error_p::make(
cs, "cannot override persistent variable '%s'",
name().data()
);
}
if (!(p_impl->p_flags & IDENT_FLAG_OVERRIDDEN)) {
auto *imp = static_cast<var_impl *>(p_impl);
imp->p_override = std::move(imp->p_storage);
p_impl->p_flags |= IDENT_FLAG_OVERRIDDEN;
}
} else {
p_impl->p_flags &= IDENT_FLAG_OVERRIDDEN;
}
}
LIBCUBESCRIPT_EXPORT any_value builtin_var::call(
span_type<any_value> args, state &cs
) {
command *hid = static_cast<var_impl *>(this)->get_setter(state_p{cs}.ts());
any_value ret{};
auto &ts = state_p{cs}.ts();
auto *cimp = static_cast<command_impl *>(hid);
auto &targs = ts.vmstack;
auto osz = targs.size();
auto anargs = std::size_t(cimp->arg_count());
auto nargs = args.size();
targs.resize(
osz + std::max(args.size(), anargs + 1)
);
for (std::size_t i = 0; i < nargs; ++i) {
targs[osz + i + 1] = args[i];
}
exec_command(ts, cimp, this, &targs[osz], ret, nargs + 1, false);
return ret;
}
LIBCUBESCRIPT_EXPORT any_value builtin_var::value() const {
return static_cast<var_impl const *>(p_impl)->p_storage;
}
LIBCUBESCRIPT_EXPORT void builtin_var::set_raw_value(
state &cs, any_value val
) {
switch (static_cast<var_impl *>(p_impl)->p_storage.type()) {
case value_type::INTEGER:
val.force_integer();
break;
case value_type::FLOAT:
val.force_float();
break;
case value_type::STRING:
val.force_string(cs);
break;
default:
abort(); /* unreachable unless we have a bug */
break;
}
static_cast<var_impl *>(p_impl)->p_storage = std::move(val);
}
LIBCUBESCRIPT_EXPORT void builtin_var::set_value(
state &cs, any_value val, bool do_write, bool trigger
) {
if (is_read_only()) {
throw error_p::make(
cs, "variable '%s' is read only", name().data()
);
}
if (!do_write) {
return;
}
save(cs);
auto oldv = value();
set_raw_value(cs, std::move(val));
if (trigger) {
var_changed(state_p{cs}.ts(), *this, oldv);
}
}
LIBCUBESCRIPT_EXPORT any_value alias::value(state &cs) const {
return state_p{cs}.ts().get_astack(this).node->val_s;
}
LIBCUBESCRIPT_EXPORT void alias::set_value(state &cs, any_value v) {
auto &ts = state_p{cs}.ts();
if (is_arg()) {
ts.get_astack(this).set_arg(this, ts, v);
} else {
ts.get_astack(this).set_alias(this, ts, v);
}
}
LIBCUBESCRIPT_EXPORT bool alias::is_arg() const {
return (static_cast<alias_impl const *>(this)->p_flags & IDENT_FLAG_ARG);
}
LIBCUBESCRIPT_EXPORT any_value alias::call(
span_type<any_value> args, state &cs
) {
auto &ts = state_p{cs}.ts();
if (is_arg() && !ident_is_used_arg(this, ts)) {
return any_value{};
}
auto nargs = args.size();
auto &ast = ts.get_astack(this);
if (ast.node->val_s.type() != value_type::NONE) {
return exec_alias(ts, this, &args[0], nargs, ast);
}
return any_value{};
}
LIBCUBESCRIPT_EXPORT std::string_view command::args() const {
return static_cast<command_impl const *>(this)->p_cargs;
}
LIBCUBESCRIPT_EXPORT int command::arg_count() const {
return static_cast<command_impl const *>(this)->p_numargs;
}
LIBCUBESCRIPT_EXPORT any_value command::call(
span_type<any_value> args, state &cs
) {
any_value ret{};
auto &cimpl = static_cast<command_impl &>(*this);
if (!cimpl.p_cb_cftv) {
return ret;
}
auto nargs = args.size();
auto &ts = state_p{cs}.ts();
if (nargs < std::size_t(cimpl.arg_count())) {
auto &targs = ts.vmstack;
auto osz = targs.size();
targs.resize(osz + cimpl.arg_count());
try {
for (std::size_t i = 0; i < nargs; ++i) {
targs[osz + i] = args[i];
}
exec_command(ts, &cimpl, this, &targs[osz], ret, nargs, false);
} catch (...) {
targs.resize(osz);
throw;
}
targs.resize(osz);
} else {
exec_command(ts, &cimpl, this, &args[0], ret, nargs, false);
}
return ret;
}
/* external API for alias stack management */
LIBCUBESCRIPT_EXPORT alias_local::alias_local(state &cs, ident &a) {
if (a.type() != ident_type::ALIAS) {
throw error_p::make(cs, "ident '%s' is not an alias", a.name().data());
}
auto &ts = state_p{cs}.ts();
p_alias = static_cast<alias *>(&a);
auto &ast = ts.get_astack(p_alias);
ast.push(ts.idstack.emplace_back());
p_sp = &ast;
ast.flags &= ~IDENT_FLAG_UNKNOWN;
}
LIBCUBESCRIPT_EXPORT alias_local::alias_local(state &cs, std::string_view name):
alias_local{cs, cs.new_ident(name)}
{}
LIBCUBESCRIPT_EXPORT alias_local::alias_local(state &cs, any_value const &v):
alias_local{cs, (
v.type() == value_type::IDENT
) ? v.get_ident(cs) : cs.new_ident(v.get_string(cs))}
{}
LIBCUBESCRIPT_EXPORT alias_local::~alias_local() {
if (p_alias) {
static_cast<alias_stack *>(p_sp)->pop();
}
}
LIBCUBESCRIPT_EXPORT bool alias_local::set(any_value val) {
if (!p_alias) {
return false;
}
static_cast<alias_stack *>(p_sp)->node->val_s = std::move(val);
return true;
}
} /* namespace cubescript */

119
src/cs_ident.hh 100644
View File

@ -0,0 +1,119 @@
#ifndef LIBCUBESCRIPT_ALIAS_HH
#define LIBCUBESCRIPT_ALIAS_HH
#include <cubescript/cubescript.hh>
#include <bitset>
namespace cubescript {
static constexpr std::size_t MAX_ARGUMENTS = 32;
using argset = std::bitset<MAX_ARGUMENTS>;
enum {
ID_UNKNOWN = -1, ID_VAR, ID_COMMAND, ID_ALIAS,
ID_LOCAL, ID_DO, ID_DOARGS, ID_IF, ID_BREAK, ID_CONTINUE, ID_RESULT,
ID_NOT, ID_AND, ID_OR
};
enum {
IDENT_FLAG_UNKNOWN = 1 << 0,
IDENT_FLAG_ARG = 1 << 1,
IDENT_FLAG_READONLY = 1 << 2,
IDENT_FLAG_OVERRIDE = 1 << 3,
IDENT_FLAG_OVERRIDDEN = 1 << 4,
IDENT_FLAG_PERSIST = 1 << 5
};
struct ident_stack {
any_value val_s;
bcode_ref code;
ident_stack *next;
ident_stack(): val_s{}, code{}, next{nullptr} {}
};
struct alias_stack {
ident_stack *node = nullptr;
int flags = 0;
void push(ident_stack &st);
void pop();
void set_arg(alias *a, thread_state &ts, any_value &v);
void set_alias(alias *a, thread_state &ts, any_value &v);
};
struct ident_impl {
ident_impl() = delete;
ident_impl(ident_impl const &) = delete;
ident_impl(ident_impl &&) = delete;
/* trigger destructors for all inherited members properly */
virtual ~ident_impl() {};
ident_impl &operator=(ident_impl const &) = delete;
ident_impl &operator=(ident_impl &&) = delete;
ident_impl(ident_type tp, string_ref name, int flags = 0);
string_ref p_name;
/* represents the ident_type above, but internally it has a wider
* variety of values, so it's an int here (maps to an internal enum)
*/
int p_type, p_flags;
int p_index = -1;
};
bool ident_is_callable(ident const *id);
struct var_impl: ident_impl, builtin_var {
var_impl(string_ref name, int flags);
command *get_setter(thread_state &ts) const;
any_value p_storage{};
any_value p_override{};
};
void var_changed(thread_state &ts, builtin_var &id, any_value &oldval);
struct alias_impl: ident_impl, alias {
alias_impl(state &cs, string_ref n, string_ref a, int flags);
alias_impl(state &cs, string_ref n, std::string_view a, int flags);
alias_impl(state &cs, string_ref n, integer_type a, int flags);
alias_impl(state &cs, string_ref n, float_type a, int flags);
alias_impl(state &cs, string_ref n, int flags);
alias_impl(state &cs, string_ref n, any_value v, int flags);
ident_stack p_initial;
};
struct command_impl: ident_impl, command {
command_impl(
string_ref name, string_ref args, int numargs, command_func func
);
void call(
thread_state &ts, span_type<any_value> args, any_value &ret
) const;
string_ref p_cargs;
command_func p_cb_cftv;
int p_numargs;
};
bool ident_is_used_arg(ident const *id, thread_state &ts);
struct ident_p {
ident_p(ident &id): ip{&id} {}
ident_impl &impl() { return *ip->p_impl; }
void impl(ident_impl *impl) { ip->p_impl = impl; }
ident *ip;
};
} /* namespace cubescript */
#endif

1542
src/cs_parser.cc 100644

File diff suppressed because it is too large Load Diff

90
src/cs_parser.hh 100644
View File

@ -0,0 +1,90 @@
#ifndef LIBCUBESCRIPT_PARSER_HH
#define LIBCUBESCRIPT_PARSER_HH
#include <cstdlib>
#include <string_view>
#include <type_traits>
#include <cubescript/cubescript.hh>
#include "cs_std.hh"
#include "cs_bcode.hh"
#include "cs_ident.hh"
#include "cs_thread.hh"
#include "cs_gen.hh"
namespace cubescript {
integer_type parse_int(std::string_view input, std::string_view *end = nullptr);
float_type parse_float(std::string_view input, std::string_view *end = nullptr);
bool is_valid_name(std::string_view input);
struct parser_state {
thread_state &ts;
gen_state &gs;
char const *source, *send;
std::size_t current_line;
parser_state() = delete;
parser_state(thread_state &tsr, gen_state &gsr):
ts{tsr}, gs{gsr}, source{}, send{}, current_line{1}
{
ts.current_line = &current_line;
}
~parser_state() {
ts.current_line = nullptr;
}
std::string_view get_str();
charbuf get_str_dup();
std::string_view get_word();
void parse_block(int ltype, int term = '\0');
void next_char() {
if (source == send) {
return;
}
if (*source == '\n') {
++current_line;
}
++source;
}
char current(size_t ahead = 0) {
if (std::size_t(send - source) <= ahead) {
return '\0';
}
return source[ahead];
}
std::string_view read_macro_name();
char skip_until(std::string_view chars);
char skip_until(char cf);
void skip_comments();
void parse_lookup(int ltype);
bool parse_subblock();
void parse_blockarg(int ltype);
bool parse_arg(int ltype, charbuf *word = nullptr);
bool parse_call_command(command_impl *id, ident &self, int rettype);
bool parse_call_alias(alias &id);
bool parse_call_id(ident &id, int ltype);
bool parse_assign(charbuf &idname, int ltype, int term, bool &noass);
bool parse_id_local();
bool parse_id_do(bool args, int ltype);
bool parse_id_if(ident &id, int ltype);
bool parse_id_and_or(ident &id, int ltype);
};
} /* namespace cubescript */
#endif

745
src/cs_state.cc 100644
View File

@ -0,0 +1,745 @@
#include <memory>
#include <cstdio>
#include <cmath>
#include "cs_bcode.hh"
#include "cs_state.hh"
#include "cs_thread.hh"
#include "cs_strman.hh"
#include "cs_vm.hh"
#include "cs_parser.hh"
#include "cs_error.hh"
namespace cubescript {
internal_state::internal_state(alloc_func af, void *data):
allocf{af}, aptr{data},
idents{allocator_type{this}},
identmap{allocator_type{this}},
strman{create<string_pool>(this)},
empty{bcode_init_empty(this)}
{}
internal_state::~internal_state() {
for (auto &p: idents) {
destroy(&ident_p{*p.second}.impl());
}
bcode_free_empty(this, empty);
destroy(strman);
}
void *internal_state::alloc(void *ptr, size_t os, size_t ns) {
void *p = allocf(aptr, ptr, os, ns);
if (!p && ns) {
throw std::bad_alloc{};
}
return p;
}
static void *default_alloc(void *, void *p, size_t, size_t ns) {
if (!ns) {
std::free(p);
return nullptr;
}
return std::realloc(p, ns);
}
ident *internal_state::add_ident(ident *id, ident_impl *impl) {
if (!id) {
return nullptr;
}
ident_p{*id}.impl(impl);
idents[id->name()] = id;
impl->p_index = int(identmap.size());
identmap.push_back(id);
return identmap.back();
}
ident &internal_state::new_ident(state &cs, std::string_view name, int flags) {
ident *id = get_ident(name);
if (!id) {
if (!is_valid_name(name)) {
throw error_p::make(
cs, "'%s' is not a valid identifier name", name.data()
);
}
auto *inst = create<alias_impl>(
cs, string_ref{cs, name}, flags
);
id = add_ident(inst, inst);
}
return *id;
}
ident *internal_state::get_ident(std::string_view name) const {
auto id = idents.find(name);
if (id == idents.end()) {
return nullptr;
}
return id->second;
}
/* public interfaces */
state::state(): state{default_alloc, nullptr} {}
state::state(alloc_func func, void *data) {
command *p;
if (!func) {
func = default_alloc;
}
/* allocator is not set up yet, use func directly */
auto *statep = static_cast<internal_state *>(
func(data, nullptr, 0, sizeof(internal_state))
);
/* allocator will be set up in the constructor */
new (statep) internal_state{func, data};
try {
p_tstate = statep->create<thread_state>(statep);
} catch (...) {
statep->destroy(statep);
throw;
}
p_tstate->pstate = this;
p_tstate->istate = statep;
p_tstate->owner = true;
for (std::size_t i = 0; i < MAX_ARGUMENTS; ++i) {
char buf[16];
snprintf(buf, sizeof(buf), "arg%zu", i + 1);
statep->new_ident(
*this, static_cast<char const *>(buf), IDENT_FLAG_ARG
);
}
statep->id_dummy = &statep->new_ident(*this, "//dummy", IDENT_FLAG_UNKNOWN);
statep->ivar_numargs = &new_var("numargs", 0, true);
statep->ivar_dbgalias = &new_var("dbgalias", 4);
/* default handlers for variables */
statep->cmd_ivar = &new_command("//ivar_builtin", "$i#", [](
auto &cs, auto args, auto &
) {
auto &iv = static_cast<builtin_var &>(args[0].get_ident(cs));
if (args[2].get_integer() <= 1) {
std::printf("%s = ", iv.name().data());
std::printf(INTEGER_FORMAT, iv.value().get_integer());
std::printf("\n");
} else {
iv.set_value(cs, args[1]);
}
});
statep->cmd_fvar = &new_command("//fvar_builtin", "$f#", [](
auto &cs, auto args, auto &
) {
auto &fv = static_cast<builtin_var &>(args[0].get_ident(cs));
if (args[2].get_integer() <= 1) {
auto val = fv.value().get_float();
std::printf("%s = ", fv.name().data());
if (std::floor(val) == val) {
std::printf(ROUND_FLOAT_FORMAT, val);
} else {
std::printf(FLOAT_FORMAT, val);
}
std::printf("\n");
} else {
fv.set_value(cs, args[1]);
}
});
statep->cmd_svar = &new_command("//svar_builtin", "$s#", [](
auto &cs, auto args, auto &
) {
auto &sv = static_cast<builtin_var &>(args[0].get_ident(cs));
if (args[2].get_integer() <= 1) {
auto val = sv.value().get_string(cs);
if (val.view().find('"') == std::string_view::npos) {
std::printf("%s = \"%s\"\n", sv.name().data(), val.data());
} else {
std::printf("%s = [%s]\n", sv.name().data(), val.data());
}
} else {
sv.set_value(cs, args[1]);
}
});
statep->cmd_var_changed = nullptr;
/* builtins */
p = &new_command("do", "b", [](auto &cs, auto args, auto &res) {
res = args[0].get_code().call(cs);
});
static_cast<command_impl *>(p)->p_type = ID_DO;
p = &new_command("doargs", "b", [](auto &cs, auto args, auto &res) {
res = exec_code_with_args(*cs.p_tstate, args[0].get_code());
});
static_cast<command_impl *>(p)->p_type = ID_DOARGS;
p = &new_command("if", "abb", [](auto &cs, auto args, auto &res) {
res = (args[0].get_bool() ? args[1] : args[2]).get_code().call(cs);
});
static_cast<command_impl *>(p)->p_type = ID_IF;
p = &new_command("result", "a", [](auto &, auto args, auto &res) {
res = std::move(args[0]);
});
static_cast<command_impl *>(p)->p_type = ID_RESULT;
p = &new_command("!", "a", [](auto &, auto args, auto &res) {
res.set_integer(!args[0].get_bool());
});
static_cast<command_impl *>(p)->p_type = ID_NOT;
p = &new_command("&&", "c1...", [](auto &cs, auto args, auto &res) {
if (args.empty()) {
res.set_integer(1);
} else {
for (size_t i = 0; i < args.size(); ++i) {
auto code = args[i].get_code();
if (code) {
res = code.call(cs);
} else {
res = std::move(args[i]);
}
if (!res.get_bool()) {
break;
}
}
}
});
static_cast<command_impl *>(p)->p_type = ID_AND;
p = &new_command("||", "c1...", [](auto &cs, auto args, auto &res) {
if (args.empty()) {
res.set_integer(0);
} else {
for (size_t i = 0; i < args.size(); ++i) {
auto code = args[i].get_code();
if (code) {
res = code.call(cs);
} else {
res = std::move(args[i]);
}
if (res.get_bool()) {
break;
}
}
}
});
static_cast<command_impl *>(p)->p_type = ID_OR;
p = &new_command("local", "", nullptr);
static_cast<command_impl *>(p)->p_type = ID_LOCAL;
p = &new_command("break", "", [](auto &cs, auto, auto &) {
if (cs.p_tstate->loop_level) {
throw break_exception{};
} else {
throw error{cs, "no loop to break"};
}
});
static_cast<command_impl *>(p)->p_type = ID_BREAK;
p = &new_command("continue", "", [](auto &cs, auto, auto &) {
if (cs.p_tstate->loop_level) {
throw continue_exception{};
} else {
throw error{cs, "no loop to continue"};
}
});
static_cast<command_impl *>(p)->p_type = ID_CONTINUE;
}
LIBCUBESCRIPT_EXPORT state::~state() {
if (!p_tstate || !p_tstate->owner) {
return;
}
auto *sp = p_tstate->istate;
sp->destroy(p_tstate);
sp->destroy(sp);
}
LIBCUBESCRIPT_EXPORT state::state(state &&s) {
swap(s);
}
LIBCUBESCRIPT_EXPORT state &state::operator=(state &&s) {
if (p_tstate && p_tstate->owner) {
auto *sp = p_tstate->istate;
sp->destroy(p_tstate);
sp->destroy(sp);
}
p_tstate = s.p_tstate;
s.p_tstate = nullptr;
return *this;
}
LIBCUBESCRIPT_EXPORT void state::swap(state &s) {
std::swap(p_tstate, s.p_tstate);
}
state::state(void *is) {
auto *s = static_cast<internal_state *>(is);
p_tstate = s->create<thread_state>(s);
p_tstate->pstate = this;
p_tstate->istate = s;
p_tstate->owner = false;
}
LIBCUBESCRIPT_EXPORT state state::new_thread() {
return state{p_tstate->istate};
}
LIBCUBESCRIPT_EXPORT hook_func state::call_hook(hook_func func) {
return p_tstate->set_hook(std::move(func));
}
LIBCUBESCRIPT_EXPORT hook_func const &state::call_hook() const {
return p_tstate->get_hook();
}
LIBCUBESCRIPT_EXPORT hook_func &state::call_hook() {
return p_tstate->get_hook();
}
LIBCUBESCRIPT_EXPORT void *state::alloc(void *ptr, size_t os, size_t ns) {
return p_tstate->istate->alloc(ptr, os, ns);
}
LIBCUBESCRIPT_EXPORT std::size_t state::ident_count() const {
return p_tstate->istate->identmap.size();
}
LIBCUBESCRIPT_EXPORT std::optional<
std::reference_wrapper<ident>
> state::get_ident(std::string_view name) {
auto *id = p_tstate->istate->get_ident(name);
if (!id) {
return std::nullopt;
}
return *id;
}
LIBCUBESCRIPT_EXPORT std::optional<
std::reference_wrapper<ident const>
> state::get_ident(std::string_view name) const {
auto *id = p_tstate->istate->get_ident(name);
if (!id) {
return std::nullopt;
}
return *id;
}
LIBCUBESCRIPT_EXPORT ident &state::get_ident(std::size_t index) {
return *p_tstate->istate->identmap[index];
}
LIBCUBESCRIPT_EXPORT ident const &state::get_ident(std::size_t index) const {
return *p_tstate->istate->identmap[index];
}
LIBCUBESCRIPT_EXPORT void state::clear_override(ident &id) {
if (!id.is_overridden(*this)) {
return;
}
switch (id.type()) {
case ident_type::ALIAS: {
auto &ast = p_tstate->get_astack(static_cast<alias *>(&id));
ast.node->val_s.set_string("", *this);
ast.node->code = bcode_ref{};
ast.flags &= ~IDENT_FLAG_OVERRIDDEN;
return;
}
case ident_type::VAR: {
auto &v = static_cast<var_impl &>(id);
any_value oldv = v.value();
v.p_storage = std::move(v.p_override);
var_changed(*p_tstate, v, oldv);
static_cast<var_impl *>(
static_cast<builtin_var *>(&v)
)->p_flags &= ~IDENT_FLAG_OVERRIDDEN;
return;
}
default:
break;
}
}
LIBCUBESCRIPT_EXPORT void state::clear_overrides() {
for (auto &p: p_tstate->istate->idents) {
clear_override(*(p.second));
}
}
inline int var_flags(bool read_only, var_type vtp) {
int ret = 0;
if (read_only) {
ret |= IDENT_FLAG_READONLY;
}
switch (vtp) {
case var_type::PERSISTENT:
ret |= IDENT_FLAG_PERSIST;
break;
case var_type::OVERRIDABLE:
ret |= IDENT_FLAG_OVERRIDE;
break;
default:
break;
}
return ret;
}
static void var_name_check(
state &cs, ident *id, std::string_view n
) {
if (id) {
throw error_p::make(
cs, "redefinition of ident '%.*s'", int(n.size()), n.data()
);
} else if (!is_valid_name(n)) {
throw error_p::make(
cs, "'%.*s' is not a valid variable name",
int(n.size()), n.data()
);
}
}
LIBCUBESCRIPT_EXPORT builtin_var &state::new_var(
std::string_view n, integer_type v, bool read_only, var_type vtp
) {
auto *iv = p_tstate->istate->create<var_impl>(
string_ref{*this, n},var_flags(read_only, vtp)
);
iv->p_storage.set_integer(v);
try {
var_name_check(*this, p_tstate->istate->get_ident(n), n);
} catch (...) {
p_tstate->istate->destroy(iv);
throw;
}
p_tstate->istate->add_ident(iv, iv);
return *iv;
}
LIBCUBESCRIPT_EXPORT builtin_var &state::new_var(
std::string_view n, float_type v, bool read_only, var_type vtp
) {
auto *fv = p_tstate->istate->create<var_impl>(
string_ref{*this, n}, var_flags(read_only, vtp)
);
fv->p_storage.set_float(v);
try {
var_name_check(*this, p_tstate->istate->get_ident(n), n);
} catch (...) {
p_tstate->istate->destroy(fv);
throw;
}
p_tstate->istate->add_ident(fv, fv);
return *fv;
}
LIBCUBESCRIPT_EXPORT builtin_var &state::new_var(
std::string_view n, std::string_view v, bool read_only, var_type vtp
) {
auto *sv = p_tstate->istate->create<var_impl>(
string_ref{*this, n}, var_flags(read_only, vtp)
);
sv->p_storage.set_string(v, *this);
try {
var_name_check(*this, p_tstate->istate->get_ident(n), n);
} catch (...) {
p_tstate->istate->destroy(sv);
throw;
}
p_tstate->istate->add_ident(sv, sv);
return *sv;
}
LIBCUBESCRIPT_EXPORT ident &state::new_ident(std::string_view n) {
return p_tstate->istate->new_ident(*this, n, IDENT_FLAG_UNKNOWN);
}
LIBCUBESCRIPT_EXPORT void state::assign_value(
std::string_view name, any_value v
) {
auto id = get_ident(name);
if (id) {
switch (id->get().type()) {
case ident_type::ALIAS: {
static_cast<alias &>(id->get()).set_value(*this, std::move(v));
return;
}
case ident_type::VAR:
id->get().call(span_type<any_value>{&v, 1}, *this);
break;
default:
throw error_p::make(
*this, "cannot redefine builtin %s with an alias",
id->get().name().data()
);
}
} else if (!is_valid_name(name)) {
throw error_p::make(
*this, "cannot alias invalid name '%s'", name.data()
);
} else {
auto *a = p_tstate->istate->create<alias_impl>(
*this, string_ref{*this, name}, std::move(v),
p_tstate->ident_flags
);
p_tstate->istate->add_ident(a, a);
}
}
LIBCUBESCRIPT_EXPORT any_value state::lookup_value(std::string_view name) {
ident *id = nullptr;
auto idopt = get_ident(name);
if (!idopt) {
id = nullptr;
} else {
id = &idopt->get();
}
alias_stack *ast;
if (id) {
switch(id->type()) {
case ident_type::ALIAS: {
auto *a = static_cast<alias_impl *>(id);
ast = &p_tstate->get_astack(static_cast<alias *>(id));
if (ast->flags & IDENT_FLAG_UNKNOWN) {
break;
}
if (a->is_arg() && !ident_is_used_arg(id, *p_tstate)) {
return any_value{};
}
return ast->node->val_s.get_plain();
}
case ident_type::VAR:
return static_cast<builtin_var *>(id)->value();
case ident_type::COMMAND: {
any_value val{};
auto *cimpl = static_cast<command_impl *>(id);
auto &args = p_tstate->vmstack;
auto osz = args.size();
/* pad with as many empty values as we need */
args.resize(osz + cimpl->arg_count());
try {
exec_command(
*p_tstate, cimpl, cimpl, &args[osz], val, 0, true
);
} catch (...) {
args.resize(osz);
throw;
}
args.resize(osz);
return val;
}
default:
return any_value{};
}
}
throw error_p::make(*this, "unknown alias lookup: %s", name.data());
}
LIBCUBESCRIPT_EXPORT void state::reset_value(std::string_view name) {
auto id = get_ident(name);
if (!id) {
throw error_p::make(
*this, "variable '%s' does not exist", name.data()
);
}
if (id->get().type() == ident_type::VAR) {
if (static_cast<builtin_var &>(id->get()).is_read_only()) {
throw error_p::make(
*this, "variable '%s' is read only", name.data()
);
}
}
clear_override(id->get());
}
LIBCUBESCRIPT_EXPORT void state::touch_value(std::string_view name) {
auto id = get_ident(name);
if (!id) {
return;
}
auto &idr = id->get();
if (idr.type() != ident_type::VAR) {
return;
}
auto &v = static_cast<builtin_var &>(idr);
auto vv = v.value();
var_changed(*p_tstate, v, vv);
}
static char const *allowed_builtins[] = {
"//ivar", "//fvar", "//svar", "//var_changed",
"//ivar_builtin", "//fvar_builtin", "//svar_builtin",
nullptr
};
LIBCUBESCRIPT_EXPORT command &state::new_command(
std::string_view name, std::string_view args, command_func func
) {
int nargs = 0;
for (auto fmt = args.begin(); fmt != args.end(); ++fmt) {
switch (*fmt) {
case 'i':
case 'f':
case 'a':
case 'c':
case '#':
case 's':
case 'b':
case 'v':
case '$':
++nargs;
break;
case '1':
case '2':
case '3':
case '4': {
int nrep = (*fmt - '0');
if (nargs < nrep) {
throw error{
*this, "not enough arguments to repeat"
};
}
if ((args.end() - fmt) != 4) {
throw error{
*this, "malformed argument list"
};
}
if (fmt[1] != '.') {
throw error{
*this, "repetition without variadic arguments"
};
}
nargs -= nrep;
break;
}
case '.':
if (
((fmt + 3) != args.end()) ||
std::memcmp(&fmt[0], "...", 3)
) {
throw error{
*this, "unterminated variadic argument list"
};
}
fmt += 2;
break;
default:
throw error_p::make(
*this, "invalid argument type: %c", *fmt
);
}
}
auto &is = *p_tstate->istate;
auto *cmd = is.create<command_impl>(
string_ref{*this, name}, string_ref{*this, args},
nargs, std::move(func)
);
/* we can set these builtins */
command **bptrs[] = {
&is.cmd_ivar, &is.cmd_fvar, &is.cmd_svar, &is.cmd_var_changed
};
auto nbptrs = sizeof(bptrs) / sizeof(*bptrs);
/* provided a builtin */
if ((name.size() >= 2) && (name[0] == '/') && (name[1] == '/')) {
/* sanitize */
for (auto **p = allowed_builtins; *p; ++p) {
if (!name.compare(*p)) {
/* if it's one of the settable ones, maybe set it */
if (std::size_t(p - allowed_builtins) < nbptrs) {
if (!is.get_ident(name)) {
/* only set if it does not exist already */
*bptrs[p - allowed_builtins] = cmd;
goto do_add;
}
}
/* this will ensure we're not redefining them */
goto valid;
}
}
/* we haven't found one matching the list, so error */
is.destroy(cmd);
throw error_p::make(
*this, "forbidden builtin command: %.*s",
int(name.size()), name.data()
);
}
valid:
if (is.get_ident(name)) {
is.destroy(cmd);
throw error_p::make(
*this, "redefinition of ident '%.*s'",
int(name.size()), name.data()
);
}
do_add:
is.add_ident(cmd, cmd);
return *cmd;
}
LIBCUBESCRIPT_EXPORT bcode_ref state::compile(
std::string_view v, std::string_view source
) {
gen_state gs{*p_tstate};
gs.gen_main(v, source);
return gs.steal_ref();
}
LIBCUBESCRIPT_EXPORT bool state::override_mode() const {
return (p_tstate->ident_flags & IDENT_FLAG_OVERRIDDEN);
}
LIBCUBESCRIPT_EXPORT bool state::override_mode(bool v) {
bool was = override_mode();
if (v) {
p_tstate->ident_flags |= IDENT_FLAG_OVERRIDDEN;
} else {
p_tstate->ident_flags &= ~IDENT_FLAG_OVERRIDDEN;
}
return was;
}
LIBCUBESCRIPT_EXPORT bool state::persist_mode() const {
return (p_tstate->ident_flags & IDENT_FLAG_PERSIST);
}
LIBCUBESCRIPT_EXPORT bool state::persist_mode(bool v) {
bool was = persist_mode();
if (v) {
p_tstate->ident_flags |= IDENT_FLAG_PERSIST;
} else {
p_tstate->ident_flags &= ~IDENT_FLAG_PERSIST;
}
return was;
}
LIBCUBESCRIPT_EXPORT std::size_t state::max_call_depth() const {
return p_tstate->max_call_depth;
}
LIBCUBESCRIPT_EXPORT std::size_t state::max_call_depth(std::size_t v) {
auto old = p_tstate->max_call_depth;
p_tstate->max_call_depth = v;
return old;
}
LIBCUBESCRIPT_EXPORT void std_init_all(state &cs) {
std_init_base(cs);
std_init_math(cs);
std_init_string(cs);
std_init_list(cs);
}
} /* namespace cubescript */

141
src/cs_state.hh 100644
View File

@ -0,0 +1,141 @@
#ifndef LIBCUBESCRIPT_STATE_HH
#define LIBCUBESCRIPT_STATE_HH
#include <cubescript/cubescript.hh>
#include <unordered_map>
#include <string>
#include <vector>
#include "cs_bcode.hh"
#include "cs_ident.hh"
namespace cubescript {
struct internal_state;
struct string_pool;
template<typename T>
struct std_allocator {
using value_type = T;
inline std_allocator(internal_state *s);
template<typename U>
std_allocator(std_allocator<U> const &a): istate{a.istate} {}
inline T *allocate(std::size_t n);
inline void deallocate(T *p, std::size_t n);
template<typename U>
bool operator==(std_allocator<U> const &a) {
return istate == a.istate;
}
internal_state *istate;
};
struct internal_state {
using allocator_type = std_allocator<
std::pair<std::string_view const, ident *>
>;
alloc_func allocf;
void *aptr;
std::unordered_map<
std::string_view, ident *,
std::hash<std::string_view>,
std::equal_to<std::string_view>,
allocator_type
> idents;
std::vector<ident *, std_allocator<ident *>> identmap;
string_pool *strman;
empty_block *empty;
ident *id_dummy;
builtin_var *ivar_numargs;
builtin_var *ivar_dbgalias;
command *cmd_ivar;
command *cmd_fvar;
command *cmd_svar;
command *cmd_var_changed;
internal_state() = delete;
internal_state(alloc_func af, void *data);
~internal_state();
ident *add_ident(ident *id, ident_impl *impl);
ident &new_ident(state &cs, std::string_view name, int flags);
ident *get_ident(std::string_view name) const;
void *alloc(void *ptr, size_t os, size_t ns);
template<typename T, typename ...A>
T *create(A &&...args) {
T *ret = static_cast<T *>(alloc(nullptr, 0, sizeof(T)));
new (ret) T{std::forward<A>(args)...};
return ret;
}
template<typename T, typename ...A>
T *create_array(size_t len, A &&...args) {
T *ret = static_cast<T *>(alloc(nullptr, 0, len * sizeof(T)));
for (size_t i = 0; i < len; ++i) {
new (&ret[i]) T{std::forward<A>(args)...};
}
return ret;
}
template<typename T>
void destroy(T *v) noexcept {
v->~T();
alloc(v, sizeof(T), 0);
}
template<typename T>
void destroy_array(T *v, size_t len) noexcept {
v->~T();
alloc(v, len * sizeof(T), 0);
}
};
struct state_p {
state_p(state &cs): csp{&cs} {}
thread_state &ts() { return *csp->p_tstate; }
state *csp;
};
template<typename T>
inline std_allocator<T>::std_allocator(internal_state *s): istate{s} {}
template<typename T>
inline T *std_allocator<T>::allocate(std::size_t n) {
return static_cast<T *>(istate->alloc(nullptr, 0, n * sizeof(T)));
}
template<typename T>
inline void std_allocator<T>::deallocate(T *p, std::size_t n) {
istate->alloc(p, n, 0);
}
template<typename F>
inline void new_cmd_quiet(
state &cs, std::string_view name, std::string_view args, F &&f
) {
try {
cs.new_command(name, args, std::forward<F>(f));
} catch (error const &) {
return;
}
}
} /* namespace cubescript */
#endif

10
src/cs_std.cc 100644
View File

@ -0,0 +1,10 @@
#include "cs_std.hh"
#include "cs_thread.hh"
namespace cubescript {
charbuf::charbuf(state &cs): charbuf{state_p{cs}.ts().istate} {}
charbuf::charbuf(thread_state &ts): charbuf{ts.istate} {}
} /* namespace cubescript */

105
src/cs_std.hh 100644
View File

@ -0,0 +1,105 @@
#ifndef LIBCUBESCRIPT_STD_HH
#define LIBCUBESCRIPT_STD_HH
#include <cubescript/cubescript.hh>
#include <cstddef>
#include <new>
#include <utility>
#include <vector>
#include <type_traits>
#include <string_view>
#include "cs_state.hh"
namespace cubescript {
/* a value buffer */
template<typename T>
struct valbuf {
valbuf() = delete;
valbuf(internal_state *cs): buf{std_allocator<T>{cs}} {}
using size_type = std::size_t;
using value_type = T;
using reference = T &;
using const_reference = T const &;
void reserve(std::size_t s) { buf.reserve(s); }
void resize(std::size_t s) { buf.resize(s); }
void resize(std::size_t s, value_type const &v) {
buf.resize(s, v);
}
void append(T const *beg, T const *end) {
buf.insert(buf.end(), beg, end);
}
void insert(std::size_t i, T const &it) {
buf.insert(buf.begin() + i, it);
}
template<typename ...A>
reference emplace_back(A &&...args) {
return buf.emplace_back(std::forward<A>(args)...);
}
void push_back(T const &v) { buf.push_back(v); }
void pop_back() { buf.pop_back(); }
T &back() { return buf.back(); }
T const &back() const { return buf.back(); }
std::size_t size() const { return buf.size(); }
std::size_t capacity() const { return buf.capacity(); }
bool empty() const { return buf.empty(); }
void clear() { buf.clear(); }
T &operator[](std::size_t i) { return buf[i]; }
T const &operator[](std::size_t i) const { return buf[i]; }
T *data() { return &buf[0]; }
T const *data() const { return &buf[0]; }
std::vector<T, std_allocator<T>> buf;
};
/* specialization of value buffer for bytes */
struct charbuf: valbuf<char> {
charbuf(internal_state *cs): valbuf<char>{cs} {}
charbuf(state &cs);
charbuf(thread_state &ts);
void append(char const *beg, char const *end) {
valbuf<char>::append(beg, end);
}
void append(std::string_view v) {
append(&v[0], &v[v.size()]);
}
std::string_view str() {
return std::string_view{buf.data(), buf.size()};
}
std::string_view str_term() {
return std::string_view{buf.data(), buf.size() - 1};
}
};
/* because the dual-iterator constructor is not supported everywhere
* and the pointer + size constructor is ugly as heck
*/
inline std::string_view make_str_view(char const *a, char const *b) {
return std::string_view{a, std::size_t(b - a)};
}
} /* namespace cubescript */
#endif

182
src/cs_strman.cc 100644
View File

@ -0,0 +1,182 @@
#include <cassert>
#include <cstring>
#include <cubescript/cubescript.hh>
#include "cs_strman.hh"
#include "cs_thread.hh"
namespace cubescript {
struct string_ref_state {
internal_state *state;
std::size_t length;
std::size_t refcount;
};
inline string_ref_state *get_ref_state(char const *ptr) {
string_ref_state *r;
std::memcpy(&r, &ptr, sizeof(r));
return r - 1;
}
char const *string_pool::add(std::string_view str) {
auto it = counts.find(str);
/* already present: just increment ref */
if (it != counts.end()) {
auto *st = it->second;
/* having a null pointer is the same as non-existence */
if (st) {
++st->refcount;
st += 1;
char const *r;
std::memcpy(&r, &st, sizeof(r));
return r;
}
}
/* not present: allocate brand new data */
auto ss = str.size();
auto strp = alloc_buf(ss);
/* write string data, it's already pre-terminated */
memcpy(strp, str.data(), ss);
/* store it */
counts.emplace(std::string_view{strp, ss}, get_ref_state(strp));
return strp;
}
char const *string_pool::ref(char const *ptr) {
auto *ss = get_ref_state(ptr);
++ss->refcount;
return ptr;
}
string_ref string_pool::steal(char *ptr) {
auto *ss = get_ref_state(ptr);
auto sr = std::string_view{ptr, ss->length};
/* much like add(), but we already have memory */
auto it = counts.find(sr);
if (it != counts.end()) {
auto *st = it->second;
if (st) {
/* the buffer is superfluous now */
cstate->alloc(ss, ss->length + sizeof(string_ref_state) + 1, 0);
st += 1;
char const *rp;
std::memcpy(&rp, &st, sizeof(rp));
return string_ref{rp};
}
}
ss->refcount = 0; /* string_ref will increment it */
counts.emplace(sr, ss);
return string_ref{ptr};
}
void string_pool::unref(char const *ptr) {
auto *ss = get_ref_state(ptr);
if (!--ss->refcount) {
/* refcount zero, so ditch it
* this path is a little slow...
*/
auto sr = std::string_view{ptr, ss->length};
auto it = counts.find(sr);
/* this should *never* happen unless we have a bug */
#ifndef NDEBUG
assert(it != counts.end());
#else
if (it == counts.end()) {
abort();
}
#endif
/* we're freeing the key */
counts.erase(it);
/* dealloc */
cstate->alloc(ss, ss->length + sizeof(string_ref_state) + 1, 0);
}
}
char const *string_pool::find(std::string_view str) const {
auto it = counts.find(str);
if (it == counts.end()) {
return nullptr;
}
auto *sp = it->second + 1;
char const *rp;
std::memcpy(&rp, &sp, sizeof(rp));
return rp;
}
std::string_view string_pool::get(char const *ptr) const {
auto *ss = get_ref_state(ptr);
return std::string_view{ptr, ss->length};
}
char *string_pool::alloc_buf(std::size_t len) const {
auto mem = cstate->alloc(nullptr, 0, len + sizeof(string_ref_state) + 1);
/* write length and initial refcount */
auto *sst = static_cast<string_ref_state *>(mem);
sst->state = cstate;
sst->length = len;
sst->refcount = 1;
/* pre-terminate */
char *strp;
sst += 1;
std::memcpy(&strp, &sst, sizeof(strp));
strp[len] = '\0';
/* now the user can fill it */
return strp;
}
char const *str_managed_ref(char const *str) {
return get_ref_state(str)->state->strman->ref(str);
}
void str_managed_unref(char const *str) {
get_ref_state(str)->state->strman->unref(str);
}
std::string_view str_managed_view(char const *str) {
return get_ref_state(str)->state->strman->get(str);
}
/* strref implementation */
LIBCUBESCRIPT_EXPORT string_ref::string_ref(state &cs, std::string_view str) {
p_str = state_p{cs}.ts().istate->strman->add(str);
}
LIBCUBESCRIPT_EXPORT string_ref::string_ref(string_ref const &ref):
p_str{ref.p_str}
{
get_ref_state(p_str)->state->strman->ref(p_str);
}
/* this can be used by friends to do quick string_ref creation */
LIBCUBESCRIPT_EXPORT string_ref::string_ref(char const *p) {
p_str = str_managed_ref(p);
}
LIBCUBESCRIPT_EXPORT string_ref::~string_ref() {
str_managed_unref(p_str);
}
LIBCUBESCRIPT_EXPORT string_ref &string_ref::operator=(string_ref const &ref) {
p_str = str_managed_ref(ref.p_str);
return *this;
}
LIBCUBESCRIPT_EXPORT char const *string_ref::data() const {
return p_str;
}
LIBCUBESCRIPT_EXPORT string_ref::operator std::string_view() const {
return str_managed_view(p_str);
}
LIBCUBESCRIPT_EXPORT bool string_ref::operator==(string_ref const &s) const {
return p_str == s.p_str;
}
LIBCUBESCRIPT_EXPORT bool string_ref::operator!=(string_ref const &s) const {
return p_str != s.p_str;
}
} /* namespace cubescript */

96
src/cs_strman.hh 100644
View File

@ -0,0 +1,96 @@
#ifndef LIBCUBESCRIPT_STRMAN_HH
#define LIBCUBESCRIPT_STRMAN_HH
#include <cubescript/cubescript.hh>
#include <unordered_map>
#include <string_view>
#include "cs_std.hh"
#include "cs_state.hh"
namespace cubescript {
struct string_ref_state;
char const *str_managed_ref(char const *str);
void str_managed_unref(char const *str);
std::string_view str_managed_view(char const *str);
/* string manager
*
* the purpose of this is to handle interning of strings; each string within
* a libcs state is represented (and allocated) exactly once, and reference
* counted; that both helps save resources, and potentially provide a means
* to reliably represent returned strings in places that is compatible with
* multiple threads and eliminate the chance of dangling pointers
*
* strings are allocated in a manner where the refcount and length are stored
* as a part of the string's memory, so it can be easily accessed using just
* the pointer to the string, but also this is transparent for usage
*
* this is not thread-safe yet, and later on it should be made that,
* for now we don't bother...
*/
struct string_pool {
using allocator_type = std_allocator<
std::pair<std::string_view const, string_ref_state *>
>;
string_pool() = delete;
string_pool(internal_state *cs): cstate{cs}, counts{allocator_type{cs}} {}
~string_pool() {}
string_pool(string_pool const &) = delete;
string_pool(string_pool &&) = delete;
string_pool &operator=(string_pool const &) = delete;
string_pool &operator=(string_pool &&) = delete;
/* adds a string into the manager using any source, and returns a managed
* version; this is "slow" as it has to hash the string and potentially
* allocate fresh memory for it, but is perfectly safe at any time
*/
char const *add(std::string_view str);
/* this simply increments the reference count of an existing managed
* string, this is only safe when you know the pointer you are passing
* is already managed the system
*/
char const *ref(char const *ptr);
/* this will use the provided memory, assuming it is a fresh string that
* is yet to be added; the memory must be allocated with alloc_buf()
*/
string_ref steal(char *ptr);
/* decrements the reference count and removes it from the system if
* that reaches zero; likewise, only safe with pointers that are managed
*/
void unref(char const *ptr);
/* just finds a managed pointer with the same contents
* as the input, if not found then a null pointer is returned
*/
char const *find(std::string_view str) const;
/* a quick helper to make a proper string view out of a ptr */
std::string_view get(char const *ptr) const;
/* this will allocate a buffer of the given length (plus one for
* terminating zero) so you can fill it; use steal() to write it
*/
char *alloc_buf(std::size_t len) const;
internal_state *cstate;
std::unordered_map<
std::string_view, string_ref_state *,
std::hash<std::string_view>,
std::equal_to<std::string_view>,
allocator_type
> counts;
};
} /* namespace cubescript */
#endif

69
src/cs_thread.cc 100644
View File

@ -0,0 +1,69 @@
#include "cs_thread.hh"
#include <cstdio>
namespace cubescript {
thread_state::thread_state(internal_state *cs):
vmstack{cs}, idstack{cs}, callstack{cs}, astacks{cs}, errbuf{cs}
{
vmstack.reserve(32);
idstack.reserve(MAX_ARGUMENTS);
}
hook_func thread_state::set_hook(hook_func f) {
auto hk = std::move(call_hook);
call_hook = std::move(f);
return hk;
}
alias_stack &thread_state::get_astack(alias const *a) {
auto it = astacks.try_emplace(a->index());
if (it.second) {
auto *imp = const_cast<alias_impl *>(
static_cast<alias_impl const *>(a)
);
it.first->second.node = &imp->p_initial;
it.first->second.flags = imp->p_flags;
}
return it.first->second;
}
char *thread_state::request_errbuf(std::size_t bufs, char *&sp) {
errbuf.clear();
std::size_t sz = 0;
if (current_line) {
/* we can attach line number */
sz = source.size() + 32;
for (;;) {
/* we are using so the buffer tracks the elements and therefore
* does not wipe them when we attempt to reserve more capacity
*/
errbuf.resize(sz);
int nsz;
if (!source.empty()) {
nsz = std::snprintf(
errbuf.data(), sz, "%.*s:%zu: ",
int(source.size()), source.data(),
*current_line
);
} else {
nsz = std::snprintf(
errbuf.data(), sz, "%zu: ", *current_line
);
}
if (nsz <= 0) {
abort(); /* should be unreachable */
} else if (std::size_t(nsz) < sz) {
sz = std::size_t(nsz);
break;
}
sz = std::size_t(nsz + 1);
}
}
errbuf.resize(sz + bufs + 1);
sp = errbuf.data();
return &errbuf[sz];
}
} /* namespace cubescript */

69
src/cs_thread.hh 100644
View File

@ -0,0 +1,69 @@
#ifndef LIBCUBESCRIPT_THREAD_HH
#define LIBCUBESCRIPT_THREAD_HH
#include <cubescript/cubescript.hh>
#include <utility>
#include "cs_std.hh"
#include "cs_state.hh"
#include "cs_ident.hh"
namespace cubescript {
struct ident_level {
ident &id;
argset usedargs{};
ident_level(ident &i): id{i} {};
};
struct thread_state {
using astack_allocator = std_allocator<std::pair<int const, alias_stack>>;
/* the shared state pointer */
internal_state *istate{};
/* the public state interface */
state *pstate{};
/* VM stack */
valbuf<any_value> vmstack;
/* ident stack */
valbuf<ident_stack> idstack;
/* call stack */
valbuf<ident_level> callstack;
/* per-alias stack pointer */
std::unordered_map<
int, alias_stack, std::hash<int>, std::equal_to<int>, astack_allocator
> astacks;
/* per-thread storage buffer for error messages */
charbuf errbuf;
/* we can attach a hook to vm events */
hook_func call_hook{};
/* whether we own the internal state (i.e. not a side thread */
bool owner = false;
/* thread ident flags */
int ident_flags = 0;
/* call depth limit */
std::size_t max_call_depth = 1024;
/* current call depth */
std::size_t call_depth = 0;
/* loop nesting level */
std::size_t loop_level = 0;
/* debug info */
std::string_view source{};
std::size_t *current_line = nullptr;
thread_state(internal_state *cs);
hook_func set_hook(hook_func f);
hook_func &get_hook() { return call_hook; }
hook_func const &get_hook() const { return call_hook; }
alias_stack &get_astack(alias const *a);
char *request_errbuf(std::size_t bufs, char *&sp);
};
} /* namespace cubescript */
#endif /* LIBCUBESCRIPT_THREAD_HH */

View File

@ -1,385 +0,0 @@
#include <cubescript/cubescript.hh>
#include "cs_util.hh"
#include <ctype.h>
#include <math.h>
namespace cscript {
static inline void p_skip_white(ostd::string_range &v) {
while (!v.empty() && isspace(*v)) {
++v;
}
}
static inline void p_set_end(
const ostd::string_range &v, ostd::string_range *end
) {
if (!end) {
return;
}
*end = v;
}
/* this function assumes the input is definitely a hex digit */
static inline cs_int p_hexd_to_int(char c) {
if (c >= 97) { /* a-f */
return (c - 'a') + 10;
} else if (c >= 65) { /* A-F */
return (c - 'A') + 10;
}
/* 0-9 */
return c - '0';
}
static inline bool p_check_neg(ostd::string_range &input) {
bool neg = (*input == '-');
if (neg || (*input == '+')) {
++input;
}
return neg;
}
cs_int cs_parse_int(ostd::string_range input, ostd::string_range *end) {
ostd::string_range orig = input;
p_skip_white(input);
if (input.empty()) {
p_set_end(orig, end);
return cs_int(0);
}
bool neg = p_check_neg(input);
cs_int ret = 0;
ostd::string_range past = input;
if (input.size() >= 2) {
ostd::string_range pfx = input.slice(0, 2);
if ((pfx == "0x") || (pfx == "0X")) {
input = input.slice(2, input.size());
past = input;
while (!past.empty() && isxdigit(*past)) {
ret = ret * 16 + p_hexd_to_int(*past);
++past;
}
goto done;
} else if ((pfx == "0b") || (pfx == "0B")) {
input = input.slice(2, input.size());
past = input;
while (!past.empty() && ((*past == '0') || (*past == '1'))) {
ret = ret * 2 + (*past - '0');
++past;
}
goto done;
}
}
while (!past.empty() && isdigit(*past)) {
ret = ret * 10 + (*past - '0');
++past;
}
done:
if (&past[0] == &input[0]) {
p_set_end(orig, end);
} else {
p_set_end(past, end);
}
if (neg) {
return -ret;
}
return ret;
}
template<bool Hex, char e1 = Hex ? 'p' : 'e', char e2 = Hex ? 'P' : 'E'>
static inline bool p_read_exp(ostd::string_range &input, cs_int &fn) {
if (input.empty()) {
return true;
}
if ((*input != e1) && (*input != e2)) {
return true;
}
++input;
if (input.empty()) {
return false;
}
bool neg = p_check_neg(input);
if (input.empty() || !isdigit(*input)) {
return false;
}
cs_int exp = 0;
while (!input.empty() && isdigit(*input)) {
exp = exp * 10 + (*input - '0');
++input;
}
if (neg) {
exp = -exp;
}
fn += exp;
return true;
}
template<bool Hex>
static inline bool parse_gen_float(
ostd::string_range input, ostd::string_range *end, cs_float &ret
) {
auto read_digits = [&input](double r, cs_int &n) {
while (!input.empty() && (Hex ? isxdigit(*input) : isdigit(*input))) {
if (Hex) {
r = r * 16.0 + double(p_hexd_to_int(*input));
} else {
r = r * 10.0 + double(*input - '0');
}
++n;
++input;
}
return r;
};
cs_int wn = 0, fn = 0;
double r = read_digits(0.0, wn);
if (!input.empty() && (*input == '.')) {
++input;
r = read_digits(r, fn);
}
if (!wn && !fn) {
return false;
}
fn = -fn;
p_set_end(input, end); /* we have a valid number until here */
if (p_read_exp<Hex>(input, fn)) {
p_set_end(input, end);
}
if (Hex) {
ret = cs_float(ldexp(r, fn * 4));
} else {
ret = cs_float(r * pow(10, fn));
}
return true;
}
cs_float cs_parse_float(ostd::string_range input, ostd::string_range *end) {
ostd::string_range orig = input;
p_skip_white(input);
if (input.empty()) {
p_set_end(orig, end);
return cs_float(0);
}
bool neg = p_check_neg(input);
cs_float ret = cs_float(0);
if (input.size() >= 2) {
ostd::string_range pfx = input.slice(0, 2);
if ((pfx == "0x") || (pfx == "0X")) {
input = input.slice(2, input.size());
if (!parse_gen_float<true>(input, end, ret)) {
p_set_end(orig, end);
return ret;
}
goto done;
}
}
if (!parse_gen_float<false>(input, end, ret)) {
p_set_end(orig, end);
return ret;
}
done:
if (neg) {
return -ret;
}
return ret;
}
namespace util {
OSTD_EXPORT ostd::string_range parse_string(
cs_state &cs, ostd::string_range str, size_t &nlines
) {
size_t nl = 0;
nlines = nl;
if (str.empty() || (*str != '\"')) {
return str;
}
ostd::string_range orig = str;
++str;
++nl;
while (!str.empty()) {
switch (*str) {
case '\r':
case '\n':
case '\"':
goto end;
case '^':
case '\\': {
bool needn = (*str == '\\');
++str;
if (str.empty()) {
goto end;
}
if ((*str == '\r') || (*str == '\n')) {
char c = *str;
++str;
++nl;
if (!str.empty() && (c == '\r') && (*str == '\n')) {
++str;
}
} else if (needn) {
goto end;
} else {
++str;
}
continue;
}
}
++str;
}
end:
nlines = nl;
if (str.empty() || (*str != '\"')) {
throw cs_error(
cs, "unfinished string '%s'", orig.slice(0, &str[0] - &orig[0])
);
}
str.pop_front();
return str;
}
OSTD_EXPORT ostd::string_range parse_word(
cs_state &cs, ostd::string_range str
) {
for (;;) {
str = ostd::find_one_of(str, ostd::string_range("\"/;()[] \t\r\n"));
if (str.empty()) {
return str;
}
switch (*str) {
case '"':
case ';':
case ' ':
case '\t':
case '\r':
case '\n':
return str;
case '/':
if ((str.size() > 1) && (str[1] == '/')) {
return str;
}
break;
case '[':
str.pop_front();
str = parse_word(cs, str);
if (str.empty() || (*str != ']')) {
throw cs_error(cs, "missing \"]\"");
}
break;
case '(':
str.pop_front();
str = parse_word(cs, str);
if (str.empty() || (*str != ')')) {
throw cs_error(cs, "missing \")\"");
}
break;
case ']':
case ')':
return str;
}
++str;
}
return str;
}
void list_parser::skip() {
for (;;) {
while (!p_input.empty()) {
char c = *p_input;
if ((c == ' ') || (c == '\t') || (c == '\r') || (c == '\n')) {
++p_input;
} else {
break;
}
}
if ((p_input.size() < 2) || (p_input[0] != '/') || (p_input[1] != '/')) {
break;
}
p_input = ostd::find(p_input, '\n');
}
}
bool list_parser::parse() {
skip();
if (p_input.empty()) {
return false;
}
switch (*p_input) {
case '"':
p_quote = p_input;
p_input = parse_string(p_state, p_input);
p_quote = p_quote.slice(0, &p_input[0] - &p_quote[0]);
p_item = p_quote.slice(1, p_quote.size() - 1);
break;
case '(':
case '[': {
p_quote = p_input;
++p_input;
p_item = p_input;
char btype = *p_quote;
int brak = 1;
for (;;) {
p_input = ostd::find_one_of(
p_input, ostd::string_range("\"/;()[]")
);
if (p_input.empty()) {
return true;
}
char c = *p_input;
++p_input;
switch (c) {
case '"':
p_input = parse_string(p_state, p_input);
break;
case '/':
if (!p_input.empty() && (*p_input == '/')) {
p_input = ostd::find(p_input, '\n');
}
break;
case '(':
case '[':
brak += (c == btype);
break;
case ')':
if ((btype == '(') && (--brak <= 0)) {
goto endblock;
}
break;
case ']':
if ((btype == '[') && (--brak <= 0)) {
goto endblock;
}
break;
}
}
endblock:
p_item = p_item.slice(0, &p_input[0] - &p_item[0]);
p_item.pop_back();
p_quote = p_quote.slice(0, &p_input[0] - &p_quote[0]);
break;
}
case ')':
case ']':
return false;
default: {
ostd::string_range e = parse_word(p_state, p_input);
p_quote = p_item = p_input.slice(0, &e[0] - &p_input[0]);
p_input = e;
break;
}
}
skip();
if (!p_input.empty() && (*p_input == ';')) {
++p_input;
}
return true;
}
size_t list_parser::count() {
size_t ret = 0;
while (parse()) {
++ret;
}
return ret;
}
} /* namespace util */
} /* namespace cscript */

View File

@ -1,43 +0,0 @@
#ifndef LIBCUBESCRIPT_CS_UTIL_HH
#define LIBCUBESCRIPT_CS_UTIL_HH
#include <type_traits>
#include <unordered_map>
#include <ostd/string.hh>
namespace cscript {
template<typename K, typename V>
using cs_map = std::unordered_map<K, V>;
template<typename T>
using cs_vector = std::vector<T>;
cs_int cs_parse_int(
ostd::string_range input, ostd::string_range *end = nullptr
);
cs_float cs_parse_float(
ostd::string_range input, ostd::string_range *end = nullptr
);
template<typename F>
struct CsScopeExit {
template<typename FF>
CsScopeExit(FF &&f): func(std::forward<FF>(f)) {}
~CsScopeExit() {
func();
}
std::decay_t<F> func;
};
template<typename F1, typename F2>
inline void cs_do_and_cleanup(F1 &&dof, F2 &&clf) {
CsScopeExit<F2> cleanup(std::forward<F2>(clf));
dof();
}
} /* namespace cscript */
#endif /* LIBCUBESCRIPT_CS_UTIL_HH */

View File

@ -1,26 +1,69 @@
#include <cubescript/cubescript.hh>
#include "cs_vm.hh"
#include "cs_util.hh"
#include "cs_std.hh"
#include "cs_parser.hh"
#include "cs_state.hh"
#include "cs_strman.hh"
namespace cscript {
#include <cmath>
#include <cstdlib>
#include <iterator>
template<typename T, typename U>
static inline T &csv_get(U &stor) {
/* ugly, but internal and unlikely to cause bugs */
return const_cast<T &>(reinterpret_cast<T const &>(stor));
namespace cubescript {
static std::string_view intstr(integer_type v, charbuf &buf) {
buf.reserve(32);
int n = snprintf(buf.data(), 32, INTEGER_FORMAT, v);
if (n > 32) {
buf.reserve(n + 1);
int nn = snprintf(buf.data(), n + 1, INTEGER_FORMAT, v);
if ((nn > n) || (nn <= 0)) {
n = -1;
} else {
n = nn;
}
}
if (n <= 0) {
abort(); /* unreachable, provided a well-formed format string */
}
return std::string_view{buf.data(), std::size_t(n)};
}
static std::string_view floatstr(float_type v, charbuf &buf) {
buf.reserve(32);
int n;
if (v == std::floor(v)) {
n = snprintf(buf.data(), 32, ROUND_FLOAT_FORMAT, v);
} else {
n = snprintf(buf.data(), 32, FLOAT_FORMAT, v);
}
if (n > 32) {
buf.reserve(n + 1);
int nn;
if (v == std::floor(v)) {
nn = snprintf(buf.data(), n + 1, ROUND_FLOAT_FORMAT, v);
} else {
nn = snprintf(buf.data(), n + 1, FLOAT_FORMAT, v);
}
if ((nn > n) || (nn <= 0)) {
n = -1;
} else {
n = nn;
}
}
if (n <= 0) {
abort(); /* unreachable, provided a well-formed format string */
}
return std::string_view{buf.data(), std::size_t(n)};
}
template<typename T>
static inline void csv_cleanup(cs_value_type tv, T &stor) {
static inline void csv_cleanup(value_type tv, T *stor) {
switch (tv) {
case cs_value_type::String:
delete[] csv_get<char *>(stor);
case value_type::STRING:
str_managed_unref(stor->s);
break;
case cs_value_type::Code: {
uint32_t *bcode = csv_get<uint32_t *>(stor);
if (bcode[-1] == CsCodeStart) {
delete[] &bcode[-1];
}
case value_type::CODE: {
bcode_unref(stor->b->raw());
break;
}
default:
@ -28,40 +71,77 @@ static inline void csv_cleanup(cs_value_type tv, T &stor) {
}
}
cs_value::cs_value():
p_stor(), p_len(0), p_type(cs_value_type::Null)
any_value::any_value():
p_stor{}, p_type{value_type::NONE}
{}
cs_value::~cs_value() {
csv_cleanup(p_type, p_stor);
any_value::any_value(integer_type val):
p_stor{}, p_type{value_type::INTEGER}
{
p_stor.i = val;
}
cs_value::cs_value(cs_value const &v): cs_value() {
any_value::any_value(float_type val):
p_stor{}, p_type{value_type::FLOAT}
{
p_stor.f = val;
}
any_value::any_value(std::string_view val, state &cs):
p_stor{}, p_type{value_type::STRING}
{
p_stor.s = state_p{cs}.ts().istate->strman->add(val);
}
any_value::any_value(string_ref const &val):
p_stor{}, p_type{value_type::STRING}
{
p_stor.s = str_managed_ref(val.p_str);
}
any_value::any_value(bcode_ref const &val):
p_stor{}, p_type{value_type::CODE}
{
bcode *p = bcode_p{val}.get();
bcode_addref(p->raw());
p_stor.b = p;
}
any_value::any_value(ident &val):
p_stor{}, p_type{value_type::IDENT}
{
p_stor.v = &val;
}
any_value::~any_value() {
csv_cleanup(p_type, &p_stor);
}
any_value::any_value(any_value const &v): any_value{} {
*this = v;
}
cs_value::cs_value(cs_value &&v): cs_value() {
any_value::any_value(any_value &&v): any_value{} {
*this = std::move(v);
}
cs_value &cs_value::operator=(cs_value const &v) {
csv_cleanup(p_type, p_stor);
p_type = cs_value_type::Null;
switch (v.get_type()) {
case cs_value_type::Int:
case cs_value_type::Float:
case cs_value_type::Ident:
p_len = v.p_len;
any_value &any_value::operator=(any_value const &v) {
csv_cleanup(p_type, &p_stor);
p_type = value_type::NONE;
switch (v.type()) {
case value_type::INTEGER:
case value_type::FLOAT:
case value_type::IDENT:
p_type = v.p_type;
p_stor = v.p_stor;
std::memcpy(&p_stor, &v.p_stor, sizeof(p_stor));
break;
case cs_value_type::String:
case cs_value_type::Cstring:
case cs_value_type::Macro:
set_str(cs_string{csv_get<char const *>(v.p_stor), v.p_len});
case value_type::STRING:
p_type = value_type::STRING;
p_stor.s = v.p_stor.s;
str_managed_ref(p_stor.s);
break;
case cs_value_type::Code:
set_code(cs_copy_code(v.get_code()));
case value_type::CODE:
set_code(v.get_code());
break;
default:
break;
@ -69,94 +149,114 @@ cs_value &cs_value::operator=(cs_value const &v) {
return *this;
}
cs_value &cs_value::operator=(cs_value &&v) {
csv_cleanup(p_type, p_stor);
p_stor = v.p_stor;
p_type = v.p_type;
p_len = v.p_len;
v.p_type = cs_value_type::Null;
any_value &any_value::operator=(any_value &&v) {
*this = v;
v.set_none();
return *this;
}
cs_value_type cs_value::get_type() const {
any_value &any_value::operator=(integer_type val) {
set_integer(val);
return *this;
}
any_value &any_value::operator=(float_type val) {
set_float(val);
return *this;
}
any_value &any_value::operator=(string_ref const &val) {
set_string(val);
return *this;
}
any_value &any_value::operator=(bcode_ref const &val) {
set_code(val);
return *this;
}
any_value &any_value::operator=(ident &val) {
set_ident(val);
return *this;
}
value_type any_value::type() const {
return p_type;
}
void cs_value::set_int(cs_int val) {
csv_cleanup(p_type, p_stor);
p_type = cs_value_type::Int;
csv_get<cs_int>(p_stor) = val;
void any_value::set_integer(integer_type val) {
csv_cleanup(p_type, &p_stor);
p_type = value_type::INTEGER;
p_stor.i = val;
}
void cs_value::set_float(cs_float val) {
csv_cleanup(p_type, p_stor);
p_type = cs_value_type::Float;
csv_get<cs_float>(p_stor) = val;
void any_value::set_float(float_type val) {
csv_cleanup(p_type, &p_stor);
p_type = value_type::FLOAT;
p_stor.f = val;
}
void cs_value::set_str(cs_string val) {
csv_cleanup(p_type, p_stor);
p_type = cs_value_type::String;
p_len = val.size();
char *buf = new char[p_len + 1];
memcpy(buf, val.data(), p_len + 1);
csv_get<char *>(p_stor) = buf;
void any_value::set_string(std::string_view val, state &cs) {
csv_cleanup(p_type, &p_stor);
p_stor.s = state_p{cs}.ts().istate->strman->add(val);
p_type = value_type::STRING;
}
void cs_value::set_null() {
csv_cleanup(p_type, p_stor);
p_type = cs_value_type::Null;
void any_value::set_string(string_ref const &val) {
csv_cleanup(p_type, &p_stor);
p_stor.s = str_managed_ref(val.p_str);
p_type = value_type::STRING;
}
void cs_value::set_code(cs_bcode *val) {
csv_cleanup(p_type, p_stor);
p_type = cs_value_type::Code;
csv_get<cs_bcode *>(p_stor) = val;
void any_value::set_none() {
csv_cleanup(p_type, &p_stor);
p_type = value_type::NONE;
}
void cs_value::set_cstr(ostd::string_range val) {
csv_cleanup(p_type, p_stor);
p_type = cs_value_type::Cstring;
p_len = val.size();
csv_get<char const *>(p_stor) = val.data();
void any_value::set_code(bcode_ref const &val) {
bcode *p = bcode_p{val}.get();
csv_cleanup(p_type, &p_stor);
p_type = value_type::CODE;
bcode_addref(p->raw());
p_stor.b = p;
}
void cs_value::set_ident(cs_ident *val) {
csv_cleanup(p_type, p_stor);
p_type = cs_value_type::Ident;
csv_get<cs_ident *>(p_stor) = val;
void any_value::set_ident(ident &val) {
csv_cleanup(p_type, &p_stor);
p_type = value_type::IDENT;
p_stor.v = &val;
}
void cs_value::set_macro(ostd::string_range val) {
csv_cleanup(p_type, p_stor);
p_type = cs_value_type::Macro;
p_len = val.size();
csv_get<char const *>(p_stor) = val.data();
}
void cs_value::force_null() {
if (get_type() == cs_value_type::Null) {
void any_value::force_none() {
if (type() == value_type::NONE) {
return;
}
set_null();
set_none();
}
cs_float cs_value::force_float() {
cs_float rf = 0.0f;
switch (get_type()) {
case cs_value_type::Int:
rf = csv_get<cs_int>(p_stor);
void any_value::force_plain() {
switch (type()) {
case value_type::FLOAT:
case value_type::INTEGER:
case value_type::STRING:
return;
default:
break;
case cs_value_type::String:
case cs_value_type::Macro:
case cs_value_type::Cstring:
rf = cs_parse_float(ostd::string_range(
csv_get<char const *>(p_stor),
csv_get<char const *>(p_stor) + p_len
));
}
force_none();
}
float_type any_value::force_float() {
float_type rf = 0.0f;
switch (type()) {
case value_type::INTEGER:
rf = float_type(p_stor.i);
break;
case cs_value_type::Float:
return csv_get<cs_float>(p_stor);
case value_type::STRING:
rf = parse_float(str_managed_view(p_stor.s));
break;
case value_type::FLOAT:
return p_stor.f;
default:
break;
}
@ -164,270 +264,197 @@ cs_float cs_value::force_float() {
return rf;
}
cs_int cs_value::force_int() {
cs_int ri = 0;
switch (get_type()) {
case cs_value_type::Float:
ri = csv_get<cs_float>(p_stor);
integer_type any_value::force_integer() {
integer_type ri = 0;
switch (type()) {
case value_type::FLOAT:
ri = integer_type(std::floor(p_stor.f));
break;
case cs_value_type::String:
case cs_value_type::Macro:
case cs_value_type::Cstring:
ri = cs_parse_int(ostd::string_range(
csv_get<char const *>(p_stor),
csv_get<char const *>(p_stor) + p_len
));
case value_type::STRING:
ri = parse_int(str_managed_view(p_stor.s));
break;
case cs_value_type::Int:
return csv_get<cs_int>(p_stor);
case value_type::INTEGER:
return p_stor.i;
default:
break;
}
set_int(ri);
set_integer(ri);
return ri;
}
ostd::string_range cs_value::force_str() {
cs_string rs;
switch (get_type()) {
case cs_value_type::Float:
rs = floatstr(csv_get<cs_float>(p_stor));
std::string_view any_value::force_string(state &cs) {
charbuf rs{cs};
std::string_view str;
switch (type()) {
case value_type::FLOAT:
str = floatstr(p_stor.f, rs);
break;
case cs_value_type::Int:
rs = intstr(csv_get<cs_int>(p_stor));
case value_type::INTEGER:
str = intstr(p_stor.i, rs);
break;
case cs_value_type::Macro:
case cs_value_type::Cstring:
rs = ostd::string_range(
csv_get<char const *>(p_stor),
csv_get<char const *>(p_stor) + p_len
);
case value_type::STRING:
return str_managed_view(p_stor.s);
default:
str = rs.str();
break;
case cs_value_type::String:
return ostd::string_range(
csv_get<char const *>(p_stor),
csv_get<char const *>(p_stor) + p_len
);
}
set_string(str, cs);
return str_managed_view(p_stor.s);
}
bcode_ref any_value::force_code(state &cs, std::string_view source) {
switch (type()) {
case value_type::CODE:
return bcode_p::make_ref(p_stor.b);
default:
break;
}
set_str(std::move(rs));
return ostd::string_range(
csv_get<char const *>(p_stor),
csv_get<char const *>(p_stor) + p_len
);
gen_state gs{state_p{cs}.ts()};
gs.gen_main(get_string(cs), source);
auto bc = gs.steal_ref();
set_code(bc);
return bc;
}
cs_int cs_value::get_int() const {
switch (get_type()) {
case cs_value_type::Float:
return cs_int(csv_get<cs_float>(p_stor));
case cs_value_type::Int:
return csv_get<cs_int>(p_stor);
case cs_value_type::String:
case cs_value_type::Macro:
case cs_value_type::Cstring:
return cs_parse_int(ostd::string_range(
csv_get<char const *>(p_stor),
csv_get<char const *>(p_stor) + p_len
));
ident &any_value::force_ident(state &cs) {
switch (type()) {
case value_type::IDENT:
return *p_stor.v;
default:
break;
}
auto &id = state_p{cs}.ts().istate->new_ident(
cs, get_string(cs), IDENT_FLAG_UNKNOWN
);
set_ident(id);
return id;
}
integer_type any_value::get_integer() const {
switch (type()) {
case value_type::FLOAT:
return integer_type(std::floor(p_stor.f));
case value_type::INTEGER:
return p_stor.i;
case value_type::STRING:
return parse_int(str_managed_view(p_stor.s));
default:
break;
}
return 0;
}
cs_float cs_value::get_float() const {
switch (get_type()) {
case cs_value_type::Float:
return csv_get<cs_float>(p_stor);
case cs_value_type::Int:
return cs_float(csv_get<cs_int>(p_stor));
case cs_value_type::String:
case cs_value_type::Macro:
case cs_value_type::Cstring:
return cs_parse_float(ostd::string_range(
csv_get<char const *>(p_stor),
csv_get<char const *>(p_stor) + p_len
));
float_type any_value::get_float() const {
switch (type()) {
case value_type::FLOAT:
return p_stor.f;
case value_type::INTEGER:
return float_type(p_stor.i);
case value_type::STRING:
return parse_float(str_managed_view(p_stor.s));
default:
break;
}
return 0.0f;
}
cs_bcode *cs_value::get_code() const {
if (get_type() != cs_value_type::Code) {
return nullptr;
bcode_ref any_value::get_code() const {
if (type() != value_type::CODE) {
return bcode_ref{};
}
return csv_get<cs_bcode *>(p_stor);
return bcode_p::make_ref(p_stor.b);
}
cs_ident *cs_value::get_ident() const {
if (get_type() != cs_value_type::Ident) {
return nullptr;
ident &any_value::get_ident(state &cs) const {
if (type() != value_type::IDENT) {
return *state_p{cs}.ts().istate->id_dummy;
}
return csv_get<cs_ident *>(p_stor);
return *p_stor.v;
}
cs_string cs_value::get_str() const {
switch (get_type()) {
case cs_value_type::String:
case cs_value_type::Macro:
case cs_value_type::Cstring:
return cs_string{csv_get<char const *>(p_stor), p_len};
case cs_value_type::Int:
return intstr(csv_get<cs_int>(p_stor));
case cs_value_type::Float:
return floatstr(csv_get<cs_float>(p_stor));
string_ref any_value::get_string(state &cs) const {
switch (type()) {
case value_type::STRING:
return string_ref{p_stor.s};
case value_type::INTEGER: {
charbuf rs{cs};
return string_ref{cs, intstr(p_stor.i, rs)};
}
case value_type::FLOAT: {
charbuf rs{cs};
return string_ref{cs, floatstr(p_stor.f, rs)};
}
default:
break;
}
return cs_string("");
return string_ref{cs, ""};
}
ostd::string_range cs_value::get_strr() const {
switch (get_type()) {
case cs_value_type::String:
case cs_value_type::Macro:
case cs_value_type::Cstring:
return ostd::string_range(
csv_get<char const *>(p_stor),
csv_get<char const *>(p_stor)+ p_len
);
any_value any_value::get_plain() const {
switch (type()) {
case value_type::STRING:
case value_type::INTEGER:
case value_type::FLOAT:
return *this;
default:
break;
}
return ostd::string_range();
return any_value{};
}
void cs_value::get_val(cs_value &r) const {
switch (get_type()) {
case cs_value_type::String:
case cs_value_type::Macro:
case cs_value_type::Cstring:
r.set_str(
cs_string{csv_get<char const *>(p_stor), p_len}
);
break;
case cs_value_type::Int:
r.set_int(csv_get<cs_int>(p_stor));
break;
case cs_value_type::Float:
r.set_float(csv_get<cs_float>(p_stor));
break;
default:
r.set_null();
break;
}
}
OSTD_EXPORT bool cs_code_is_empty(cs_bcode *code) {
if (!code) {
return true;
}
return (
*reinterpret_cast<uint32_t *>(code) & CsCodeOpMask
) == CsCodeExit;
}
bool cs_value::code_is_empty() const {
if (get_type() != cs_value_type::Code) {
return true;
}
return cscript::cs_code_is_empty(csv_get<cs_bcode *>(p_stor));
}
static inline bool cs_get_bool(ostd::string_range s) {
if (s.empty()) {
return false;
}
ostd::string_range end = s;
cs_int ival = cs_parse_int(end, &end);
if (end.empty()) {
return !!ival;
}
end = s;
cs_float fval = cs_parse_float(end, &end);
if (end.empty()) {
return !!fval;
}
return true;
}
bool cs_value::get_bool() const {
switch (get_type()) {
case cs_value_type::Float:
return csv_get<cs_float>(p_stor) != 0;
case cs_value_type::Int:
return csv_get<cs_int>(p_stor) != 0;
case cs_value_type::String:
case cs_value_type::Macro:
case cs_value_type::Cstring:
return cs_get_bool(ostd::string_range(
csv_get<char const *>(p_stor),
csv_get<char const *>(p_stor) + p_len
));
bool any_value::get_bool() const {
switch (type()) {
case value_type::FLOAT:
return p_stor.f != 0;
case value_type::INTEGER:
return p_stor.i != 0;
case value_type::STRING: {
std::string_view s = str_managed_view(p_stor.s);
if (s.empty()) {
return false;
}
std::string_view end = s;
integer_type ival = parse_int(end, &end);
if (end.empty()) {
return !!ival;
}
end = s;
float_type fval = parse_float(end, &end);
if (end.empty()) {
return !!fval;
}
return true;
}
default:
return false;
}
}
/* stacked value for easy stack management */
/* public utilities */
cs_stacked_value::cs_stacked_value(cs_ident *id):
cs_value(), p_a(nullptr), p_stack(), p_pushed(false)
{
set_alias(id);
}
cs_stacked_value::~cs_stacked_value() {
pop();
static_cast<cs_value *>(this)->~cs_value();
}
cs_stacked_value &cs_stacked_value::operator=(cs_value const &v) {
*static_cast<cs_value *>(this) = v;
return *this;
}
cs_stacked_value &cs_stacked_value::operator=(cs_value &&v) {
*static_cast<cs_value *>(this) = std::move(v);
return *this;
}
bool cs_stacked_value::set_alias(cs_ident *id) {
if (!id || !id->is_alias()) {
return false;
LIBCUBESCRIPT_EXPORT string_ref concat_values(
state &cs, span_type<any_value> vals, std::string_view sep
) {
charbuf buf{cs};
for (std::size_t i = 0; i < vals.size(); ++i) {
switch (vals[i].type()) {
case value_type::INTEGER:
case value_type::FLOAT:
case value_type::STRING: {
auto val = any_value{vals[i]};
auto str = val.force_string(cs);
std::copy(str.begin(), str.end(), std::back_inserter(buf));
break;
}
default:
break;
}
if (i == (vals.size() - 1)) {
break;
}
std::copy(sep.begin(), sep.end(), std::back_inserter(buf));
}
p_a = static_cast<cs_alias *>(id);
return true;
return string_ref{cs, buf.str()};
}
cs_alias *cs_stacked_value::get_alias() const {
return p_a;
}
bool cs_stacked_value::has_alias() const {
return p_a != nullptr;
}
bool cs_stacked_value::push() {
if (!p_a) {
return false;
}
cs_alias_internal::push_arg(p_a, *this, p_stack);
p_pushed = true;
return true;
}
bool cs_stacked_value::pop() {
if (!p_pushed || !p_a) {
return false;
}
cs_alias_internal::pop_arg(p_a);
p_pushed = false;
return true;
}
} /* namespace cscript */
} /* namespace cubescript */

File diff suppressed because it is too large Load Diff

View File

@ -1,447 +1,38 @@
#ifndef LIBCUBESCRIPT_CS_VM_HH
#define LIBCUBESCRIPT_CS_VM_HH
#ifndef LIBCUBESCRIPT_VM_HH
#define LIBCUBESCRIPT_VM_HH
#include "cubescript/cubescript.hh"
#include <cubescript/cubescript.hh>
#include <cstdlib>
#include <array>
#include <vector>
#include "cs_std.hh"
#include "cs_ident.hh"
#include "cs_thread.hh"
#include "cs_util.hh"
#include <utility>
namespace cscript {
namespace cubescript {
static constexpr int MaxArguments = 25;
static constexpr int MaxResults = 7;
static constexpr int DummyIdx = MaxArguments;
static constexpr int NumargsIdx = MaxArguments + 1;
static constexpr int DbgaliasIdx = MaxArguments + 2;
enum {
CsIdUnknown = -1, CsIdIvar, CsIdFvar, CsIdSvar, CsIdCommand, CsIdAlias,
CsIdLocal, CsIdDo, CsIdDoArgs, CsIdIf, CsIdBreak, CsIdContinue, CsIdResult,
CsIdNot, CsIdAnd, CsIdOr
struct break_exception {
};
struct cs_identLink {
cs_ident *id;
cs_identLink *next;
int usedargs;
cs_ident_stack *argstack;
struct continue_exception {
};
enum {
CsValNull = 0, CsValInt, CsValFloat, CsValString,
CsValAny, CsValCode, CsValMacro, CsValIdent, CsValCstring,
CsValCany, CsValWord, CsValPop, CsValCond
};
void exec_command(
thread_state &ts, command_impl *id, ident *self, any_value *args,
any_value &res, std::size_t nargs, bool lookup = false
);
static const int cs_valtypet[] = {
CsValNull, CsValInt, CsValFloat, CsValString,
CsValCstring, CsValCode, CsValMacro, CsValIdent
};
any_value exec_alias(
thread_state &ts, alias *a, any_value *args,
std::size_t callargs, alias_stack &astack
);
static inline int cs_vtype_to_int(cs_value_type v) {
return cs_valtypet[int(v)];
}
any_value exec_code_with_args(thread_state &ts, bcode_ref const &body);
/* instruction: uint32 [length 24][retflag 2][opcode 6] */
enum {
CsCodeStart = 0,
CsCodeOffset,
CsCodeNull, CsCodeTrue, CsCodeFalse, CsCodeNot,
CsCodePop,
CsCodeEnter, CsCodeEnterResult,
CsCodeExit, CsCodeResultArg,
CsCodeVal, CsCodeValInt,
CsCodeDup,
CsCodeMacro,
CsCodeBool,
CsCodeBlock, CsCodeEmpty,
CsCodeCompile, CsCodeCond,
CsCodeForce,
CsCodeResult,
CsCodeIdent, CsCodeIdentU, CsCodeIdentArg,
CsCodeCom, CsCodeComC, CsCodeComV,
CsCodeConc, CsCodeConcW, CsCodeConcM,
CsCodeSvar, CsCodeSvarM, CsCodeSvar1,
CsCodeIvar, CsCodeIvar1, CsCodeIvar2, CsCodeIvar3,
CsCodeFvar, CsCodeFvar1,
CsCodeLookup, CsCodeLookupU, CsCodeLookupArg,
CsCodeLookupM, CsCodeLookupMu, CsCodeLookupMarg,
CsCodeAlias, CsCodeAliasU, CsCodeAliasArg,
CsCodeCall, CsCodeCallU, CsCodeCallArg,
CsCodePrint,
CsCodeLocal,
CsCodeDo, CsCodeDoArgs,
CsCodeJump, CsCodeJumpB, CsCodeJumpResult,
CsCodeBreak,
std::uint32_t *vm_exec(
thread_state &ts, std::uint32_t *code, any_value &result
);
CsCodeOpMask = 0x3F,
CsCodeRet = 6,
CsCodeRetMask = 0xC0,
} /* namespace cubescript */
/* return type flags */
CsRetNull = CsValNull << CsCodeRet,
CsRetString = CsValString << CsCodeRet,
CsRetInt = CsValInt << CsCodeRet,
CsRetFloat = CsValFloat << CsCodeRet,
/* CsCodeJumpB, CsCodeJumpResult */
CsCodeFlagTrue = 1 << CsCodeRet,
CsCodeFlagFalse = 0 << CsCodeRet
};
struct cs_shared_state {
cs_map<ostd::string_range, cs_ident *> idents;
cs_vector<cs_ident *> identmap;
cs_alloc_cb allocf;
void *aptr;
void *alloc(void *ptr, size_t os, size_t ns) {
return allocf(aptr, ptr, os, ns);
}
template<typename T, typename ...A>
T *create(A &&...args) {
T *ret = static_cast<T *>(alloc(nullptr, 0, sizeof(T)));
new (ret) T(std::forward<A>(args)...);
return ret;
}
template<typename T>
T *create_array(size_t len) {
T *ret = static_cast<T *>(alloc(nullptr, 0, len * sizeof(T)));
for (size_t i = 0; i < len; ++i) {
new (&ret[i]) T();
}
return ret;
}
template<typename T>
void destroy(T *v) noexcept {
v->~T();
alloc(v, sizeof(T), 0);
}
template<typename T>
void destroy_array(T *v, size_t len) noexcept {
v->~T();
alloc(v, len * sizeof(T), 0);
}
};
struct CsBreakException {
};
struct CsContinueException {
};
template<typename T>
constexpr size_t CsTypeStorageSize =
(sizeof(T) - 1) / sizeof(uint32_t) + 1;
struct cs_gen_state {
cs_state &cs;
cs_gen_state *prevps;
bool parsing = true;
cs_vector<uint32_t> code;
ostd::string_range source;
size_t current_line;
ostd::string_range src_name;
cs_gen_state() = delete;
cs_gen_state(cs_state &csr):
cs(csr), prevps(csr.p_pstate), code(),
source(nullptr), current_line(1), src_name()
{
csr.p_pstate = this;
}
~cs_gen_state() {
done();
}
void done() {
if (!parsing) {
return;
}
cs.p_pstate = prevps;
parsing = false;
}
ostd::string_range get_str();
cs_string get_str_dup(bool unescape = true);
ostd::string_range get_word();
void gen_str(ostd::string_range word, bool macro = false) {
if (word.size() <= 3 && !macro) {
uint32_t op = CsCodeValInt | CsRetString;
for (size_t i = 0; i < word.size(); ++i) {
op |= uint32_t(
static_cast<unsigned char>(word[i])
) << ((i + 1) * 8);
}
code.push_back(op);
return;
}
code.push_back(
(macro ? CsCodeMacro : (CsCodeVal | CsRetString)) | (word.size() << 8)
);
auto it = reinterpret_cast<uint32_t const *>(word.data());
code.insert(
code.end(), it, it + (word.size() / sizeof(uint32_t))
);
size_t esz = word.size() % sizeof(uint32_t);
union {
char c[sizeof(uint32_t)];
uint32_t u;
} end;
end.u = 0;
memcpy(end.c, word.data() + word.size() - esz, esz);
code.push_back(end.u);
}
void gen_str() {
code.push_back(CsCodeValInt | CsRetString);
}
void gen_null() {
code.push_back(CsCodeValInt | CsRetNull);
}
void gen_int(cs_int i = 0) {
if (i >= -0x800000 && i <= 0x7FFFFF) {
code.push_back(CsCodeValInt | CsRetInt | (i << 8));
} else {
union {
cs_int i;
uint32_t u[CsTypeStorageSize<cs_int>];
} c;
c.i = i;
code.push_back(CsCodeVal | CsRetInt);
code.insert(code.end(), c.u, c.u + CsTypeStorageSize<cs_int>);
}
}
void gen_int(ostd::string_range word);
void gen_float(cs_float f = 0.0f) {
if (cs_int(f) == f && f >= -0x800000 && f <= 0x7FFFFF) {
code.push_back(CsCodeValInt | CsRetFloat | (cs_int(f) << 8));
} else {
union {
cs_float f;
uint32_t u[CsTypeStorageSize<cs_float>];
} c;
c.f = f;
code.push_back(CsCodeVal | CsRetFloat);
code.insert(code.end(), c.u, c.u + CsTypeStorageSize<cs_float>);
}
}
void gen_float(ostd::string_range word);
void gen_ident(cs_ident *id) {
code.push_back(
((id->get_index() < MaxArguments)
? CsCodeIdentArg
: CsCodeIdent
) | (id->get_index() << 8)
);
}
void gen_ident() {
gen_ident(cs.p_state->identmap[DummyIdx]);
}
void gen_ident(ostd::string_range word) {
gen_ident(cs.new_ident(word));
}
void gen_value(
int wordtype, ostd::string_range word = ostd::string_range(),
int line = 0
);
void gen_main(ostd::string_range s, int ret_type = CsValAny);
void next_char() {
if (source.empty()) {
return;
}
if (*source == '\n') {
++current_line;
}
source.pop_front();
}
char current(size_t ahead = 0) {
if (source.size() <= ahead) {
return '\0';
}
return source[ahead];
}
ostd::string_range read_macro_name();
char skip_until(ostd::string_range chars);
char skip_until(char cf);
void skip_comments();
};
cs_string intstr(cs_int v);
cs_string floatstr(cs_float v);
bool cs_check_num(ostd::string_range s);
static inline void bcode_incr(uint32_t *bc) {
*bc += 0x100;
}
static inline void bcode_decr(uint32_t *bc) {
*bc -= 0x100;
if (std::int32_t(*bc) < 0x100) {
delete[] bc;
}
}
static inline bool cs_is_arg_used(cs_state &cs, cs_ident *id) {
if (!cs.p_callstack) {
return true;
}
return cs.p_callstack->usedargs & (1 << id->get_index());
}
struct cs_alias_internal {
static void push_arg(
cs_alias *a, cs_value &v, cs_ident_stack &st, bool um = true
) {
if (a->p_astack == &st) {
/* prevent cycles and unnecessary code elsewhere */
a->p_val = std::move(v);
clean_code(a);
return;
}
st.val_s = std::move(a->p_val);
st.next = a->p_astack;
a->p_astack = &st;
a->p_val = std::move(v);
clean_code(a);
if (um) {
a->p_flags &= ~CS_IDF_UNKNOWN;
}
}
static void pop_arg(cs_alias *a) {
if (!a->p_astack) {
return;
}
cs_ident_stack *st = a->p_astack;
a->p_val = std::move(a->p_astack->val_s);
clean_code(a);
a->p_astack = st->next;
}
static void undo_arg(cs_alias *a, cs_ident_stack &st) {
cs_ident_stack *prev = a->p_astack;
st.val_s = std::move(a->p_val);
st.next = prev;
a->p_astack = prev->next;
a->p_val = std::move(prev->val_s);
clean_code(a);
}
static void redo_arg(cs_alias *a, cs_ident_stack &st) {
cs_ident_stack *prev = st.next;
prev->val_s = std::move(a->p_val);
a->p_astack = prev;
a->p_val = std::move(st.val_s);
clean_code(a);
}
static void set_arg(cs_alias *a, cs_state &cs, cs_value &v) {
if (cs_is_arg_used(cs, a)) {
a->p_val = std::move(v);
clean_code(a);
} else {
push_arg(a, v, cs.p_callstack->argstack[a->get_index()], false);
cs.p_callstack->usedargs |= 1 << a->get_index();
}
}
static void set_alias(cs_alias *a, cs_state &cs, cs_value &v) {
a->p_val = std::move(v);
clean_code(a);
a->p_flags = (a->p_flags & cs.identflags) | cs.identflags;
}
static void clean_code(cs_alias *a) {
uint32_t *bcode = reinterpret_cast<uint32_t *>(a->p_acode);
if (bcode) {
bcode_decr(bcode);
a->p_acode = nullptr;
}
}
static cs_bcode *compile_code(cs_alias *a, cs_state &cs) {
if (!a->p_acode) {
cs_gen_state gs(cs);
gs.code.reserve(64);
gs.gen_main(a->get_value().get_str());
/* i wish i could steal the memory somehow */
uint32_t *code = new uint32_t[gs.code.size()];
memcpy(code, gs.code.data(), gs.code.size() * sizeof(uint32_t));
bcode_incr(code);
a->p_acode = reinterpret_cast<cs_bcode *>(code);
}
return a->p_acode;
}
};
template<typename F>
static void cs_do_args(cs_state &cs, F body) {
if (!cs.p_callstack) {
body();
return;
}
cs_ident_stack argstack[MaxArguments];
int argmask1 = cs.p_callstack->usedargs;
for (int i = 0; argmask1; argmask1 >>= 1, ++i) {
if (argmask1 & 1) {
cs_alias_internal::undo_arg(
static_cast<cs_alias *>(cs.p_state->identmap[i]), argstack[i]
);
}
}
cs_identLink *prevstack = cs.p_callstack->next;
cs_identLink aliaslink = {
cs.p_callstack->id, cs.p_callstack,
prevstack ? prevstack->usedargs : ((1 << MaxArguments) - 1),
prevstack ? prevstack->argstack : nullptr
};
cs.p_callstack = &aliaslink;
cs_do_and_cleanup(std::move(body), [&]() {
if (prevstack) {
prevstack->usedargs = aliaslink.usedargs;
}
cs.p_callstack = aliaslink.next;
int argmask2 = cs.p_callstack->usedargs;
for (int i = 0; argmask2; argmask2 >>= 1, ++i) {
if (argmask2 & 1) {
cs_alias_internal::redo_arg(
static_cast<cs_alias *>(cs.p_state->identmap[i]), argstack[i]
);
}
}
});
}
cs_bcode *cs_copy_code(cs_bcode *c);
} /* namespace cscript */
#endif /* LIBCUBESCRIPT_CS_VM_HH */
#endif /* LIBCUBESCRIPT_VM_HH */

File diff suppressed because it is too large Load Diff

382
src/lib_base.cc 100644
View File

@ -0,0 +1,382 @@
#include <cubescript/cubescript.hh>
#include <iterator>
#include "cs_std.hh"
#include "cs_ident.hh"
#include "cs_thread.hh"
#include "cs_error.hh"
namespace cubescript {
static inline void do_loop(
state &cs, ident &id, integer_type offset, integer_type n, integer_type step,
bcode_ref &&cond, bcode_ref &&body
) {
if (n <= 0) {
return;
}
alias_local st{cs, id};
any_value idv{};
for (integer_type i = 0; i < n; ++i) {
idv.set_integer(offset + i * step);
st.set(idv);
if (cond && !cond.call(cs).get_bool()) {
break;
}
switch (body.call_loop(cs)) {
case loop_state::BREAK:
return;
default: /* continue and normal */
break;
}
}
}
static inline void do_loop_conc(
state &cs, any_value &res, ident &id, integer_type offset, integer_type n,
integer_type step, bcode_ref &&body, bool space
) {
if (n <= 0) {
return;
}
alias_local st{cs, id};
charbuf s{cs};
any_value idv{};
for (integer_type i = 0; i < n; ++i) {
idv.set_integer(offset + i * step);
st.set(idv);
any_value v{};
switch (body.call_loop(cs, v)) {
case loop_state::BREAK:
goto end;
case loop_state::CONTINUE:
continue;
default:
break;
}
if (space && i) {
s.push_back(' ');
}
s.append(v.get_string(cs));
}
end:
res.set_string(s.str(), cs);
}
LIBCUBESCRIPT_EXPORT void std_init_base(state &gcs) {
new_cmd_quiet(gcs, "error", "s", [](auto &cs, auto args, auto &) {
throw error{cs, args[0].get_string(cs)};
});
new_cmd_quiet(gcs, "pcall", "bvvvb", [](auto &cs, auto args, auto &ret) {
auto &ts = state_p{cs}.ts();
auto &cret = args[1].get_ident(cs);
if (cret.type() != ident_type::ALIAS) {
throw error_p::make(cs, "'%s' is not an alias", cret.name().data());
}
auto *ra = static_cast<alias *>(&cret);
any_value result{};
try {
result = args[0].get_code().call(cs);
} catch (error const &e) {
auto val = any_value{e.what(), cs};
auto tb = any_value{};
val.set_string(e.what(), cs);
ts.get_astack(ra).set_alias(ra, ts, val);
if (auto nds = e.stack(); !nds.empty()) {
auto bc = args[4].get_code();
if (!bc.empty()) {
alias_local ist{cs, args[2].get_ident(cs)};
alias_local vst{cs, args[3].get_ident(cs)};
any_value idv{};
for (auto &nd: nds) {
idv.set_integer(integer_type(nd.index));
ist.set(idv);
idv.set_string(nd.id.name().data(), cs);
vst.set(idv);
bc.call(cs);
}
}
}
ret.set_integer(0);
return;
}
ret.set_integer(1);
ts.get_astack(ra).set_alias(ra, ts, result);
});
new_cmd_quiet(gcs, "assert", "ss#", [](auto &s, auto args, auto &ret) {
auto val = args[0];
val.force_code(s);
if (!val.get_code().call(s).get_bool()) {
if (args[2].get_integer() > 1) {
throw error_p::make(
s, "assertion failed: [%s] (%s)",
args[0].get_string(s).data(), args[1].get_string(s).data()
);
} else {
throw error_p::make(
s, "assertion failed: [%s]",
args[0].get_string(s).data()
);
}
}
ret = std::move(args[0]);
});
new_cmd_quiet(gcs, "?", "aaa", [](auto &, auto args, auto &res) {
if (args[0].get_bool()) {
res = std::move(args[1]);
} else {
res = std::move(args[2]);
}
});
new_cmd_quiet(gcs, "cond", "bb2...", [](auto &cs, auto args, auto &res) {
for (size_t i = 0; i < args.size(); i += 2) {
if ((i + 1) < args.size()) {
if (args[i].get_code().call(cs).get_bool()) {
res = args[i + 1].get_code().call(cs);
break;
}
} else {
res = args[i].get_code().call(cs);
break;
}
}
});
new_cmd_quiet(gcs, "case", "iab2...", [](auto &cs, auto args, auto &res) {
integer_type val = args[0].get_integer();
for (size_t i = 1; (i + 1) < args.size(); i += 2) {
if (
(args[i].type() == value_type::NONE) ||
(args[i].get_integer() == val)
) {
res = args[i + 1].get_code().call(cs);
return;
}
}
});
new_cmd_quiet(gcs, "casef", "fab2...", [](auto &cs, auto args, auto &res) {
float_type val = args[0].get_float();
for (size_t i = 1; (i + 1) < args.size(); i += 2) {
if (
(args[i].type() == value_type::NONE) ||
(args[i].get_float() == val)
) {
res = args[i + 1].get_code().call(cs);
return;
}
}
});
new_cmd_quiet(gcs, "cases", "sab2...", [](auto &cs, auto args, auto &res) {
string_ref val = args[0].get_string(cs);
for (size_t i = 1; (i + 1) < args.size(); i += 2) {
if (
(args[i].type() == value_type::NONE) ||
(args[i].get_string(cs) == val)
) {
res = args[i + 1].get_code().call(cs);
return;
}
}
});
new_cmd_quiet(gcs, "pushif", "vab", [](auto &cs, auto args, auto &res) {
alias_local st{cs, args[0]};
if (st.get_alias().is_arg()) {
throw error{cs, "cannot push an argument"};
}
if (args[1].get_bool()) {
st.set(args[1]);
res = args[2].get_code().call(cs);
}
});
new_cmd_quiet(gcs, "loop", "vab", [](auto &cs, auto args, auto &) {
do_loop(
cs, args[0].get_ident(cs), 0, args[1].get_integer(), 1,
bcode_ref{}, args[2].get_code()
);
});
new_cmd_quiet(gcs, "loop+", "viib", [](auto &cs, auto args, auto &) {
do_loop(
cs, args[0].get_ident(cs), args[1].get_integer(),
args[2].get_integer(), 1, bcode_ref{}, args[3].get_code()
);
});
new_cmd_quiet(gcs, "loop*", "viib", [](auto &cs, auto args, auto &) {
do_loop(
cs, args[0].get_ident(cs), 0, args[1].get_integer(),
args[2].get_integer(), bcode_ref{}, args[3].get_code()
);
});
new_cmd_quiet(gcs, "loop+*", "viiib", [](auto &cs, auto args, auto &) {
do_loop(
cs, args[0].get_ident(cs), args[1].get_integer(),
args[3].get_integer(), args[2].get_integer(),
bcode_ref{}, args[4].get_code()
);
});
new_cmd_quiet(gcs, "loopwhile", "vibb", [](auto &cs, auto args, auto &) {
do_loop(
cs, args[0].get_ident(cs), 0, args[1].get_integer(), 1,
args[2].get_code(), args[3].get_code()
);
});
new_cmd_quiet(gcs, "loopwhile+", "viibb", [](auto &cs, auto args, auto &) {
do_loop(
cs, args[0].get_ident(cs), args[1].get_integer(),
args[2].get_integer(), 1, args[3].get_code(), args[4].get_code()
);
});
new_cmd_quiet(gcs, "loopwhile*", "viibb", [](auto &cs, auto args, auto &) {
do_loop(
cs, args[0].get_ident(cs), 0, args[2].get_integer(),
args[1].get_integer(), args[3].get_code(), args[4].get_code()
);
});
new_cmd_quiet(gcs, "loopwhile+*", "viiibb", [](
auto &cs, auto args, auto &
) {
do_loop(
cs, args[0].get_ident(cs), args[1].get_integer(),
args[3].get_integer(), args[2].get_integer(), args[4].get_code(),
args[5].get_code()
);
});
new_cmd_quiet(gcs, "while", "bb", [](auto &cs, auto args, auto &) {
auto cond = args[0].get_code();
auto body = args[1].get_code();
while (cond.call(cs).get_bool()) {
switch (body.call_loop(cs)) {
case loop_state::BREAK:
goto end;
default: /* continue and normal */
break;
}
}
end:
return;
});
new_cmd_quiet(gcs, "loopconcat", "vib", [](
auto &cs, auto args, auto &res
) {
do_loop_conc(
cs, res, args[0].get_ident(cs), 0, args[1].get_integer(), 1,
args[2].get_code(), true
);
});
new_cmd_quiet(gcs, "loopconcat+", "viib", [](
auto &cs, auto args, auto &res
) {
do_loop_conc(
cs, res, args[0].get_ident(cs), args[1].get_integer(),
args[2].get_integer(), 1, args[3].get_code(), true
);
});
new_cmd_quiet(gcs, "loopconcat*", "viib", [](
auto &cs, auto args, auto &res
) {
do_loop_conc(
cs, res, args[0].get_ident(cs), 0, args[2].get_integer(),
args[1].get_integer(), args[3].get_code(), true
);
});
new_cmd_quiet(gcs, "loopconcat+*", "viiib", [](
auto &cs, auto args, auto &res
) {
do_loop_conc(
cs, res, args[0].get_ident(cs), args[1].get_integer(),
args[3].get_integer(), args[2].get_integer(),
args[4].get_code(), true
);
});
new_cmd_quiet(gcs, "loopconcatword", "vib", [](
auto &cs, auto args, auto &res
) {
do_loop_conc(
cs, res, args[0].get_ident(cs), 0, args[1].get_integer(), 1,
args[2].get_code(), false
);
});
new_cmd_quiet(gcs, "loopconcatword+", "viib", [](
auto &cs, auto args, auto &res
) {
do_loop_conc(
cs, res, args[0].get_ident(cs), args[1].get_integer(),
args[2].get_integer(), 1, args[3].get_code(), false
);
});
new_cmd_quiet(gcs, "loopconcatword*", "viib", [](
auto &cs, auto args, auto &res
) {
do_loop_conc(
cs, res, args[0].get_ident(cs), 0, args[2].get_integer(),
args[1].get_integer(), args[3].get_code(), false
);
});
new_cmd_quiet(gcs, "loopconcatword+*", "viiib", [](
auto &cs, auto args, auto &res
) {
do_loop_conc(
cs, res, args[0].get_ident(cs), args[1].get_integer(),
args[3].get_integer(), args[2].get_integer(),
args[4].get_code(), false
);
});
new_cmd_quiet(gcs, "push", "vab", [](auto &cs, auto args, auto &res) {
alias_local st{cs, args[0]};
if (st.get_alias().is_arg()) {
throw error{cs, "cannot push an argument"};
}
st.set(args[1]);
res = args[2].get_code().call(cs);
});
new_cmd_quiet(gcs, "resetvar", "s", [](auto &cs, auto args, auto &) {
cs.reset_value(args[0].get_string(cs));
});
new_cmd_quiet(gcs, "alias", "sa", [](auto &cs, auto args, auto &) {
cs.assign_value(args[0].get_string(cs), args[1]);
});
new_cmd_quiet(gcs, "identexists", "s", [](auto &cs, auto args, auto &res) {
res.set_integer(cs.get_ident(args[0].get_string(cs)) != std::nullopt);
});
new_cmd_quiet(gcs, "getalias", "s", [](auto &cs, auto args, auto &res) {
auto &id = cs.new_ident(args[0].get_string(cs));
if (id.type() != ident_type::ALIAS) {
throw error_p::make(cs, "'%s' is not an alias", id.name().data());
}
if (ident_p{id}.impl().p_flags & IDENT_FLAG_UNKNOWN) {
return;
}
res = static_cast<alias &>(id).value(cs);
});
}
} /* namespace cubescript */

View File

@ -1,43 +1,46 @@
#include <functional>
#include <iterator>
#include <cubescript/cubescript.hh>
#include "cs_util.hh"
#include "cs_std.hh"
#include "cs_parser.hh"
#include "cs_thread.hh"
namespace cscript {
namespace cubescript {
template<typename T>
struct cs_arg_val;
struct arg_val;
template<>
struct cs_arg_val<cs_int> {
static cs_int get(cs_value &tv) {
return tv.get_int();
struct arg_val<integer_type> {
static integer_type get(any_value &tv, state &) {
return tv.get_integer();
}
};
template<>
struct cs_arg_val<cs_float> {
static cs_float get(cs_value &tv) {
struct arg_val<float_type> {
static float_type get(any_value &tv, state &) {
return tv.get_float();
}
};
template<>
struct cs_arg_val<ostd::string_range> {
static ostd::string_range get(cs_value &tv) {
return tv.get_strr();
struct arg_val<std::string_view> {
static std::string_view get(any_value &tv, state &cs) {
return tv.get_string(cs);
}
};
template<typename T, typename F>
static inline void cs_list_find(
cs_state &cs, cs_value_r args, cs_value &res, F cmp
static inline void list_find(
state &cs, span_type<any_value> args, any_value &res, F cmp
) {
cs_int n = 0, skip = args[2].get_int();
T val = cs_arg_val<T>::get(args[1]);
for (util::list_parser p(cs, args[0].get_strr()); p.parse(); ++n) {
integer_type n = 0, skip = args[2].get_integer();
T val = arg_val<T>::get(args[1], cs);
for (list_parser p{cs, args[0].get_string(cs)}; p.parse(); ++n) {
if (cmp(p, val)) {
res.set_int(n);
res.set_integer(n);
return;
}
for (int i = 0; i < skip; ++i) {
@ -48,18 +51,18 @@ static inline void cs_list_find(
}
}
notfound:
res.set_int(-1);
res.set_integer(-1);
}
template<typename T, typename F>
static inline void cs_list_assoc(
cs_state &cs, cs_value_r args, cs_value &res, F cmp
static inline void list_assoc(
state &cs, span_type<any_value> args, any_value &res, F cmp
) {
T val = cs_arg_val<T>::get(args[1]);
for (util::list_parser p(cs, args[0].get_strr()); p.parse();) {
T val = arg_val<T>::get(args[1], cs);
for (list_parser p{cs, args[0].get_string(cs)}; p.parse();) {
if (cmp(p, val)) {
if (p.parse()) {
res.set_str(p.get_item());
res.set_string(p.get_item());
}
return;
}
@ -69,43 +72,41 @@ static inline void cs_list_assoc(
}
}
static void cs_loop_list_conc(
cs_state &cs, cs_value &res, cs_ident *id, ostd::string_range list,
cs_bcode *body, bool space
static void loop_list_conc(
state &cs, any_value &res, ident &id, std::string_view list,
bcode_ref &&body, bool space
) {
cs_stacked_value idv{id};
if (!idv.has_alias()) {
return;
}
cs_string r;
alias_local st{cs, id};
any_value idv{};
charbuf r{cs};
int n = 0;
for (util::list_parser p(cs, list); p.parse(); ++n) {
idv.set_str(p.get_item());
idv.push();
for (list_parser p{cs, list}; p.parse(); ++n) {
idv.set_string(p.get_item());
st.set(std::move(idv));
if (n && space) {
r += ' ';
r.push_back(' ');
}
cs_value v;
switch (cs.run_loop(body, v)) {
case CsLoopState::Break:
any_value v{};
switch (body.call_loop(cs, v)) {
case loop_state::BREAK:
goto end;
case CsLoopState::Continue:
case loop_state::CONTINUE:
continue;
default:
break;
}
r += v.get_str();
r.append(v.get_string(cs));
}
end:
res.set_str(std::move(r));
res.set_string(r.str(), cs);
}
int cs_list_includes(
cs_state &cs, ostd::string_range list, ostd::string_range needle
int list_includes(
state &cs, std::string_view list, std::string_view needle
) {
int offset = 0;
for (util::list_parser p(cs, list); p.parse();) {
if (p.get_raw_item() == needle) {
for (list_parser p{cs, list}; p.parse();) {
if (p.raw_item() == needle) {
return offset;
}
++offset;
@ -114,122 +115,124 @@ int cs_list_includes(
}
template<bool PushList, bool Swap, typename F>
static inline void cs_list_merge(
cs_state &cs, cs_value_r args, cs_value &res, F cmp
static inline void list_merge(
state &cs, span_type<any_value> args, any_value &res, F cmp
) {
ostd::string_range list = args[0].get_strr();
ostd::string_range elems = args[1].get_strr();
cs_string buf;
std::string_view list = args[0].get_string(cs);
std::string_view elems = args[1].get_string(cs);
charbuf buf{cs};
if (PushList) {
buf += list;
buf.append(list);
}
if (Swap) {
std::swap(list, elems);
}
for (util::list_parser p(cs, list); p.parse();) {
if (cmp(cs_list_includes(cs, elems, p.get_raw_item()), 0)) {
for (list_parser p{cs, list}; p.parse();) {
if (cmp(list_includes(cs, elems, p.raw_item()), 0)) {
if (!buf.empty()) {
buf += ' ';
buf.push_back(' ');
}
buf += p.get_raw_item(true);
buf.append(p.quoted_item());
}
}
res.set_str(std::move(buf));
res.set_string(buf.str(), cs);
}
static void cs_init_lib_list_sort(cs_state &cs);
static void init_lib_list_sort(state &cs);
void cs_init_lib_list(cs_state &gcs) {
gcs.new_command("listlen", "s", [](auto &cs, auto args, auto &res) {
res.set_int(cs_int(util::list_parser(cs, args[0].get_strr()).count()));
LIBCUBESCRIPT_EXPORT void std_init_list(state &gcs) {
new_cmd_quiet(gcs, "listlen", "s", [](auto &cs, auto args, auto &res) {
res.set_integer(
integer_type(list_parser{cs, args[0].get_string(cs)}.count())
);
});
gcs.new_command("at", "si1V", [](auto &cs, auto args, auto &res) {
new_cmd_quiet(gcs, "at", "si1...", [](auto &cs, auto args, auto &res) {
if (args.empty()) {
return;
}
cs_string str = std::move(args[0].get_str());
util::list_parser p(cs, str);
p.get_raw_item() = str;
if (args.size() <= 1) {
res = args[0];
return;
}
auto str = args[0].get_string(cs);
list_parser p{cs, str};
for (size_t i = 1; i < args.size(); ++i) {
p.get_input() = str;
cs_int pos = args[i].get_int();
p.set_input(str);
integer_type pos = args[i].get_integer();
for (; pos > 0; --pos) {
if (!p.parse()) {
break;
}
}
if (pos > 0 || !p.parse()) {
p.get_raw_item() = p.get_raw_item(true) = ostd::string_range();
p.set_input("");
}
}
res.set_str(p.get_item());
res.set_string(p.get_item());
});
gcs.new_command("sublist", "siiN", [](auto &cs, auto args, auto &res) {
cs_int skip = args[1].get_int(),
count = args[2].get_int(),
numargs = args[3].get_int();
new_cmd_quiet(gcs, "sublist", "sii#", [](auto &cs, auto args, auto &res) {
integer_type skip = args[1].get_integer(),
count = args[2].get_integer(),
numargs = args[3].get_integer();
cs_int offset = std::max(skip, cs_int(0)),
len = (numargs >= 3) ? std::max(count, cs_int(0)) : -1;
integer_type offset = std::max(skip, integer_type(0)),
len = (numargs >= 3) ? std::max(count, integer_type(0)) : -1;
util::list_parser p(cs, args[0].get_strr());
for (cs_int i = 0; i < offset; ++i) {
list_parser p{cs, args[0].get_string(cs)};
for (integer_type i = 0; i < offset; ++i) {
if (!p.parse()) break;
}
if (len < 0) {
if (offset > 0) {
p.skip();
p.skip_until_item();
}
res.set_str(cs_string{p.get_input()});
res.set_string(p.input(), cs);
return;
}
char const *list = p.get_input().data();
p.get_raw_item(true) = ostd::string_range();
char const *list = p.input().data();
if (len > 0 && p.parse()) {
while (--len > 0 && p.parse());
}
ostd::string_range quote = p.get_raw_item(true);
char const *qend = !quote.empty() ? &quote[quote.size()] : list;
res.set_str(cs_string{list, size_t(qend - list)});
});
gcs.new_command("listfind", "rse", [](auto &cs, auto args, auto &res) {
cs_stacked_value idv{args[0].get_ident()};
if (!idv.has_alias()) {
res.set_int(-1);
} else {
res.set_string("", cs);
return;
}
auto quote = p.quoted_item();
auto *qend = &quote[quote.size()];
res.set_string(make_str_view(list, qend), cs);
});
new_cmd_quiet(gcs, "listfind", "vsb", [](auto &cs, auto args, auto &res) {
alias_local st{cs, args[0]};
any_value idv{};
auto body = args[2].get_code();
int n = -1;
for (util::list_parser p(cs, args[1].get_strr()); p.parse();) {
for (list_parser p{cs, args[1].get_string(cs)}; p.parse();) {
++n;
idv.set_str(cs_string{p.get_raw_item()});
idv.push();
if (cs.run_bool(body)) {
res.set_int(cs_int(n));
idv.set_string(p.raw_item(), cs);
st.set(std::move(idv));
if (body.call(cs).get_bool()) {
res.set_integer(integer_type(n));
return;
}
}
res.set_int(-1);
res.set_integer(-1);
});
gcs.new_command("listassoc", "rse", [](auto &cs, auto args, auto &res) {
cs_stacked_value idv{args[0].get_ident()};
if (!idv.has_alias()) {
return;
}
new_cmd_quiet(gcs, "listassoc", "vsb", [](auto &cs, auto args, auto &res) {
alias_local st{cs, args[0]};
any_value idv{};
auto body = args[2].get_code();
int n = -1;
for (util::list_parser p(cs, args[1].get_strr()); p.parse();) {
for (list_parser p{cs, args[1].get_string(cs)}; p.parse();) {
++n;
idv.set_str(cs_string{p.get_raw_item()});
idv.push();
if (cs.run_bool(body)) {
idv.set_string(p.raw_item(), cs);
st.set(std::move(idv));
if (body.call(cs).get_bool()) {
if (p.parse()) {
res.set_str(p.get_item());
res.set_string(p.get_item());
}
break;
}
@ -239,341 +242,330 @@ void cs_init_lib_list(cs_state &gcs) {
}
});
gcs.new_command("listfind=", "i", [](auto &cs, auto args, auto &res) {
cs_list_find<cs_int>(
cs, args, res, [](const util::list_parser &p, cs_int val) {
return cs_parse_int(p.get_raw_item()) == val;
new_cmd_quiet(gcs, "listfind=", "i", [](auto &cs, auto args, auto &res) {
list_find<integer_type>(
cs, args, res, [](list_parser const &p, integer_type val) {
return parse_int(p.raw_item()) == val;
}
);
});
gcs.new_command("listfind=f", "f", [](auto &cs, auto args, auto &res) {
cs_list_find<cs_float>(
cs, args, res, [](const util::list_parser &p, cs_float val) {
return cs_parse_float(p.get_raw_item()) == val;
new_cmd_quiet(gcs, "listfind=f", "f", [](auto &cs, auto args, auto &res) {
list_find<float_type>(
cs, args, res, [](list_parser const &p, float_type val) {
return parse_float(p.raw_item()) == val;
}
);
});
gcs.new_command("listfind=s", "s", [](auto &cs, auto args, auto &res) {
cs_list_find<ostd::string_range>(
cs, args, res, [](const util::list_parser &p, ostd::string_range val) {
return p.get_raw_item() == val;
new_cmd_quiet(gcs, "listfind=s", "s", [](auto &cs, auto args, auto &res) {
list_find<std::string_view>(
cs, args, res, [](list_parser const &p, std::string_view val) {
return p.raw_item() == val;
}
);
});
gcs.new_command("listassoc=", "i", [](auto &cs, auto args, auto &res) {
cs_list_assoc<cs_int>(
cs, args, res, [](const util::list_parser &p, cs_int val) {
return cs_parse_int(p.get_raw_item()) == val;
new_cmd_quiet(gcs, "listassoc=", "i", [](auto &cs, auto args, auto &res) {
list_assoc<integer_type>(
cs, args, res, [](list_parser const &p, integer_type val) {
return parse_int(p.raw_item()) == val;
}
);
});
gcs.new_command("listassoc=f", "f", [](auto &cs, auto args, auto &res) {
cs_list_assoc<cs_float>(
cs, args, res, [](const util::list_parser &p, cs_float val) {
return cs_parse_float(p.get_raw_item()) == val;
new_cmd_quiet(gcs, "listassoc=f", "f", [](auto &cs, auto args, auto &res) {
list_assoc<float_type>(
cs, args, res, [](list_parser const &p, float_type val) {
return parse_float(p.raw_item()) == val;
}
);
});
gcs.new_command("listassoc=s", "s", [](auto &cs, auto args, auto &res) {
cs_list_assoc<ostd::string_range>(
cs, args, res, [](const util::list_parser &p, ostd::string_range val) {
return p.get_raw_item() == val;
new_cmd_quiet(gcs, "listassoc=s", "s", [](auto &cs, auto args, auto &res) {
list_assoc<std::string_view>(
cs, args, res, [](list_parser const &p, std::string_view val) {
return p.raw_item() == val;
}
);
});
gcs.new_command("looplist", "rse", [](auto &cs, auto args, auto &) {
cs_stacked_value idv{args[0].get_ident()};
if (!idv.has_alias()) {
return;
}
new_cmd_quiet(gcs, "looplist", "vsb", [](auto &cs, auto args, auto &) {
alias_local st{cs, args[0]};
any_value idv{};
auto body = args[2].get_code();
int n = 0;
for (util::list_parser p(cs, args[1].get_strr()); p.parse(); ++n) {
idv.set_str(p.get_item());
idv.push();
switch (cs.run_loop(body)) {
case CsLoopState::Break:
goto end;
for (list_parser p{cs, args[1].get_string(cs)}; p.parse(); ++n) {
idv.set_string(p.get_item());
st.set(std::move(idv));
switch (body.call_loop(cs)) {
case loop_state::BREAK:
return;
default: /* continue and normal */
break;
}
}
end:
return;
});
gcs.new_command("looplist2", "rrse", [](auto &cs, auto args, auto &) {
cs_stacked_value idv1{args[0].get_ident()}, idv2{args[1].get_ident()};
if (!idv1.has_alias() || !idv2.has_alias()) {
return;
}
new_cmd_quiet(gcs, "looplist2", "vvsb", [](auto &cs, auto args, auto &) {
alias_local st1{cs, args[0]};
alias_local st2{cs, args[1]};
any_value idv{};
auto body = args[3].get_code();
int n = 0;
for (util::list_parser p(cs, args[2].get_strr()); p.parse(); n += 2) {
idv1.set_str(p.get_item());
for (list_parser p{cs, args[2].get_string(cs)}; p.parse(); n += 2) {
idv.set_string(p.get_item());
st1.set(std::move(idv));
if (p.parse()) {
idv2.set_str(p.get_item());
idv.set_string(p.get_item());
} else {
idv2.set_str("");
idv.set_string("", cs);
}
idv1.push();
idv2.push();
switch (cs.run_loop(body)) {
case CsLoopState::Break:
goto end;
st2.set(std::move(idv));
switch (body.call_loop(cs)) {
case loop_state::BREAK:
return;
default: /* continue and normal */
break;
}
}
end:
return;
});
gcs.new_command("looplist3", "rrrse", [](auto &cs, auto args, auto &) {
cs_stacked_value idv1{args[0].get_ident()};
cs_stacked_value idv2{args[1].get_ident()};
cs_stacked_value idv3{args[2].get_ident()};
if (!idv1.has_alias() || !idv2.has_alias() || !idv3.has_alias()) {
return;
}
new_cmd_quiet(gcs, "looplist3", "vvvsb", [](auto &cs, auto args, auto &) {
alias_local st1{cs, args[0]};
alias_local st2{cs, args[1]};
alias_local st3{cs, args[2]};
any_value idv{};
auto body = args[4].get_code();
int n = 0;
for (util::list_parser p(cs, args[3].get_strr()); p.parse(); n += 3) {
idv1.set_str(p.get_item());
for (list_parser p{cs, args[3].get_string(cs)}; p.parse(); n += 3) {
idv.set_string(p.get_item());
st1.set(std::move(idv));
if (p.parse()) {
idv2.set_str(p.get_item());
idv.set_string(p.get_item());
} else {
idv2.set_str("");
idv.set_string("", cs);
}
st2.set(std::move(idv));
if (p.parse()) {
idv3.set_str(p.get_item());
idv.set_string(p.get_item());
} else {
idv3.set_str("");
idv.set_string("", cs);
}
idv1.push();
idv2.push();
idv3.push();
switch (cs.run_loop(body)) {
case CsLoopState::Break:
goto end;
st3.set(std::move(idv));
switch (body.call_loop(cs)) {
case loop_state::BREAK:
return;
default: /* continue and normal */
break;
}
}
end:
return;
});
gcs.new_command("looplistconcat", "rse", [](auto &cs, auto args, auto &res) {
cs_loop_list_conc(
cs, res, args[0].get_ident(), args[1].get_strr(),
new_cmd_quiet(gcs, "looplistconcat", "vsb", [](
auto &cs, auto args, auto &res
) {
loop_list_conc(
cs, res, args[0].get_ident(cs), args[1].get_string(cs),
args[2].get_code(), true
);
});
gcs.new_command("looplistconcatword", "rse", [](
new_cmd_quiet(gcs, "looplistconcatword", "vsb", [](
auto &cs, auto args, auto &res
) {
cs_loop_list_conc(
cs, res, args[0].get_ident(), args[1].get_strr(),
loop_list_conc(
cs, res, args[0].get_ident(cs), args[1].get_string(cs),
args[2].get_code(), false
);
});
gcs.new_command("listfilter", "rse", [](auto &cs, auto args, auto &res) {
cs_stacked_value idv{args[0].get_ident()};
if (!idv.has_alias()) {
return;
}
new_cmd_quiet(gcs, "listfilter", "vsb", [](
auto &cs, auto args, auto &res
) {
alias_local st{cs, args[0]};
any_value idv{};
auto body = args[2].get_code();
cs_string r;
charbuf r{cs};
int n = 0;
for (util::list_parser p(cs, args[1].get_strr()); p.parse(); ++n) {
idv.set_str(cs_string{p.get_raw_item()});
idv.push();
if (cs.run_bool(body)) {
for (list_parser p{cs, args[1].get_string(cs)}; p.parse(); ++n) {
idv.set_string(p.raw_item(), cs);
st.set(std::move(idv));
if (body.call(cs).get_bool()) {
if (r.size()) {
r += ' ';
r.push_back(' ');
}
r += p.get_raw_item(true);
r.append(p.quoted_item());
}
}
res.set_str(std::move(r));
res.set_string(r.str(), cs);
});
gcs.new_command("listcount", "rse", [](auto &cs, auto args, auto &res) {
cs_stacked_value idv{args[0].get_ident()};
if (!idv.has_alias()) {
return;
}
new_cmd_quiet(gcs, "listcount", "vsb", [](auto &cs, auto args, auto &res) {
alias_local st{cs, args[0]};
any_value idv{};
auto body = args[2].get_code();
int n = 0, r = 0;
for (util::list_parser p(cs, args[1].get_strr()); p.parse(); ++n) {
idv.set_str(cs_string{p.get_raw_item()});
idv.push();
if (cs.run_bool(body)) {
for (list_parser p{cs, args[1].get_string(cs)}; p.parse(); ++n) {
idv.set_string(p.raw_item(), cs);
st.set(std::move(idv));
if (body.call(cs).get_bool()) {
r++;
}
}
res.set_int(r);
res.set_integer(r);
});
gcs.new_command("prettylist", "ss", [](auto &cs, auto args, auto &res) {
auto buf = ostd::appender<cs_string>();
ostd::string_range s = args[0].get_strr();
ostd::string_range conj = args[1].get_strr();
size_t len = util::list_parser(cs, s).count();
new_cmd_quiet(gcs, "prettylist", "ss", [](auto &cs, auto args, auto &res) {
charbuf buf{cs};
std::string_view s = args[0].get_string(cs);
std::string_view conj = args[1].get_string(cs);
list_parser p{cs, s};
size_t len = p.count();
size_t n = 0;
for (util::list_parser p(cs, s); p.parse(); ++n) {
if (!p.get_raw_item(true).empty() &&
(p.get_raw_item(true).front() == '"')) {
util::unescape_string(buf, p.get_raw_item());
for (p.set_input(s); p.parse(); ++n) {
auto qi = p.quoted_item();
if (!qi.empty() && (qi.front() == '"')) {
unescape_string(std::back_inserter(buf), p.raw_item());
} else {
ostd::range_put_all(buf, p.get_raw_item());
buf.append(p.raw_item());
}
if ((n + 1) < len) {
if ((len > 2) || conj.empty()) {
buf.put(',');
buf.push_back(',');
}
if ((n + 2 == len) && !conj.empty()) {
buf.put(' ');
ostd::range_put_all(buf, conj);
buf.push_back(' ');
buf.append(conj);
}
buf.put(' ');
buf.push_back(' ');
}
}
res.set_str(std::move(buf.get()));
res.set_string(buf.str(), cs);
});
gcs.new_command("indexof", "ss", [](auto &cs, auto args, auto &res) {
res.set_int(
cs_list_includes(cs, args[0].get_strr(), args[1].get_strr())
new_cmd_quiet(gcs, "indexof", "ss", [](auto &cs, auto args, auto &res) {
res.set_integer(
list_includes(cs, args[0].get_string(cs), args[1].get_string(cs))
);
});
gcs.new_command("listdel", "ss", [](auto &cs, auto args, auto &res) {
cs_list_merge<false, false>(cs, args, res, std::less<int>());
new_cmd_quiet(gcs, "listdel", "ss", [](auto &cs, auto args, auto &res) {
list_merge<false, false>(cs, args, res, std::less<int>());
});
gcs.new_command("listintersect", "ss", [](auto &cs, auto args, auto &res) {
cs_list_merge<false, false>(cs, args, res, std::greater_equal<int>());
new_cmd_quiet(gcs, "listintersect", "ss", [](
auto &cs, auto args, auto &res
) {
list_merge<false, false>(cs, args, res, std::greater_equal<int>());
});
gcs.new_command("listunion", "ss", [](auto &cs, auto args, auto &res) {
cs_list_merge<true, true>(cs, args, res, std::less<int>());
new_cmd_quiet(gcs, "listunion", "ss", [](auto &cs, auto args, auto &res) {
list_merge<true, true>(cs, args, res, std::less<int>());
});
gcs.new_command("listsplice", "ssii", [](auto &cs, auto args, auto &res) {
cs_int offset = std::max(args[2].get_int(), cs_int(0));
cs_int len = std::max(args[3].get_int(), cs_int(0));
ostd::string_range s = args[0].get_strr();
ostd::string_range vals = args[1].get_strr();
new_cmd_quiet(gcs, "listsplice", "ssii", [](
auto &cs, auto args, auto &res
) {
integer_type offset = std::max(args[2].get_integer(), integer_type(0));
integer_type len = std::max(args[3].get_integer(), integer_type(0));
std::string_view s = args[0].get_string(cs);
std::string_view vals = args[1].get_string(cs);
char const *list = s.data();
util::list_parser p(cs, s);
for (cs_int i = 0; i < offset; ++i) {
list_parser p{cs, s};
for (integer_type i = 0; i < offset; ++i) {
if (!p.parse()) {
break;
}
}
ostd::string_range quote = p.get_raw_item(true);
std::string_view quote = p.quoted_item();
char const *qend = !quote.empty() ? &quote[quote.size()] : list;
cs_string buf;
charbuf buf{cs};
if (qend > list) {
buf += ostd::string_range(list, qend);
buf.append(list, qend);
}
if (!vals.empty()) {
if (!buf.empty()) {
buf += ' ';
buf.push_back(' ');
}
buf += vals;
buf.append(vals);
}
for (cs_int i = 0; i < len; ++i) {
for (integer_type i = 0; i < len; ++i) {
if (!p.parse()) {
break;
}
}
p.skip();
if (!p.get_input().empty()) {
switch (p.get_input().front()) {
p.skip_until_item();
if (!p.input().empty()) {
switch (p.input().front()) {
case ')':
case ']':
break;
default:
if (!buf.empty()) {
buf += ' ';
buf.push_back(' ');
}
buf += p.get_input();
buf.append(p.input());
break;
}
}
res.set_str(std::move(buf));
res.set_string(buf.str(), cs);
});
cs_init_lib_list_sort(gcs);
init_lib_list_sort(gcs);
}
struct ListSortItem {
ostd::string_range str;
ostd::string_range quote;
std::string_view str;
std::string_view quote;
};
struct ListSortFun {
cs_state &cs;
cs_stacked_value &xv, &yv;
cs_bcode *body;
state &cs;
alias_local &xst, &yst;
bcode_ref const *body;
bool operator()(ListSortItem const &xval, ListSortItem const &yval) {
xv.set_cstr(xval.str);
yv.set_cstr(yval.str);
xv.push();
yv.push();
return cs.run_bool(body);
any_value v{};
v.set_string(xval.str, cs);
xst.set(std::move(v));
v.set_string(yval.str, cs);
yst.set(std::move(v));
return body->call(cs).get_bool();
}
};
static void cs_list_sort(
cs_state &cs, cs_value &res, ostd::string_range list,
cs_ident *x, cs_ident *y, cs_bcode *body, cs_bcode *unique
static void list_sort(
state &cs, any_value &res, std::string_view list,
ident &x, ident &y, bcode_ref &&body, bcode_ref &&unique
) {
if (x == y || !x->is_alias() || !y->is_alias()) {
if (x == y) {
return;
}
cs_alias *xa = static_cast<cs_alias *>(x), *ya = static_cast<cs_alias *>(y);
alias_local xst{cs, x}, yst{cs, y};
cs_vector<ListSortItem> items;
valbuf<ListSortItem> items{state_p{cs}.ts().istate};
size_t total = 0;
for (util::list_parser p(cs, list); p.parse();) {
ListSortItem item = { p.get_raw_item(), p.get_raw_item(true) };
for (list_parser p{cs, list}; p.parse();) {
ListSortItem item = { p.raw_item(), p.quoted_item() };
items.push_back(item);
total += item.quote.size();
}
if (items.empty()) {
res.set_str(cs_string{list});
res.set_string(list, cs);
return;
}
cs_stacked_value xval{xa}, yval{ya};
xval.set_null();
yval.set_null();
xval.push();
yval.push();
size_t totaluniq = total;
size_t nuniq = items.size();
if (body) {
ListSortFun f = { cs, xval, yval, body };
ostd::sort_cmp(ostd::iter(items), f);
if (!cs_code_is_empty(unique)) {
f.body = unique;
ListSortFun f = { cs, xst, yst, &body };
std::sort(items.buf.begin(), items.buf.end(), f);
if (!unique.empty()) {
f.body = &unique;
totaluniq = items[0].quote.size();
nuniq = 1;
for (size_t i = 1; i < items.size(); i++) {
ListSortItem &item = items[i];
if (f(items[i - 1], item)) {
item.quote = nullptr;
item.quote = std::string_view{};
} else {
totaluniq += item.quote.size();
++nuniq;
@ -581,7 +573,7 @@ static void cs_list_sort(
}
}
} else {
ListSortFun f = { cs, xval, yval, unique };
ListSortFun f = { cs, xst, yst, &unique };
totaluniq = items[0].quote.size();
nuniq = 1;
for (size_t i = 1; i < items.size(); i++) {
@ -589,7 +581,7 @@ static void cs_list_sort(
for (size_t j = 0; j < i; ++j) {
ListSortItem &prev = items[j];
if (!prev.quote.empty() && f(item, prev)) {
item.quote = nullptr;
item.quote = std::string_view{};
break;
}
}
@ -600,10 +592,7 @@ static void cs_list_sort(
}
}
xval.pop();
yval.pop();
cs_string sorted;
charbuf sorted{cs};
sorted.reserve(totaluniq + std::max(nuniq - 1, size_t(0)));
for (size_t i = 0; i < items.size(); ++i) {
ListSortItem &item = items[i];
@ -611,26 +600,30 @@ static void cs_list_sort(
continue;
}
if (i) {
sorted += ' ';
sorted.push_back(' ');
}
sorted += item.quote;
sorted.append(item.quote);
}
res.set_str(std::move(sorted));
res.set_string(sorted.str(), cs);
}
static void cs_init_lib_list_sort(cs_state &gcs) {
gcs.new_command("sortlist", "srree", [](auto &cs, auto args, auto &res) {
cs_list_sort(
cs, res, args[0].get_strr(), args[1].get_ident(),
args[2].get_ident(), args[3].get_code(), args[4].get_code()
static void init_lib_list_sort(state &gcs) {
new_cmd_quiet(gcs, "sortlist", "svvbb", [](
auto &cs, auto args, auto &res
) {
list_sort(
cs, res, args[0].get_string(cs), args[1].get_ident(cs),
args[2].get_ident(cs), args[3].get_code(), args[4].get_code()
);
});
gcs.new_command("uniquelist", "srre", [](auto &cs, auto args, auto &res) {
cs_list_sort(
cs, res, args[0].get_strr(), args[1].get_ident(),
args[2].get_ident(), nullptr, args[3].get_code()
new_cmd_quiet(gcs, "uniquelist", "svvb", [](
auto &cs, auto args, auto &res
) {
list_sort(
cs, res, args[0].get_string(cs), args[1].get_ident(cs),
args[2].get_ident(cs), bcode_ref{}, args[3].get_code()
);
});
}
} /* namespace cscript */
} /* namespace cubescript */

View File

@ -6,362 +6,365 @@
#include <cubescript/cubescript.hh>
namespace cscript {
#include "cs_state.hh"
static constexpr cs_float PI = 3.14159265358979f;
static constexpr cs_float RAD = PI / 180.0f;
namespace cubescript {
static constexpr float_type PI = float_type(3.14159265358979323846);
static constexpr float_type LN2 = float_type(0.693147180559945309417);
static constexpr float_type RAD = PI / float_type(180.0);
template<typename T>
struct cs_math_val;
struct math_val;
template<>
struct cs_math_val<cs_int> {
static cs_int get(cs_value &tv) {
return tv.get_int();
struct math_val<integer_type> {
static integer_type get(any_value &tv) {
return tv.get_integer();
}
static void set(cs_value &res, cs_int val) {
res.set_int(val);
static void set(any_value &res, integer_type val) {
res.set_integer(val);
}
};
template<>
struct cs_math_val<cs_float> {
static cs_float get(cs_value &tv) {
struct math_val<float_type> {
static float_type get(any_value &tv) {
return tv.get_float();
}
static void set(cs_value &res, cs_float val) {
static void set(any_value &res, float_type val) {
res.set_float(val);
}
};
template<typename T>
struct cs_math_noop {
struct math_noop {
T operator()(T arg) {
return arg;
}
};
template<typename T, typename F1, typename F2>
static inline void cs_mathop(
cs_value_r args, cs_value &res, T initval,
static inline void math_op(
span_type<any_value> args, any_value &res, T initval,
F1 binop, F2 unop
) {
T val;
if (args.size() >= 2) {
val = binop(cs_math_val<T>::get(args[0]), cs_math_val<T>::get(args[1]));
val = binop(math_val<T>::get(args[0]), math_val<T>::get(args[1]));
for (size_t i = 2; i < args.size(); ++i) {
val = binop(val, cs_math_val<T>::get(args[i]));
val = binop(val, math_val<T>::get(args[i]));
}
} else {
val = unop(!args.empty() ? cs_math_val<T>::get(args[0]) : initval);
val = unop(!args.empty() ? math_val<T>::get(args[0]) : initval);
}
cs_math_val<T>::set(res, val);
math_val<T>::set(res, val);
}
template<typename T, typename F>
static inline void cs_cmpop(cs_value_r args, cs_value &res, F cmp) {
static inline void cmp_op(span_type<any_value> args, any_value &res, F cmp) {
bool val;
if (args.size() >= 2) {
val = cmp(cs_math_val<T>::get(args[0]), cs_math_val<T>::get(args[1]));
val = cmp(math_val<T>::get(args[0]), math_val<T>::get(args[1]));
for (size_t i = 2; (i < args.size()) && val; ++i) {
val = cmp(cs_math_val<T>::get(args[i - 1]), cs_math_val<T>::get(args[i]));
val = cmp(math_val<T>::get(args[i - 1]), math_val<T>::get(args[i]));
}
} else {
val = cmp(!args.empty() ? cs_math_val<T>::get(args[0]) : T(0), T(0));
val = cmp(!args.empty() ? math_val<T>::get(args[0]) : T(0), T(0));
}
res.set_int(cs_int(val));
res.set_integer(integer_type(val));
}
void cs_init_lib_math(cs_state &cs) {
cs.new_command("sin", "f", [](auto &, auto args, auto &res) {
LIBCUBESCRIPT_EXPORT void std_init_math(state &cs) {
new_cmd_quiet(cs, "sin", "f", [](auto &, auto args, auto &res) {
res.set_float(std::sin(args[0].get_float() * RAD));
});
cs.new_command("cos", "f", [](auto &, auto args, auto &res) {
new_cmd_quiet(cs, "cos", "f", [](auto &, auto args, auto &res) {
res.set_float(std::cos(args[0].get_float() * RAD));
});
cs.new_command("tan", "f", [](auto &, auto args, auto &res) {
new_cmd_quiet(cs, "tan", "f", [](auto &, auto args, auto &res) {
res.set_float(std::tan(args[0].get_float() * RAD));
});
cs.new_command("asin", "f", [](auto &, auto args, auto &res) {
new_cmd_quiet(cs, "asin", "f", [](auto &, auto args, auto &res) {
res.set_float(std::asin(args[0].get_float()) / RAD);
});
cs.new_command("acos", "f", [](auto &, auto args, auto &res) {
new_cmd_quiet(cs, "acos", "f", [](auto &, auto args, auto &res) {
res.set_float(std::acos(args[0].get_float()) / RAD);
});
cs.new_command("atan", "f", [](auto &, auto args, auto &res) {
new_cmd_quiet(cs, "atan", "f", [](auto &, auto args, auto &res) {
res.set_float(std::atan(args[0].get_float()) / RAD);
});
cs.new_command("atan2", "ff", [](auto &, auto args, auto &res) {
new_cmd_quiet(cs, "atan2", "ff", [](auto &, auto args, auto &res) {
res.set_float(std::atan2(args[0].get_float(), args[1].get_float()) / RAD);
});
cs.new_command("sqrt", "f", [](auto &, auto args, auto &res) {
new_cmd_quiet(cs, "sqrt", "f", [](auto &, auto args, auto &res) {
res.set_float(std::sqrt(args[0].get_float()));
});
cs.new_command("loge", "f", [](auto &, auto args, auto &res) {
new_cmd_quiet(cs, "loge", "f", [](auto &, auto args, auto &res) {
res.set_float(std::log(args[0].get_float()));
});
cs.new_command("log2", "f", [](auto &, auto args, auto &res) {
res.set_float(std::log(args[0].get_float()) / M_LN2);
new_cmd_quiet(cs, "log2", "f", [](auto &, auto args, auto &res) {
res.set_float(std::log(args[0].get_float()) / LN2);
});
cs.new_command("log10", "f", [](auto &, auto args, auto &res) {
new_cmd_quiet(cs, "log10", "f", [](auto &, auto args, auto &res) {
res.set_float(std::log10(args[0].get_float()));
});
cs.new_command("exp", "f", [](auto &, auto args, auto &res) {
new_cmd_quiet(cs, "exp", "f", [](auto &, auto args, auto &res) {
res.set_float(std::exp(args[0].get_float()));
});
cs.new_command("min", "i1V", [](auto &, auto args, auto &res) {
cs_int v = (!args.empty() ? args[0].get_int() : 0);
new_cmd_quiet(cs, "min", "i1...", [](auto &, auto args, auto &res) {
integer_type v = (!args.empty() ? args[0].get_integer() : 0);
for (size_t i = 1; i < args.size(); ++i) {
v = std::min(v, args[i].get_int());
v = std::min(v, args[i].get_integer());
}
res.set_int(v);
res.set_integer(v);
});
cs.new_command("max", "i1V", [](auto &, auto args, auto &res) {
cs_int v = (!args.empty() ? args[0].get_int() : 0);
new_cmd_quiet(cs, "max", "i1...", [](auto &, auto args, auto &res) {
integer_type v = (!args.empty() ? args[0].get_integer() : 0);
for (size_t i = 1; i < args.size(); ++i) {
v = std::max(v, args[i].get_int());
v = std::max(v, args[i].get_integer());
}
res.set_int(v);
res.set_integer(v);
});
cs.new_command("minf", "f1V", [](auto &, auto args, auto &res) {
cs_float v = (!args.empty() ? args[0].get_float() : 0);
new_cmd_quiet(cs, "minf", "f1...", [](auto &, auto args, auto &res) {
float_type v = (!args.empty() ? args[0].get_float() : 0);
for (size_t i = 1; i < args.size(); ++i) {
v = std::min(v, args[i].get_float());
}
res.set_float(v);
});
cs.new_command("maxf", "f1V", [](auto &, auto args, auto &res) {
cs_float v = (!args.empty() ? args[0].get_float() : 0);
new_cmd_quiet(cs, "maxf", "f1...", [](auto &, auto args, auto &res) {
float_type v = (!args.empty() ? args[0].get_float() : 0);
for (size_t i = 1; i < args.size(); ++i) {
v = std::max(v, args[i].get_float());
}
res.set_float(v);
});
cs.new_command("abs", "i", [](auto &, auto args, auto &res) {
res.set_int(std::abs(args[0].get_int()));
new_cmd_quiet(cs, "abs", "i", [](auto &, auto args, auto &res) {
res.set_integer(std::abs(args[0].get_integer()));
});
cs.new_command("absf", "f", [](auto &, auto args, auto &res) {
new_cmd_quiet(cs, "absf", "f", [](auto &, auto args, auto &res) {
res.set_float(std::abs(args[0].get_float()));
});
cs.new_command("floor", "f", [](auto &, auto args, auto &res) {
new_cmd_quiet(cs, "floor", "f", [](auto &, auto args, auto &res) {
res.set_float(std::floor(args[0].get_float()));
});
cs.new_command("ceil", "f", [](auto &, auto args, auto &res) {
new_cmd_quiet(cs, "ceil", "f", [](auto &, auto args, auto &res) {
res.set_float(std::ceil(args[0].get_float()));
});
cs.new_command("round", "ff", [](auto &, auto args, auto &res) {
cs_float step = args[1].get_float();
cs_float r = args[0].get_float();
new_cmd_quiet(cs, "round", "ff", [](auto &, auto args, auto &res) {
float_type step = args[1].get_float();
float_type r = args[0].get_float();
if (step > 0) {
r += step * ((r < 0) ? -0.5 : 0.5);
r -= std::fmod(r, step);
r += float_type(step * ((r < 0) ? -0.5 : 0.5));
r -= float_type(std::fmod(r, step));
} else {
r = (r < 0) ? std::ceil(r - 0.5) : std::floor(r + 0.5);
r = float_type((r < 0) ? std::ceil(r - 0.5) : std::floor(r + 0.5));
}
res.set_float(r);
});
cs.new_command("+", "i1V", [](auto &, auto args, auto &res) {
cs_mathop<cs_int>(args, res, 0, std::plus<cs_int>(), cs_math_noop<cs_int>());
new_cmd_quiet(cs, "+", "i1...", [](auto &, auto args, auto &res) {
math_op<integer_type>(args, res, 0, std::plus<integer_type>(), math_noop<integer_type>());
});
cs.new_command("*", "i1V", [](auto &, auto args, auto &res) {
cs_mathop<cs_int>(
args, res, 1, std::multiplies<cs_int>(), cs_math_noop<cs_int>()
new_cmd_quiet(cs, "*", "i1...", [](auto &, auto args, auto &res) {
math_op<integer_type>(
args, res, 1, std::multiplies<integer_type>(), math_noop<integer_type>()
);
});
cs.new_command("-", "i1V", [](auto &, auto args, auto &res) {
cs_mathop<cs_int>(
args, res, 0, std::minus<cs_int>(), std::negate<cs_int>()
new_cmd_quiet(cs, "-", "i1...", [](auto &, auto args, auto &res) {
math_op<integer_type>(
args, res, 0, std::minus<integer_type>(), std::negate<integer_type>()
);
});
cs.new_command("^", "i1V", [](auto &, auto args, auto &res) {
cs_mathop<cs_int>(
args, res, 0, std::bit_xor<cs_int>(), [](cs_int val) { return ~val; }
new_cmd_quiet(cs, "^", "i1...", [](auto &, auto args, auto &res) {
math_op<integer_type>(
args, res, 0, std::bit_xor<integer_type>(), [](integer_type val) { return ~val; }
);
});
cs.new_command("~", "i1V", [](auto &, auto args, auto &res) {
cs_mathop<cs_int>(
args, res, 0, std::bit_xor<cs_int>(), [](cs_int val) { return ~val; }
new_cmd_quiet(cs, "~", "i1...", [](auto &, auto args, auto &res) {
math_op<integer_type>(
args, res, 0, std::bit_xor<integer_type>(), [](integer_type val) { return ~val; }
);
});
cs.new_command("&", "i1V", [](auto &, auto args, auto &res) {
cs_mathop<cs_int>(
args, res, 0, std::bit_and<cs_int>(), cs_math_noop<cs_int>()
new_cmd_quiet(cs, "&", "i1...", [](auto &, auto args, auto &res) {
math_op<integer_type>(
args, res, 0, std::bit_and<integer_type>(), math_noop<integer_type>()
);
});
cs.new_command("|", "i1V", [](auto &, auto args, auto &res) {
cs_mathop<cs_int>(
args, res, 0, std::bit_or<cs_int>(), cs_math_noop<cs_int>()
new_cmd_quiet(cs, "|", "i1...", [](auto &, auto args, auto &res) {
math_op<integer_type>(
args, res, 0, std::bit_or<integer_type>(), math_noop<integer_type>()
);
});
/* special combined cases */
cs.new_command("^~", "i1V", [](auto &, auto args, auto &res) {
cs_int val;
new_cmd_quiet(cs, "^~", "i1...", [](auto &, auto args, auto &res) {
integer_type val;
if (args.size() >= 2) {
val = args[0].get_int() ^ ~args[1].get_int();
val = args[0].get_integer() ^ ~args[1].get_integer();
for (size_t i = 2; i < args.size(); ++i) {
val ^= ~args[i].get_int();
val ^= ~args[i].get_integer();
}
} else {
val = !args.empty() ? args[0].get_int() : 0;
val = !args.empty() ? args[0].get_integer() : 0;
}
res.set_int(val);
res.set_integer(val);
});
cs.new_command("&~", "i1V", [](auto &, auto args, auto &res) {
cs_int val;
new_cmd_quiet(cs, "&~", "i1...", [](auto &, auto args, auto &res) {
integer_type val;
if (args.size() >= 2) {
val = args[0].get_int() & ~args[1].get_int();
val = args[0].get_integer() & ~args[1].get_integer();
for (size_t i = 2; i < args.size(); ++i) {
val &= ~args[i].get_int();
val &= ~args[i].get_integer();
}
} else {
val = !args.empty() ? args[0].get_int() : 0;
val = !args.empty() ? args[0].get_integer() : 0;
}
res.set_int(val);
res.set_integer(val);
});
cs.new_command("|~", "i1V", [](auto &, auto args, auto &res) {
cs_int val;
new_cmd_quiet(cs, "|~", "i1...", [](auto &, auto args, auto &res) {
integer_type val;
if (args.size() >= 2) {
val = args[0].get_int() | ~args[1].get_int();
val = args[0].get_integer() | ~args[1].get_integer();
for (size_t i = 2; i < args.size(); ++i) {
val |= ~args[i].get_int();
val |= ~args[i].get_integer();
}
} else {
val = !args.empty() ? args[0].get_int() : 0;
val = !args.empty() ? args[0].get_integer() : 0;
}
res.set_int(val);
res.set_integer(val);
});
cs.new_command("<<", "i1V", [](auto &, auto args, auto &res) {
cs_mathop<cs_int>(
args, res, 0, [](cs_int val1, cs_int val2) {
return (val2 < cs_int(sizeof(cs_int) * CHAR_BIT))
? (val1 << std::max(val2, cs_int(0)))
new_cmd_quiet(cs, "<<", "i1...", [](auto &, auto args, auto &res) {
math_op<integer_type>(
args, res, 0, [](integer_type val1, integer_type val2) {
return (val2 < integer_type(sizeof(integer_type) * CHAR_BIT))
? (val1 << std::max(val2, integer_type(0)))
: 0;
}, cs_math_noop<cs_int>()
}, math_noop<integer_type>()
);
});
cs.new_command(">>", "i1V", [](auto &, auto args, auto &res) {
cs_mathop<cs_int>(
args, res, 0, [](cs_int val1, cs_int val2) {
new_cmd_quiet(cs, ">>", "i1...", [](auto &, auto args, auto &res) {
math_op<integer_type>(
args, res, 0, [](integer_type val1, integer_type val2) {
return val1 >> std::clamp(
val2, cs_int(0), cs_int(sizeof(cs_int) * CHAR_BIT)
val2, integer_type(0), integer_type(sizeof(integer_type) * CHAR_BIT)
);
}, cs_math_noop<cs_int>()
}, math_noop<integer_type>()
);
});
cs.new_command("+f", "f1V", [](auto &, auto args, auto &res) {
cs_mathop<cs_float>(
args, res, 0, std::plus<cs_float>(), cs_math_noop<cs_float>()
new_cmd_quiet(cs, "+f", "f1...", [](auto &, auto args, auto &res) {
math_op<float_type>(
args, res, 0, std::plus<float_type>(), math_noop<float_type>()
);
});
cs.new_command("*f", "f1V", [](auto &, auto args, auto &res) {
cs_mathop<cs_float>(
args, res, 1, std::multiplies<cs_float>(), cs_math_noop<cs_float>()
new_cmd_quiet(cs, "*f", "f1...", [](auto &, auto args, auto &res) {
math_op<float_type>(
args, res, 1, std::multiplies<float_type>(), math_noop<float_type>()
);
});
cs.new_command("-f", "f1V", [](auto &, auto args, auto &res) {
cs_mathop<cs_float>(
args, res, 0, std::minus<cs_float>(), std::negate<cs_float>()
new_cmd_quiet(cs, "-f", "f1...", [](auto &, auto args, auto &res) {
math_op<float_type>(
args, res, 0, std::minus<float_type>(), std::negate<float_type>()
);
});
cs.new_command("div", "i1V", [](auto &, auto args, auto &res) {
cs_mathop<cs_int>(
args, res, 0, [](cs_int val1, cs_int val2) {
new_cmd_quiet(cs, "div", "i1...", [](auto &, auto args, auto &res) {
math_op<integer_type>(
args, res, 0, [](integer_type val1, integer_type val2) {
if (val2) {
return val1 / val2;
}
return cs_int(0);
}, cs_math_noop<cs_int>()
return integer_type(0);
}, math_noop<integer_type>()
);
});
cs.new_command("mod", "i1V", [](auto &, auto args, auto &res) {
cs_mathop<cs_int>(
args, res, 0, [](cs_int val1, cs_int val2) {
new_cmd_quiet(cs, "mod", "i1...", [](auto &, auto args, auto &res) {
math_op<integer_type>(
args, res, 0, [](integer_type val1, integer_type val2) {
if (val2) {
return val1 % val2;
}
return cs_int(0);
}, cs_math_noop<cs_int>()
return integer_type(0);
}, math_noop<integer_type>()
);
});
cs.new_command("divf", "f1V", [](auto &, auto args, auto &res) {
cs_mathop<cs_float>(
args, res, 0, [](cs_float val1, cs_float val2) {
new_cmd_quiet(cs, "divf", "f1...", [](auto &, auto args, auto &res) {
math_op<float_type>(
args, res, 0, [](float_type val1, float_type val2) {
if (val2) {
return val1 / val2;
}
return cs_float(0);
}, cs_math_noop<cs_float>()
return float_type(0);
}, math_noop<float_type>()
);
});
cs.new_command("modf", "f1V", [](auto &, auto args, auto &res) {
cs_mathop<cs_float>(
args, res, 0, [](cs_float val1, cs_float val2) {
new_cmd_quiet(cs, "modf", "f1...", [](auto &, auto args, auto &res) {
math_op<float_type>(
args, res, 0, [](float_type val1, float_type val2) {
if (val2) {
return cs_float(fmod(val1, val2));
return float_type(fmod(val1, val2));
}
return cs_float(0);
}, cs_math_noop<cs_float>()
return float_type(0);
}, math_noop<float_type>()
);
});
cs.new_command("pow", "f1V", [](auto &, auto args, auto &res) {
cs_mathop<cs_float>(
args, res, 0, [](cs_float val1, cs_float val2) {
return cs_float(pow(val1, val2));
}, cs_math_noop<cs_float>()
new_cmd_quiet(cs, "pow", "f1...", [](auto &, auto args, auto &res) {
math_op<float_type>(
args, res, 0, [](float_type val1, float_type val2) {
return float_type(pow(val1, val2));
}, math_noop<float_type>()
);
});
cs.new_command("=", "i1V", [](auto &, auto args, auto &res) {
cs_cmpop<cs_int>(args, res, std::equal_to<cs_int>());
new_cmd_quiet(cs, "=", "i1...", [](auto &, auto args, auto &res) {
cmp_op<integer_type>(args, res, std::equal_to<integer_type>());
});
cs.new_command("!=", "i1V", [](auto &, auto args, auto &res) {
cs_cmpop<cs_int>(args, res, std::not_equal_to<cs_int>());
new_cmd_quiet(cs, "!=", "i1...", [](auto &, auto args, auto &res) {
cmp_op<integer_type>(args, res, std::not_equal_to<integer_type>());
});
cs.new_command("<", "i1V", [](auto &, auto args, auto &res) {
cs_cmpop<cs_int>(args, res, std::less<cs_int>());
new_cmd_quiet(cs, "<", "i1...", [](auto &, auto args, auto &res) {
cmp_op<integer_type>(args, res, std::less<integer_type>());
});
cs.new_command(">", "i1V", [](auto &, auto args, auto &res) {
cs_cmpop<cs_int>(args, res, std::greater<cs_int>());
new_cmd_quiet(cs, ">", "i1...", [](auto &, auto args, auto &res) {
cmp_op<integer_type>(args, res, std::greater<integer_type>());
});
cs.new_command("<=", "i1V", [](auto &, auto args, auto &res) {
cs_cmpop<cs_int>(args, res, std::less_equal<cs_int>());
new_cmd_quiet(cs, "<=", "i1...", [](auto &, auto args, auto &res) {
cmp_op<integer_type>(args, res, std::less_equal<integer_type>());
});
cs.new_command(">=", "i1V", [](auto &, auto args, auto &res) {
cs_cmpop<cs_int>(args, res, std::greater_equal<cs_int>());
new_cmd_quiet(cs, ">=", "i1...", [](auto &, auto args, auto &res) {
cmp_op<integer_type>(args, res, std::greater_equal<integer_type>());
});
cs.new_command("=f", "f1V", [](auto &, auto args, auto &res) {
cs_cmpop<cs_float>(args, res, std::equal_to<cs_float>());
new_cmd_quiet(cs, "=f", "f1...", [](auto &, auto args, auto &res) {
cmp_op<float_type>(args, res, std::equal_to<float_type>());
});
cs.new_command("!=f", "f1V", [](auto &, auto args, auto &res) {
cs_cmpop<cs_float>(args, res, std::not_equal_to<cs_float>());
new_cmd_quiet(cs, "!=f", "f1...", [](auto &, auto args, auto &res) {
cmp_op<float_type>(args, res, std::not_equal_to<float_type>());
});
cs.new_command("<f", "f1V", [](auto &, auto args, auto &res) {
cs_cmpop<cs_float>(args, res, std::less<cs_float>());
new_cmd_quiet(cs, "<f", "f1...", [](auto &, auto args, auto &res) {
cmp_op<float_type>(args, res, std::less<float_type>());
});
cs.new_command(">f", "f1V", [](auto &, auto args, auto &res) {
cs_cmpop<cs_float>(args, res, std::greater<cs_float>());
new_cmd_quiet(cs, ">f", "f1...", [](auto &, auto args, auto &res) {
cmp_op<float_type>(args, res, std::greater<float_type>());
});
cs.new_command("<=f", "f1V", [](auto &, auto args, auto &res) {
cs_cmpop<cs_float>(args, res, std::less_equal<cs_float>());
new_cmd_quiet(cs, "<=f", "f1...", [](auto &, auto args, auto &res) {
cmp_op<float_type>(args, res, std::less_equal<float_type>());
});
cs.new_command(">=f", "f1V", [](auto &, auto args, auto &res) {
cs_cmpop<cs_float>(args, res, std::greater_equal<cs_float>());
new_cmd_quiet(cs, ">=f", "f1...", [](auto &, auto args, auto &res) {
cmp_op<float_type>(args, res, std::greater_equal<float_type>());
});
}
} /* namespace cscript */
} /* namespace cubescript */

View File

@ -1,228 +1,245 @@
#include <cstdlib>
#include <functional>
#include <iterator>
#include <cubescript/cubescript.hh>
namespace cscript {
#include "cs_std.hh"
#include "cs_strman.hh"
#include "cs_thread.hh"
namespace cubescript {
template<typename F>
static inline void cs_strgcmp(cs_value_r args, cs_value &res, F cfunc) {
static inline void str_cmp_by(
state &cs, span_type<any_value> args, any_value &res, F cfunc
) {
bool val;
if (args.size() >= 2) {
val = cfunc(args[0].get_strr(), args[1].get_strr());
val = cfunc(args[0].get_string(cs), args[1].get_string(cs));
for (size_t i = 2; (i < args.size()) && val; ++i) {
val = cfunc(args[i - 1].get_strr(), args[i].get_strr());
val = cfunc(args[i - 1].get_string(cs), args[i].get_string(cs));
}
} else {
val = cfunc(
!args.empty() ? args[0].get_strr() : ostd::string_range(),
ostd::string_range()
!args.empty() ? args[0].get_string(cs) : std::string_view(),
std::string_view()
);
}
res.set_int(cs_int(val));
};
res.set_integer(integer_type(val));
}
void cs_init_lib_string(cs_state &cs) {
cs.new_command("strstr", "ss", [](auto &, auto args, auto &res) {
ostd::string_range a = args[0].get_strr(), b = args[1].get_strr();
ostd::string_range s = a;
for (cs_int i = 0; b.size() <= s.size(); ++i) {
if (b == s.slice(0, b.size())) {
res.set_int(i);
return;
}
++s;
}
res.set_int(-1);
});
cs.new_command("strlen", "s", [](auto &, auto args, auto &res) {
res.set_int(cs_int(args[0].get_strr().size()));
});
cs.new_command("strcode", "si", [](auto &, auto args, auto &res) {
ostd::string_range str = args[0].get_strr();
cs_int i = args[1].get_int();
if (i >= cs_int(str.size())) {
res.set_int(0);
LIBCUBESCRIPT_EXPORT void std_init_string(state &cs) {
new_cmd_quiet(cs, "strstr", "ss", [](auto &ccs, auto args, auto &res) {
std::string_view a = args[0].get_string(ccs);
std::string_view b = args[1].get_string(ccs);
auto pos = a.find(b);
if (pos == a.npos) {
res.set_integer(-1);
} else {
res.set_int(static_cast<unsigned char>(str[i]));
res.set_integer(integer_type(pos));
}
});
cs.new_command("codestr", "i", [](auto &, auto args, auto &res) {
res.set_str(cs_string(1, char(args[0].get_int())));
new_cmd_quiet(cs, "strlen", "s", [](auto &ccs, auto args, auto &res) {
res.set_integer(integer_type(args[0].get_string(ccs).size()));
});
cs.new_command("strlower", "s", [](auto &, auto args, auto &res) {
cs_string s = args[0].get_str();
for (auto i: ostd::range(s.size())) {
s[i] = tolower(s[i]);
new_cmd_quiet(cs, "strcode", "si", [](auto &ccs, auto args, auto &res) {
std::string_view str = args[0].get_string(ccs);
integer_type i = args[1].get_integer();
if (i >= integer_type(str.size())) {
res.set_integer(0);
} else {
res.set_integer(static_cast<unsigned char>(str[i]));
}
res.set_str(std::move(s));
});
cs.new_command("strupper", "s", [](auto &, auto args, auto &res) {
cs_string s = args[0].get_str();
for (auto i: ostd::range(s.size())) {
s[i] = toupper(s[i]);
new_cmd_quiet(cs, "codestr", "i", [](auto &ccs, auto args, auto &res) {
char const p[2] = { char(args[0].get_integer()), '\0' };
res.set_string(std::string_view{static_cast<char const *>(p)}, ccs);
});
new_cmd_quiet(cs, "strlower", "s", [](auto &ccs, auto args, auto &res) {
auto inps = args[0].get_string(ccs);
auto *ics = state_p{ccs}.ts().istate;
auto *buf = ics->strman->alloc_buf(inps.size());
for (std::size_t i = 0; i < inps.size(); ++i) {
buf[i] = char(tolower(inps.data()[i]));
}
res.set_str(std::move(s));
res.set_string(ics->strman->steal(buf));
});
cs.new_command("escape", "s", [](auto &, auto args, auto &res) {
auto s = ostd::appender<cs_string>();
util::escape_string(s, args[0].get_strr());
res.set_str(std::move(s.get()));
new_cmd_quiet(cs, "strupper", "s", [](auto &ccs, auto args, auto &res) {
auto inps = args[0].get_string(ccs);
auto *ics = state_p{ccs}.ts().istate;
auto *buf = ics->strman->alloc_buf(inps.size());
for (std::size_t i = 0; i < inps.size(); ++i) {
buf[i] = char(toupper(inps.data()[i]));
}
res.set_string(ics->strman->steal(buf));
});
cs.new_command("unescape", "s", [](auto &, auto args, auto &res) {
auto s = ostd::appender<cs_string>();
util::unescape_string(s, args[0].get_strr());
res.set_str(std::move(s.get()));
new_cmd_quiet(cs, "escape", "s", [](auto &ccs, auto args, auto &res) {
charbuf s{ccs};
escape_string(std::back_inserter(s), args[0].get_string(ccs));
res.set_string(s.str(), ccs);
});
cs.new_command("concat", "V", [](auto &, auto args, auto &res) {
auto s = ostd::appender<cs_string>();
cscript::util::tvals_concat(s, args, " ");
res.set_str(std::move(s.get()));
new_cmd_quiet(cs, "unescape", "s", [](auto &ccs, auto args, auto &res) {
charbuf s{ccs};
unescape_string(std::back_inserter(s), args[0].get_string(ccs));
res.set_string(s.str(), ccs);
});
cs.new_command("concatword", "V", [](auto &, auto args, auto &res) {
auto s = ostd::appender<cs_string>();
cscript::util::tvals_concat(s, args);
res.set_str(std::move(s.get()));
new_cmd_quiet(cs, "concat", "...", [](auto &ccs, auto args, auto &res) {
res.set_string(concat_values(ccs, args, " "));
});
cs.new_command("format", "V", [](auto &, auto args, auto &res) {
new_cmd_quiet(cs, "concatword", "...", [](auto &ccs, auto args, auto &res) {
res.set_string(concat_values(ccs, args));
});
new_cmd_quiet(cs, "format", "...", [](auto &ccs, auto args, auto &res) {
if (args.empty()) {
return;
}
cs_string s;
cs_string fs = args[0].get_str();
ostd::string_range f = ostd::iter(fs);
while (!f.empty()) {
char c = *f;
++f;
if ((c == '%') && !f.empty()) {
char ic = *f;
++f;
if (ic >= '1' && ic <= '9') {
charbuf s{ccs};
string_ref fs = args[0].get_string(ccs);
std::string_view f{fs};
for (auto it = f.begin(); it != f.end(); ++it) {
char c = *it;
++it;
if ((c == '%') && (it != f.end())) {
char ic = *it;
++it;
if ((ic >= '1') && (ic <= '9')) {
int i = ic - '0';
if (size_t(i) < args.size()) {
s += args[i].get_str();
if (std::size_t(i) < args.size()) {
s.append(args[i].get_string(ccs));
}
} else {
s += ic;
s.push_back(ic);
}
} else {
s += c;
s.push_back(c);
}
}
res.set_str(std::move(s));
res.set_string(s.str(), ccs);
});
cs.new_command("tohex", "ii", [](auto &, auto args, auto &res) {
auto r = ostd::appender<cs_string>();
try {
ostd::format(
r, "0x%.*X", std::max(args[1].get_int(), cs_int(1)),
args[0].get_int()
);
} catch (ostd::format_error const &e) {
throw cs_internal_error{e.what()};
new_cmd_quiet(cs, "tohex", "ii", [](auto &ccs, auto args, auto &res) {
char buf[32];
/* use long long as the largest signed integer type */
auto val = static_cast<long long>(args[0].get_integer());
int prec = std::max(int(args[1].get_integer()), 1);
int n = snprintf(buf, sizeof(buf), "0x%.*llX", prec, val);
if (n >= int(sizeof(buf))) {
charbuf s{ccs};
s.reserve(n + 1);
s.data()[0] = '\0';
int nn = snprintf(s.data(), n + 1, "0x%.*llX", prec, val);
if ((nn > 0) && (nn <= n)) {
res.set_string(
std::string_view{s.data(), std::size_t(nn)}, ccs
);
return;
}
} else if (n > 0) {
res.set_string(static_cast<char const *>(buf), ccs);
return;
}
res.set_str(std::move(r.get()));
/* should be unreachable */
abort();
});
cs.new_command("substr", "siiN", [](auto &, auto args, auto &res) {
ostd::string_range s = args[0].get_strr();
cs_int start = args[1].get_int(), count = args[2].get_int();
cs_int numargs = args[3].get_int();
cs_int len = cs_int(s.size()), offset = std::clamp(start, cs_int(0), len);
res.set_str(cs_string{
new_cmd_quiet(cs, "substr", "sii#", [](auto &ccs, auto args, auto &res) {
std::string_view s = args[0].get_string(ccs);
auto start = args[1].get_integer(), count = args[2].get_integer();
auto numargs = args[3].get_integer();
auto len = integer_type(s.size());
auto offset = std::clamp(start, integer_type(0), len);
res.set_string(std::string_view{
&s[offset],
(numargs >= 3)
? size_t(std::clamp(count, cs_int(0), len - offset))
: size_t(len - offset)
});
((numargs >= 3)
? size_t(std::clamp(count, integer_type(0), len - offset))
: size_t(len - offset))
}, ccs);
});
cs.new_command("strcmp", "s1V", [](auto &, auto args, auto &res) {
cs_strgcmp(args, res, std::equal_to<ostd::string_range>());
new_cmd_quiet(cs, "strcmp", "s1...", [](auto &ccs, auto args, auto &res) {
str_cmp_by(ccs, args, res, std::equal_to<std::string_view>());
});
cs.new_command("=s", "s1V", [](auto &, auto args, auto &res) {
cs_strgcmp(args, res, std::equal_to<ostd::string_range>());
new_cmd_quiet(cs, "=s", "s1...", [](auto &ccs, auto args, auto &res) {
str_cmp_by(ccs, args, res, std::equal_to<std::string_view>());
});
cs.new_command("!=s", "s1V", [](auto &, auto args, auto &res) {
cs_strgcmp(args, res, std::not_equal_to<ostd::string_range>());
new_cmd_quiet(cs, "!=s", "s1...", [](auto &ccs, auto args, auto &res) {
str_cmp_by(ccs, args, res, std::not_equal_to<std::string_view>());
});
cs.new_command("<s", "s1V", [](auto &, auto args, auto &res) {
cs_strgcmp(args, res, std::less<ostd::string_range>());
new_cmd_quiet(cs, "<s", "s1...", [](auto &ccs, auto args, auto &res) {
str_cmp_by(ccs, args, res, std::less<std::string_view>());
});
cs.new_command(">s", "s1V", [](auto &, auto args, auto &res) {
cs_strgcmp(args, res, std::greater<ostd::string_range>());
new_cmd_quiet(cs, ">s", "s1...", [](auto &ccs, auto args, auto &res) {
str_cmp_by(ccs, args, res, std::greater<std::string_view>());
});
cs.new_command("<=s", "s1V", [](auto &, auto args, auto &res) {
cs_strgcmp(args, res, std::less_equal<ostd::string_range>());
new_cmd_quiet(cs, "<=s", "s1...", [](auto &ccs, auto args, auto &res) {
str_cmp_by(ccs, args, res, std::less_equal<std::string_view>());
});
cs.new_command(">=s", "s1V", [](auto &, auto args, auto &res) {
cs_strgcmp(args, res, std::greater_equal<ostd::string_range>());
new_cmd_quiet(cs, ">=s", "s1...", [](auto &ccs, auto args, auto &res) {
str_cmp_by(ccs, args, res, std::greater_equal<std::string_view>());
});
cs.new_command("strreplace", "ssss", [](auto &, auto args, auto &res) {
ostd::string_range s = args[0].get_strr();
ostd::string_range oldval = args[1].get_strr(),
newval = args[2].get_strr(),
newval2 = args[3].get_strr();
new_cmd_quiet(cs, "strreplace", "ssss", [](
auto &ccs, auto args, auto &res
) {
std::string_view s = args[0].get_string(ccs);
std::string_view oldval = args[1].get_string(ccs),
newval = args[2].get_string(ccs),
newval2 = args[3].get_string(ccs);
if (newval2.empty()) {
newval2 = newval;
}
cs_string buf;
if (!oldval.size()) {
res.set_str(cs_string{s});
if (oldval.empty()) {
res.set_string(s, ccs);
return;
}
charbuf buf{ccs};
for (size_t i = 0;; ++i) {
ostd::string_range found;
ostd::string_range trys = s;
for (; oldval.size() <= trys.size(); ++trys) {
if (trys.slice(0, oldval.size()) == oldval) {
found = trys;
break;
}
}
if (!found.empty()) {
buf += s.slice(0, &found[0] - &s[0]);
buf += (i & 1) ? newval2 : newval;
s = found.slice(oldval.size(), found.size());
} else {
buf += s;
res.set_str(std::move(buf));
auto p = s.find(oldval);
if (p == s.npos) {
buf.append(s);
res.set_string(s, ccs);
return;
}
buf.append(s.substr(0, p));
buf.append((i & 1) ? newval2 : newval);
buf.append(s.substr(
p + oldval.size(),
s.size() - p - oldval.size()
));
}
});
cs.new_command("strsplice", "ssii", [](auto &, auto args, auto &res) {
ostd::string_range s = args[0].get_strr();
ostd::string_range vals = args[1].get_strr();
cs_int skip = args[2].get_int(),
count = args[3].get_int();
cs_int offset = std::clamp(skip, cs_int(0), cs_int(s.size())),
len = std::clamp(count, cs_int(0), cs_int(s.size()) - offset);
cs_string p;
new_cmd_quiet(cs, "strsplice", "ssii", [](
auto &ccs, auto args, auto &res
) {
std::string_view s = args[0].get_string(ccs);
std::string_view vals = args[1].get_string(ccs);
integer_type skip = args[2].get_integer(),
count = args[3].get_integer();
integer_type offset = std::clamp(skip, integer_type(0), integer_type(s.size())),
len = std::clamp(count, integer_type(0), integer_type(s.size()) - offset);
charbuf p{ccs};
p.reserve(s.size() - len + vals.size());
if (offset) {
p += s.slice(0, offset);
p.append(s.substr(0, offset));
}
if (!vals.empty()) {
p += vals;
p.append(vals);
if ((offset + len) < integer_type(s.size())) {
p.append(s.substr(offset + len, s.size() - offset - len));
}
if ((offset + len) < cs_int(s.size())) {
p += s.slice(offset + len, s.size());
}
res.set_str(std::move(p));
res.set_string(p.str(), ccs);
});
}
} /* namespace cscript */
} /* namespace cubescript */

View File

@ -1,38 +1,61 @@
libcubescript_header_src = [
'../include/cubescript/cubescript.hh',
'../include/cubescript/cubescript_conf.hh'
]
libcubescript_src = [
'cs_bcode.cc',
'cs_error.cc',
'cs_gen.cc',
'cs_util.cc',
'cs_ident.cc',
'cs_parser.cc',
'cs_state.cc',
'cs_std.cc',
'cs_strman.cc',
'cs_thread.cc',
'cs_val.cc',
'cs_vm.cc',
'cubescript.cc',
'lib_base.cc',
'lib_list.cc',
'lib_math.cc',
'lib_str.cc'
]
libostd_dep = dependency('libostd', fallback: ['libostd', 'libostd_static'])
lib_cxxflags = extra_cxxflags + [ '-DLIBCUBESCRIPT_BUILD' ]
dyn_cxxflags = lib_cxxflags
libcubescript_lib = both_libraries('cubescript',
libcubescript_src,
dependencies: libostd_dep,
include_directories: libcubescript_includes + [include_directories('.')],
cpp_args: extra_cxxflags,
install: true,
version: meson.project_version()
)
lib_incdirs = libcubescript_includes + [include_directories('.')]
host_system = host_machine.system()
os_uses_dlls = (host_system == 'windows' or host_system == 'cygwin')
if os_uses_dlls
dyn_cxxflags += '-DLIBCUBESCRIPT_DLL'
endif
if os_uses_dlls and get_option('default_library') == 'both'
# we need a bunch of this nonsense on windows as both_libraries()
# does not work reliably there; since DLLs work like they do, we
# need to compile one set of object files with dllexport and the
# other set without that, and there is no way to tell meson to
# do that other than making two different targets...
libcubescript_static = static_library('cubescript',
libcubescript_src, include_directories: lib_incdirs,
cpp_args: lib_cxxflags,
install: true
)
libcubescript_dynamic = shared_library('cubescript',
libcubescript_src, include_directories: lib_incdirs,
cpp_args: dyn_cxxflags,
install: true,
version: meson.project_version()
)
libcubescript_target = libcubescript_dynamic
else
libcubescript_target = library('cubescript',
libcubescript_src, include_directories: lib_incdirs,
cpp_args: dyn_cxxflags,
install: true,
version: meson.project_version()
)
endif
libcubescript = declare_dependency(
include_directories: libcubescript_includes,
link_with: libcubescript_lib.get_shared_lib()
link_with: libcubescript_target
)
libcubescript_static = declare_dependency(
include_directories: libcubescript_includes,
link_with: libcubescript_lib.get_static_lib()
)
install_headers(libcubescript_header_src, install_dir: dir_package_include)

View File

@ -1,4 +0,0 @@
[wrap-git]
directory = libostd
url = https://git.octaforge.org/OctaForge/libostd.git
revision = head

40
tests/meson.build 100644
View File

@ -0,0 +1,40 @@
lang_tests = [
# test_name test_file expected_fail
['simple example', 'simple', false],
]
lib_tests = [
]
test_runner = executable('runner',
['runner.cc'],
dependencies: libcubescript,
include_directories: libcubescript_includes,
cpp_args: extra_cxxflags,
install: false
)
penv = environment()
penv.append('PATH', join_paths(build_root, 'src'))
# when running tests for crossbuilds in wine, this is used instead
penv.append('WINEPATH', join_paths(build_root, 'src'))
foreach tcase: lang_tests
test(tcase[0],
test_runner,
args: [join_paths(meson.current_source_dir(), tcase[1] + '.cube')],
should_fail: tcase[2],
env: penv
)
endforeach
foreach tcase: lib_tests
test_exe = executable(tcase[0],
[tcase[0] + '.cc'],
dependencies: libcubescript,
include_directories: libcubescript_includes,
cpp_args: extra_cxxflags,
instalL: false
)
test(tcase[0], tcase[0], should_fail: tcase[1], env: penv)
endforeach

74
tests/runner.cc 100644
View File

@ -0,0 +1,74 @@
/* a rudimentary test runner for cubescript files */
#ifdef _MSC_VER
/* avoid silly complaints about fopen */
# define _CRT_SECURE_NO_WARNINGS 1
#endif
#include <cstdio>
#include <string>
#include <string_view>
#include <cubescript/cubescript.hh>
namespace cs = cubescript;
struct skip_test {};
static bool do_exec_file(cs::state &cs, char const *fname) {
FILE *f = std::fopen(fname, "rb");
if (!f) {
return false;
}
std::fseek(f, 0, SEEK_END);
auto len = std::ftell(f);
std::fseek(f, 0, SEEK_SET);
auto buf = std::make_unique<char[]>(len + 1);
if (!buf) {
std::fclose(f);
return false;
}
if (std::fread(buf.get(), 1, len, f) != std::size_t(len)) {
std::fclose(f);
return false;
}
buf[len] = '\0';
cs.compile(std::string_view{buf.get(), std::size_t(len)}, fname).call(cs);
return true;
}
int main(int argc, char **argv) {
if (argc != 2) {
std::fprintf(stderr, "error: incorrect number of arguments\n");
}
cs::state gcs;
cs::std_init_all(gcs);
gcs.new_command("echo", "...", [](auto &s, auto args, auto &) {
std::printf("%s\n", cs::concat_values(s, args, " ").data());
});
gcs.new_command("skip_test", "", [](auto &, auto, auto &) {
throw skip_test{};
});
try {
do_exec_file(gcs, argv[1]);
} catch (skip_test) {
return 77;
} catch (cs::error const &e) {
std::fprintf(stderr, "error: %s", e.what().data());
return 1;
} catch (...) {
std::fprintf(stderr, "error: unknown error");
return 1;
}
return 0;
}

21
tests/simple.cube 100644
View File

@ -0,0 +1,21 @@
// a simple test mainly to serve as an example
echo "hello world"
// addition
assert [= (+ 5 10) 15]
assert [!= (+ 4 5) 11]
// multiplication
assert [= (* 5 2) 10]
assert [!= (* 3 4) 13]
// string equality
assert [=s "hello world" "hello world"]
assert [!=s "hello world" "dlrow olleh"]
// simple loop
x = 5
loop i 3 [x = (* $x 2); echo $x]
assert [= $x 40]

View File

@ -2,18 +2,26 @@
/* use nothing (no line editing support) */
#include <optional>
#include <string>
#include <ostd/string.hh>
inline void init_lineedit(cs_state &, ostd::string_range) {
inline void init_lineedit(cs::state &, std::string_view) {
}
inline std::optional<std::string> read_line(cs_state &, cs_svar *pr) {
ostd::write(pr->get_value());
return std::move(ostd::cin.get_line(ostd::appender<std::string>()).get());
inline std::optional<std::string> read_line(cs::state &, cs::string_var &pr) {
std::string lbuf;
char buf[512];
printf("%s", pr.value().data());
std::fflush(stdout);
while (fgets(buf, sizeof(buf), stdin)) {
lbuf += static_cast<char const *>(buf);
if (strchr(buf, '\n')) {
break;
}
}
return std::move(lbuf);
}
inline void add_history(cs_state &, ostd::string_range) {
inline void add_history(cs::state &, std::string_view) {
}
#endif

View File

@ -1,5 +1,4 @@
#ifdef CS_REPL_USE_LINENOISE
#ifdef OSTD_PLATFORM_POSIX
#ifndef CS_REPL_HAS_EDIT
#define CS_REPL_HAS_EDIT
/* use the bundled linenoise library, default */
@ -10,59 +9,52 @@
#include <optional>
#include <ostd/string.hh>
#include "linenoise.hh"
static cs_state *ln_cs = nullptr;
static cs::state *ln_cs = nullptr;
inline void ln_complete(char const *buf, linenoiseCompletions *lc) {
ostd::string_range cmd = get_complete_cmd(buf);
for (auto id: ln_cs->get_idents()) {
if (!id->is_command()) {
inline void ln_complete(char const *buf, std::vector<std::string> &lc) {
std::string_view cmd = get_complete_cmd(buf);
for (std::size_t i = 0; i < ln_cs->ident_count(); ++i) {
auto &id = ln_cs->get_ident(i);
if (id.type() != cs::ident_type::COMMAND) {
continue;
}
ostd::string_range idname = id->get_name();
std::string_view idname = id.name();
if (idname.size() <= cmd.size()) {
continue;
}
if (idname.slice(0, cmd.size()) == cmd) {
linenoiseAddCompletion(lc, idname.data());
if (idname.substr(0, cmd.size()) == cmd) {
lc.emplace_back(idname);
}
}
}
inline char *ln_hint(char const *buf, int *color, int *bold) {
cs_command *cmd = get_hint_cmd(*ln_cs, buf);
inline std::string ln_hint(char const *buf, int &color, int &bold) {
cs::command *cmd = get_hint_cmd(*ln_cs, buf);
if (!cmd) {
return nullptr;
return std::string{};
}
std::string args = " [";
fill_cmd_args(args, cmd->get_args());
fill_cmd_args(args, cmd->args());
args += ']';
*color = 35;
*bold = 1;
char *ret = new char[args.size() + 1];
memcpy(ret, args.data(), args.size() + 1);
return ret;
color = 35;
bold = 1;
return args;
}
inline void ln_hint_free(void *hint) {
delete[] static_cast<char *>(hint);
}
inline void init_lineedit(cs_state &cs, ostd::string_range) {
inline void init_lineedit(cs::state &cs, std::string_view) {
/* sensible default history size */
linenoiseHistorySetMaxLen(1000);
linenoise::SetHistoryMaxLen(1000);
ln_cs = &cs;
linenoiseSetCompletionCallback(ln_complete);
linenoiseSetHintsCallback(ln_hint);
linenoiseSetFreeHintsCallback(ln_hint_free);
linenoise::SetCompletionCallback(ln_complete);
linenoise::SetHintsCallback(ln_hint);
}
inline std::optional<std::string> read_line(cs_state &, cs_svar *pr) {
auto line = linenoise(pr->get_value().data());
if (!line) {
inline std::optional<std::string> read_line(cs::state &s, cs::builtin_var &pr) {
std::string line;
auto quit = linenoise::Readline(pr.value().get_string(s).data(), line);
if (quit) {
/* linenoise traps ctrl-c, detect it and let the user exit */
if (errno == EAGAIN) {
raise(SIGINT);
@ -70,16 +62,13 @@ inline std::optional<std::string> read_line(cs_state &, cs_svar *pr) {
}
return std::string{};
}
std::string ret = line;
linenoiseFree(line);
return std::move(ret);
return line;
}
inline void add_history(cs_state &, ostd::string_range line) {
inline void add_history(cs::state &, std::string_view line) {
/* backed by std::string so it's terminated */
linenoiseHistoryAdd(line.data());
linenoise::AddHistory(line.data());
}
#endif
#endif
#endif

View File

@ -1,88 +0,0 @@
#ifdef CS_REPL_USE_READLINE
#ifndef CS_REPL_HAS_EDIT
#define CS_REPL_HAS_EDIT
/* use the GNU readline library */
#include <string.h>
#include <optional>
#include <ostd/string.hh>
#include <readline/readline.h>
#include <readline/history.h>
static cs_state *rd_cs = nullptr;
inline char *ln_complete_list(char const *buf, int state) {
static ostd::string_range cmd;
static ostd::iterator_range<cs_ident **> itr;
if (!state) {
cmd = get_complete_cmd(buf);
itr = rd_cs->get_idents();
}
for (; !itr.empty(); itr.pop_front()) {
cs_ident *id = itr.front();
if (!id->is_command()) {
continue;
}
ostd::string_range idname = id->get_name();
if (idname.size() <= cmd.size()) {
continue;
}
if (idname.slice(0, cmd.size()) == cmd) {
itr.pop_front();
return strdup(idname.data());
}
}
return nullptr;
}
inline char **ln_complete(char const *buf, int, int) {
rl_attempted_completion_over = 1;
return rl_completion_matches(buf, ln_complete_list);
}
inline void ln_hint() {
cs_command *cmd = get_hint_cmd(*rd_cs, rl_line_buffer);
if (!cmd) {
rl_redisplay();
return;
}
std::string old = rl_line_buffer;
std::string args = old;
args += " [";
fill_cmd_args(args, cmd->get_args());
args += "] ";
rl_extend_line_buffer(args.size());
rl_replace_line(args.data(), 0);
rl_redisplay();
rl_replace_line(old.data(), 0);
}
inline void init_lineedit(cs_state &cs, ostd::string_range) {
rd_cs = &cs;
rl_attempted_completion_function = ln_complete;
rl_redisplay_function = ln_hint;
}
inline std::optional<std::string> read_line(cs_state &, cs_svar *pr) {
auto line = readline(pr->get_value().data());
if (!line) {
return std::string();
}
std::string ret = line;
free(line);
return std::move(ret);
}
inline void add_history(cs_state &, ostd::string_range line) {
/* backed by std::string so it's terminated */
add_history(line.data());
}
#endif
#endif

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -2,21 +2,18 @@ repl_src = [
'repl.cc'
]
repl_deps = [libostd_dep, libcubescript]
repl_deps = [libcubescript]
repl_flags = []
if get_option('readline')
repl_deps += [dependency('readline')]
repl_flags = ['-DCS_REPL_USE_READLINE']
elif get_option('linenoise')
repl_src += ['linenoise.cc']
repl_flags = ['-DCS_REPL_USE_LINENOISE']
if not get_option('repl').disabled()
if not get_option('linenoise').disabled()
repl_flags = ['-DCS_REPL_USE_LINENOISE']
endif
executable('cubescript',
repl_src,
dependencies: repl_deps,
include_directories: libcubescript_includes + [include_directories('.')],
cpp_args: extra_cxxflags + repl_flags,
install: true
)
endif
executable('cubescript',
repl_src,
dependencies: repl_deps,
include_directories: libcubescript_includes + [include_directories('.')],
cpp_args: extra_cxxflags + repl_flags,
install: true
)

View File

@ -1,20 +1,32 @@
#ifdef _MSC_VER
/* avoid silly complaints about fopen */
# define _CRT_SECURE_NO_WARNINGS 1
/* work around clang bug with std::function (needed by linenoise) */
# if defined(__clang__) && !defined(_HAS_STATIC_RTTI)
# define _HAS_STATIC_RTTI 0
# endif
#endif
#include <signal.h>
#include <cmath>
#include <cctype>
#include <cstring>
#include <cstdio>
#include <optional>
#include <ostd/platform.hh>
#include <ostd/io.hh>
#include <ostd/string.hh>
#include <memory>
#include <iterator>
#include <string>
#include <cubescript/cubescript.hh>
using namespace cscript;
namespace cs = cubescript;
ostd::string_range version = "CubeScript 0.0.1";
std::string_view version = "CubeScript 0.0.1";
/* util */
#ifdef OSTD_PLATFORM_WIN32
#if defined(_WIN32)
#include <io.h>
static bool stdin_is_tty() {
return _isatty(_fileno(stdin));
@ -28,40 +40,31 @@ static bool stdin_is_tty() {
/* line editing support */
inline ostd::string_range get_complete_cmd(ostd::string_range buf) {
ostd::string_range not_allowed = "\"/;()[] \t\r\n\0";
ostd::string_range found = ostd::find_one_of(buf, not_allowed);
while (!found.empty()) {
++found;
buf = found;
found = ostd::find_one_of(found, not_allowed);
inline std::string_view get_complete_cmd(std::string_view buf) {
std::string_view not_allowed = "\"/;()[] \t\r\n\0";
auto found = buf.find_first_of(not_allowed);
while (found != buf.npos) {
buf = buf.substr(found + 1, buf.size() - found - 1);
found = buf.find_first_of(not_allowed);
}
return buf;
}
inline ostd::string_range get_arg_type(char arg) {
inline std::string_view get_arg_type(char arg) {
switch (arg) {
case 'i':
return "int";
case 'b':
return "int_min";
case 'f':
return "float";
case 'F':
return "float_prev";
case 't':
case 'a':
return "any";
case 'T':
return "any_m";
case 'E':
case 'c':
return "cond";
case 'N':
return "numargs";
case 'S':
return "str_m";
case 's':
return "str";
case 'e':
case 'b':
return "block";
case 'r':
return "ident";
@ -71,21 +74,19 @@ inline ostd::string_range get_arg_type(char arg) {
return "illegal";
}
inline void fill_cmd_args(std::string &writer, ostd::string_range args) {
char variadic = '\0';
inline void fill_cmd_args(std::string &writer, std::string_view args) {
bool variadic = false;
int nrep = 0;
if (!args.empty() && ((args.back() == 'V') || (args.back() == 'C'))) {
variadic = args.back();
args.pop_back();
if ((args.size() >= 3) && (args.substr(args.size() - 3) == "...")) {
variadic = true;
args.remove_suffix(3);
if (!args.empty() && isdigit(args.back())) {
nrep = args.back() - '0';
args.pop_back();
args.remove_suffix(1);
}
}
if (args.empty()) {
if (variadic == 'C') {
writer += "concat(...)";
} else if (variadic == 'V') {
if (variadic) {
writer += "...";
}
return;
@ -96,17 +97,14 @@ inline void fill_cmd_args(std::string &writer, ostd::string_range args) {
if (i != 0) {
writer += ", ";
}
writer += get_arg_type(*args);
++args;
writer += get_arg_type(args.front());
args.remove_prefix(1);
}
}
if (variadic) {
if (norep > 0) {
writer += ", ";
}
if (variadic == 'C') {
writer += "concat(";
}
if (!args.empty()) {
if (args.size() > 1) {
writer += '{';
@ -122,124 +120,168 @@ inline void fill_cmd_args(std::string &writer, ostd::string_range args) {
}
}
writer += "...";
if (variadic == 'C') {
writer += ")";
}
}
}
inline cs_command *get_hint_cmd(cs_state &cs, ostd::string_range buf) {
ostd::string_range nextchars = "([;";
auto lp = ostd::find_one_of(buf, nextchars);
if (!lp.empty()) {
cs_command *cmd = get_hint_cmd(cs, buf.slice(1, buf.size()));
inline cs::command *get_hint_cmd(cs::state &cs, std::string_view buf) {
std::string_view nextchars = "([;";
auto lp = buf.find_first_of(nextchars);
if (lp != buf.npos) {
cs::command *cmd = get_hint_cmd(cs, buf.substr(1, buf.size() - 1));
if (cmd) {
return cmd;
}
}
while (!buf.empty() && isspace(buf.front())) {
++buf;
std::size_t nsp = 0;
for (auto c: buf) {
if (!isspace(c)) {
break;
}
++nsp;
}
ostd::string_range spaces = " \t\r\n";
ostd::string_range s = ostd::find_one_of(buf, spaces);
if (!s.empty()) {
buf = buf.slice(0, &s[0] - &buf[0]);
buf.remove_prefix(nsp);
std::string_view spaces = " \t\r\n";
auto p = buf.find_first_of(spaces);
if (p != buf.npos) {
buf = buf.substr(0, p);
}
if (!buf.empty()) {
auto cmd = cs.get_ident(buf);
return cmd ? cmd->get_command() : nullptr;
if (cmd && (cmd->get().type() == cs::ident_type::COMMAND)) {
return static_cast<cs::command *>(&cmd->get());
}
}
return nullptr;
}
#include "edit_linenoise.hh"
#include "edit_readline.hh"
#include "edit_fallback.hh"
/* usage */
void print_usage(ostd::string_range progname, bool err) {
auto &s = err ? ostd::cerr : ostd::cout;
s.writeln(
"Usage: ", progname, " [options] [file]\n"
void print_usage(std::string_view progname, bool err) {
std::fprintf(
err ? stderr : stdout,
"Usage: %s [options] [file]\n"
"Options:\n"
" -e str run string \"str\"\n"
" -e str call string \"str\"\n"
" -i enter interactive mode after the above\n"
" -v show version information\n"
" -h show this message\n"
" -- stop handling options\n"
" - execute stdin and stop handling options"
"\n",
progname.data()
);
s.flush();
}
void print_version() {
ostd::writeln(version);
printf("%s\n", version.data());
}
static cs_state *scs = nullptr;
static cs::state *scs = nullptr;
static void do_sigint(int n) {
/* in case another SIGINT happens, terminate normally */
signal(n, SIG_DFL);
scs->set_call_hook([](cs_state &cs) {
cs.set_call_hook(nullptr);
throw cscript::cs_error(cs, "<execution interrupted>");
scs->call_hook([](cs::state &css) {
css.call_hook(nullptr);
throw cs::error{css, "<execution interrupted>"};
});
}
static bool do_call(cs_state &cs, ostd::string_range line, bool file = false) {
cs_value ret;
static bool do_exec_file(
cs::state &cs, std::string_view fname, cs::any_value &ret
) {
FILE *f = std::fopen(fname.data(), "rb");
if (!f) {
return false;
}
std::fseek(f, 0, SEEK_END);
auto len = std::ftell(f);
std::fseek(f, 0, SEEK_SET);
auto buf = std::make_unique<char[]>(len + 1);
if (!buf) {
std::fclose(f);
return false;
}
if (std::fread(buf.get(), 1, len, f) != std::size_t(len)) {
std::fclose(f);
return false;
}
buf[len] = '\0';
ret = cs.compile(
std::string_view{buf.get(), std::size_t(len)}, fname
).call(cs);
return true;
}
static bool do_call(cs::state &cs, std::string_view line, bool file = false) {
cs::any_value ret{};
scs = &cs;
signal(SIGINT, do_sigint);
try {
if (file) {
if (!cs.run_file(line, ret)) {
ostd::cerr.writeln("cannot read file: ", line);
if (!do_exec_file(cs, line, ret)) {
std::fprintf(stderr, "cannot read file: %s\n", line.data());
}
} else {
cs.run(line, ret);
ret = cs.compile(line).call(cs);
}
} catch (cscript::cs_error const &e) {
} catch (cs::error const &e) {
signal(SIGINT, SIG_DFL);
scs = nullptr;
ostd::string_range terr = e.what();
auto col = ostd::find(terr, ':');
std::string_view terr = e.what();
auto col = terr.find(':');
bool is_lnum = false;
if (!col.empty()) {
is_lnum = ostd::find_if(
terr.slice(0, &col[0] - &terr[0]),
if (col != terr.npos) {
auto pre = terr.substr(0, col);
auto it = std::find_if(
pre.begin(), pre.end(),
[](auto c) { return !isdigit(c); }
).empty();
terr = col.slice(2, col.size());
);
is_lnum = (it == pre.end());
terr = terr.substr(col + 2, terr.size() - col - 2);
}
if (!file && ((terr == "missing \"]\"") || (terr == "missing \")\""))) {
return true;
}
ostd::writeln(!is_lnum ? "stdin: " : "stdin:", e.what());
if (e.get_stack().get()) {
cscript::util::print_stack(ostd::cout.iter(), e.get_stack());
ostd::write('\n');
std::printf(
"%s%s\n", !is_lnum ? "stdin: " : "stdin:", e.what().data()
);
std::size_t pindex = 1;
for (auto &nd: e.stack()) {
std::printf(" ");
if ((nd.index == 1) && (pindex > 2)) {
std::printf("..");
}
pindex = nd.index;
std::printf("%zu) %s\n", nd.index, nd.id.name().data());
}
return false;
}
signal(SIGINT, SIG_DFL);
scs = nullptr;
if (ret.get_type() != cs_value_type::Null) {
ostd::writeln(ret.get_str());
if (ret.type() != cs::value_type::NONE) {
std::printf("%s\n", std::string_view{ret.get_string(cs)}.data());
}
return false;
}
static void do_tty(cs_state &cs) {
auto prompt = cs.new_svar("PROMPT", "> ");
auto prompt2 = cs.new_svar("PROMPT2", ">> ");
static void do_tty(cs::state &cs) {
auto &prompt = cs.new_var("PROMPT", "> ");
auto &prompt2 = cs.new_var("PROMPT2", ">> ");
bool do_exit = false;
cs.new_command("quit", "", [&do_exit](auto &, auto, auto &) {
do_exit = true;
});
ostd::writeln(version, " (REPL mode)");
std::printf("%s (REPL mode)\n", version.data());
for (;;) {
auto line = read_line(cs, prompt);
if (!line) {
@ -271,21 +313,67 @@ static void do_tty(cs_state &cs) {
}
int main(int argc, char **argv) {
cs_state gcs;
gcs.init_libs();
cs::state gcs;
cs::std_init_all(gcs);
gcs.new_command("exec", "s", [](auto &cs, auto args, auto &) {
auto file = args[0].get_strr();
bool ret = cs.run_file(file);
if (!ret) {
throw cscript::cs_error(
cs, "could not run file \"%s\"", file
/* this is how you can override a setter for variables; fvar and svar
* work equivalently - in this case we want to allow multiple values
* to be set, but you may also not be using standard i/o and so on
*/
gcs.new_command("//ivar", "$iii#", [](auto &css, auto args, auto &) {
auto &iv = static_cast<cs::builtin_var &>(args[0].get_ident(css));
auto nargs = args[4].get_integer();
if (nargs <= 1) {
auto val = iv.value().get_integer();
if ((val >= 0) && (val < 0xFFFFFF)) {
std::printf(
"%s = %d (0x%.6X: %d, %d, %d)\n",
iv.name().data(), val, val,
(val >> 16) & 0xFF, (val >> 8) & 0xFF, val & 0xFF
);
} else {
std::printf("%s = %d\n", iv.name().data(), val);
}
return;
}
cs::any_value nv;
if (nargs == 2) {
nv = args[1];
} else if (nargs == 3) {
nv = (args[1].get_integer() << 8) | (args[2].get_integer() << 16);
} else {
nv = (
args[1].get_integer() | (args[2].get_integer() << 8) |
(args[3].get_integer() << 16)
);
}
iv.set_value(css, nv);
});
gcs.new_command("//var_changed", "$aa", [](auto &css, auto args, auto &) {
std::printf(
"changed var trigger: %s (was: '%s', now: '%s')\n",
args[0].get_ident(css).name().data(),
args[1].get_string(css).data(),
args[2].get_string(css).data()
);
});
gcs.new_command("exec", "s", [](auto &css, auto args, auto &) {
auto file = args[0].get_string(css);
cs::any_value val{};
bool ret = do_exec_file(css, file, val);
if (!ret) {
char buf[4096];
std::snprintf(
buf, sizeof(buf), "could not execute file \"%s\"", file.data()
);
throw cs::error(css, buf);
}
});
gcs.new_command("echo", "C", [](auto &, auto args, auto &) {
ostd::writeln(args[0].get_strr());
gcs.new_command("echo", "...", [](auto &css, auto args, auto &) {
std::printf("%s\n", cs::concat_values(css, args, " ").data());
});
int firstarg = 0;
@ -371,8 +459,8 @@ endargs:
return 0;
} else {
std::string str;
for (char c = '\0'; (c = ostd::cin.get_char()) != EOF;) {
str += c;
for (int c = '\0'; (c = std::fgetc(stdin)) != EOF;) {
str += char(c);
}
do_call(gcs, str);
}