
UPDATE 2019 I thought it was a shame that this project was left in a state where nothing seemed to work, so I had a go getting it up and running again. Alas, the code is a mess and I can barely remember how it works. I managed to get it to work partially, so the REPL is usable but some examples are broken. OLD NOTES: Hello world, I am making a sort of dynamically typed, interpreted, object oriented programming language in scratch. It is currently incomplete. CURRENTLY AN UNSTABLE DEVELOPMENT VERSION and completely non-functional while I re-jig the call framework, sorry :/ Hold the down arrow key for a few seconds after pressing the green flag to start the interactive terminal. Without holding the key, it will pick one of a few demo scripts to run. Putting up full documentation for the subsystem and the interpreted language will take a long time. ------------------------------------------------------------------------------- Of Note: NEVER make a block definition which uses [broadcast [] and wait] run "without screen refresh". It messes up loops involving reading and writing lists in the receiving script and everything that it calls. It seems lists can only be read or written once every half second. Also, when you rename sprites, the names will not change in the clone [sprite] blocks.
– OUTDATED – __________________________________________ ===================== Usage ====================== ______________ General Syntax: \__________________________________________ 12.34 (takes a value) 'foo' (also "foo" takes a string) escape characters (including '\') with a leading '\') #foo (also # foo takes an identifier; a pointer/reference to an object) $true (also $ true (as with $false) takes a boolean literal) !alphanum (also ! alphanum takes an error state) {foo} (takes a method; a block of code) 2+3*4 (operates left to right, accumulating the value 20) 2+(3*4) (bracket expression evaluated after 2, before addition resulting in the value 24) operators: [+, -, *, /, %, mod, ^, &, |, ==, !=, >, <, >=, <=] % or mod is modulo ^ is raise to the power != works as a boolean XOR operator -(foo) (also not foo takes the additive or boolean inverse of the expression foo) foo, bar (evaluates expression foo then bar, storing previous result in case used as function parameter list. usually the comma can be safely omitted) \ (returns the accumulated value, e.g. 12.34\\ returns 12.34) kil foo (causes an object referred to by the name foo to be deleted) for =, . and ; see Manipulating #root Namespace below _________________________________________________________ ___________________________ Manipulating #root Namespace: \_____________________________ foo=0 (creates public name foo initialised to zero or set existing foo to zero) foo=2*3, (expressions to be evaluated and assigned must be terminated by a comma, close bracket or backslash) bar="bop" (sets bar to string "bop", creating bar if non-existent) del foo (deletes the name foo from the local namespace) foo; (also foo ; takes the value at the name foo if variable, executes foo if method/function, or calls foo if reference to object) foo. (also foo . same as foo; except returns method/function code or object identifiers) :self (reserved name: this object's identifier) :init (reserved name: this object's initialiser script) _________________________________________________________ _____________________________________ Using Functions: (technically #root methods) \___________________ foo={etc} (creates the method foo, just like a variable) foo; (takes the return value from executing foo with no arguments (as previously described)) bar={del bar} (function deletes itself upon execution because it shares the namespace of #root) ?n (also ? n takes the nth argument to the method) double={?1*2\} (create a method by the name double that takes the first given argument and returns twice that) double(foo) (also double foo, (double)foo, etc. takes the value returned by double when passed the result of the expression foo as an argument) Note: double 1+2\ returns 4, double (1+2)\ returns 6, and double double 1 returns 2 as all functions and operations apply left to right. (and double(double) returns the name double because the multiplication operation has no effect on names) func(foo, bar) (passes two arguments) {foo}; (effectively an indented code block foo=bar, foo;; (set foo to the name bar then indirectly call bar) _________________________________________________________ =================== How it Works =================== ________________________(not up to date, todo: forum topic) The Overarching Framework \_________________________________ Call Subsystem: The call subsystem is basically an extension of the broadcast and wait block. The most basic use allows calls (broadcasts) to be directed at a sprite, and requires the global variables, [V pointer] and [B count], and that all involved sprites and clones be uniquely identified either explicitly or by a private variable, [O identifier], and have the private variable, [B depth]. B count should be initialised to 0, indicating no calls are in progress. For the first call, the calling sprite copies a snapshot of the value of [B count] into its [B depth], increments [B count], sets [V pointer] to the id of the sprite it is calling, makes the broadcast (there may be many different 'messages' using this system), and waits for [B count], which it just incremented, to fall back to [B depth], indicating the called script (in the target sprite) is finished. This means that all and only sprites that should be able to receive this particular kind of call ('message') should have a 'when I receive' script for this call. These scripts must check if [V pointer] equals their sprite's id, and if not, stop immediately, if so, it is in the called sprite, and should execute then decrement [B count] when done to allow the calling script to continue. In order for a sprite called in this way to make subsequent calls of the same kind ('message'), the receiving script must only execute if a private variable, [R flag] (which is always set to zero except in clones of the sprite) is 0, clone the sprite then end. The 'when I start as a clone' script must set a private variable, [R flag], which is always zero in the original sprite, to 1, before it can safely make the same call. The clone's receiver script would end since [R flag] equals 1, preventing clones of a sprite from responding to that sprite's calls and cloning themselves. This also allows a sprite to call itself within a call of the same kind. Note that this only allows one kind of call to be routed to clones to facilitate nesting; different kinds of calls or different purposes for cloning could be handled if a private variable was added to indicate to each new clone what kind of call it is handling etc. The call subsystem also provides the global lists, [C args] and [C argtypes], to allow arguments (extra information) to be passed in a call, and global variables, [C returned] and [C retype], to allow a value to be returned from a call. The parameters, [C idonly?], [C origin], and the list, [C address], support a more sophisticated construct which I will call the contextual call system. Contextual Call System: (will complete when I have a demonstration) The contextual call system facilitates hierarchical program structure. It is a protocol by which objects (sprites or clones) who are aware of their parent and child objects call other objects by their relative address, using a series of nested 'Invoke' calls. _________________________________________________________ _________________________ The ROOT static object (#root) \_______________________________ Interactive Programming Interface: When you use ROOT's interactive programming interface to send a script for execution, ROOT, whose private variable, [R flag] is always set to 0, places a single . in the global list, [C address] (impossible to do in a call from the interpreter) to indicate that this is a superuser command, sets a local variable, [tmp] to your script, copies and increments [B count] (see Call Subsystem above), and clones itself. (all the script execution; initialiser, methods, superuser or impromptu (executed from inline code blocks) (impromptu is not yet supported by the interpreter) is done by clones of ROOT for the sake of consistency, even though superuser commands could be executed by the original sprite because they cannot recur) The clone of ROOT first sets its [R flag] to 1, then notifies the user of its creation in the [C log], before tokenising and interpreting your script on behalf of the original ROOT, and finally returning by decrementing [B count]. Initialiser Script: When the green flag is pressed without holding the down arrow key, ROOT empties the [C address] and [C args] global lists then clones itself. The clone responds in the same way as when created upon being called with no arguments and no name (see Invocation below and Contextual Call System above) (demonstrate with the script "self=#root, self;") Tokeniser: The tokeniser tackles the input string one character at a time. it runs through a loop for each one, keeping some state variables representing contextual information accumulated during the single pass of the script, and on the appropriate occasion, appending to the list of tokens. The loop tests for the following in order until one of the conditions is satisfied, whereupon it responds accordingly then moves on to the next character and tests again: 1. check if within string (indicated by state variable) 2. check if within code block (indicated by state variable) 3. detect string or code block leading delimiter [ ', ", or { ] 4. detect functional open bracket (as in f(x) ) 5. detect second character of a two character separating symbol 6. detect single-character separating symbol 7. detect beginning of name or number (i.e. of a lexeme with implicit type) 8. detect first non-numerical character in a name beginning with digits 9. if all else fails, append another character to the accumulating lexeme ~~~ Interpreter: The interpreter begins with a pointer pointed at the first token, and loops repetitively, keeping detailed information about the interpreter's state, and jumping forward a token each time, until an end condition causes [N exit] to be set to 1. A definite loop is not used in order that token handlers can consume multiple tokens in a single cycle. The interpreting clone takes a local copy of any arguments passed to it before executing the script. The following handlers are contained in the loop: empty string: reached the end of the script; exit a token indicating that an immediate value of a particular type follows: operate the following value with the accumulated value a token that indicates declaration of a name (variable) of a particular scope: send ROOT the 'Declare' call with the name and scope as arguments (see Call Subsystem above) ? indicating that a reference to an argument follows: operate the first argument local copy with the accumulated value then delete it, exposing the next argument ~ indicating that the name that follows is to be deleted from ROOT's namespace: send ROOT the 'Delete' call with the name to be deleted as an argument (see Call Subsystem above) ; indicating that [N name] is to be called with no arguments: make a contextual call (see Contextual Call System above) as indicated then operate the result with the accumulated value . same as ; but call with [C idonly?] flag set , separating expressions: append the accumulated value to a list and start a new expression \ indicating a return to the caller (see Call Subsystem above): set the return value to the accumulated value and return, leaving a note in [C log] a binary operator symbol set [N operator] accordingly - this will determine what operation is applied when a value is retrieved = indicating assignment: start a new expression but set [N assigname] to what [N name] was ( indicating the beginning of a sub-expression: push all state information to a stack and start anew ) indicating the end of a sub-expression: reload the lower state from the stack but: if [N funcflag] is 1, contextual-call the lower [N name] passing the upper result list and accumulated value as arguments (see Contextual Call System above) then operate the returned value with the lower accumulated value else operate the upper accumulated value with the lower accumulated value When operating a value with the accumulated value, the following is done: if the operand is a name leave the accumulator alone, set [N name] to the operand if no operator is set and [N assigname] is set make an 'Assign' call to ROOT to assign the operand's value to [N assigname] in ROOT's namespace if no operator or assign name is set, then this is the beginning of an expression simply set the accumulator to the operand ~~~ Invocation: todo Impromptu Execution: todo