Compared to... - KidneyThief/TinScript1.0 GitHub Wiki
While Tinscript will never have the depth or flexibility of C++, the goal is that whatever features it does provide, is uses a syntax that is *almost* valid C++ syntax as well. Eventually, I hope to provide a C++ export method that will handle the few discrepancies:
- Class definitions:
- TinScript does not require them, as methods and members of an object's namespace can be added dynamically - not just by reloading a script, but to the current objects as well.
- The trade off is, errors such as an "invalid member" are caught at runtime instead of compile time.
- The 'self' keyword:
- For the same reason that we do not require class definitions, we do need the explicit scoping of members and methods. While this is the same concept as the 'this' pointer in C++, its usage is not inferred.
- Variable scoping:
- In TinScript, there are only two scopes for a variable - global to the context, or local to the entire function/method. If statements, while loops, etc... share the same scope as the function.
- Variable declaration:
- As per the above, variables used in 'for' loops must be declared before the loop, and not within the initial expression.
- int i; for (i == 0; i < count; ++i) ... // this is valid
- for (int i = 0; i < count; ++i) ... // this is invalid
- As per the above, variables used in 'for' loops must be declared before the loop, and not within the initial expression.
- Conditional expressions:
- In C++, they are evaluated left to right, and the evaluation is preempted once the entire conditional value has been determined.
- In TinScript, this optimization has not yet been implemented, and the entire conditional is always fully evaluated.
- if (AlwaysFalse() && SomeOtherExpression()) ...
- In C++, the evaluation would be preempted after AlwaysFalse() returns 'false'.
- In TinScript, SomeOtherExpression() would still be evaluated.
- String delineation:
- In C++, only double-quotes are used to delineate a string, so any embedded double-quote characters must be escaped with a forward slash.
- In TinScript, strings can be delineated by any matching pair of double quotes (""), single quotes (''), or back quotes (``), so you always have an option.
- You can also use StringCat() to concatenate two differently delineated strings.
Being experienced with, but not an expert on Lua, this is not meant to be a complete comparison - but hopefully the distinctions here are both accurate and important.
- Performance:
- For general execution of scripted functions, access to registered members and methods, etc... both languages are comparable, as both make (very similar) heavy use of hash tables.
- For Recursive functions, Lua wins hands down - TinScript has no optimizations (such as tail-recursion), and relies on the scripted functions being compilable in C++, when performance becomes critical.
- Of course, LuaJIT is also a substantial performance advantage over TinScript.
- Variable declaration:
- TinScript uses both strongly typed variables, and requires them to be declared. Whether this is an advantage is subjective, but since members can also be added dynamically, the benefits of both runtime and compile time error catching, without sacrificing iteration speed is my preference.
- Additionally, a case sensitive language without variable declaration has lead to issues where inconsistent case is syntactically correct, but logically refers to different variables, which can be difficult to debug.
- Integration to C++:
- Lua provides the ability to have multiple independent lua states, whereas TinScript enforces a single context per thread.
- Once a (global) function or variable has been declared, I prefer free access without having to resolve issues where the function isn't accessible because it was defined in a different "context".
- Additionally, (this is very dependent on the integration approach), TinScript registration uses a single macro per function/method/member/etc... and only requires intermediate "wrappers" to be implemented when using an unregistered type or class. My experiences with Lua involve wrapping each function with an interface that accesses the specific lua state, and manually manages pushing and popping arguments from the stack.
- Iteration Speed:
- One big difference between the two languages comes down to co-routines vs schedules. Both are capable of solving a similar set of problems, but it is my belief that a scheduler allows for more freedom in reloading scripts without having to recreate or restart the objects relying on them.
- When a co-routine yields, it essentially "pauses" the execution of a function, requiring Lua to maintain the current state - value of all local variables, current stack of functions being called, the next instruction to be executed when it resumes... Reloading a script during execution of a co-routine is either not possible, or requires some non-trivial support for caching and restoring the current execution state.
- When a TinScript function is called, it is executed until it is finished. By structuring an Update() method into a simple state machine (phase1, phase2, etc...) in the same way that a Lua co-routine is organized into instruction blocks separated by yield()s, the next call to the Update() being scheduled allows for the system to be paused, the implementation of the Update() method to be modified, and when the scheduled call is dispatched, it simply finds the new implementation, without any "paused" state complications.
- Accessibility:
- This is completely dependent on the engineer, and the integration approach. It is noteworthy that TinScript only requires three hooks into a system - creation, update, and destruction of the context. Accessibility is an important goal in development, and the speed at which integration can be completed is hard to beat.
- Another key goal is the transparency of the implementation - especially for debugging and understanding the system. From script to code, the number of layers of execution is minimal. For example, issuing the command Print("Hello world"); here's a look at the call stack:
- ExecCommand() is the first step, passing the text parser.
- ExecuteCodeBlock() is a wrapper to create the execution stacks, and Execute() actually process the byte code in the virtual machine.
- OpExecFuncCall() is the specific instruction to dispatch the registered 'Print' function.
- Two layers in the binding code to DispatchFunction(), converting all the arguments, and Dispatch() to actually call the function pointer.
- And finally, we've reached our breakpoint inside the implementation of Print().
- History:
- There's really no comparison here - Lua has been in existence for 20+ years, and there is an endless supply of documentation and searchable anecdotes for problem solving from a huge community.
- My hope is that the ease and speed at which TinScript can become integrated into a system will allow it's runtime iteration, extensibility, and transparency advantages to shine through.