Creating a scripting language: exercising the freedom to reconsider

Over this past weekend, I realized that there’s room for a few small alterations to the language that will greatly increase its flexibility in non-standard usage scenarios, without needlessly cluttering the grammar.

Local identifiers will be split into two unified types, extending the functioncall parameter logic: any generic variable assignment, like x = 5;, will be assumed to target a context-specific scope (node, function), with the values being dereferenced when the context ends. An assignment may explicitly target the global namespace by prefixing it with the keyword global, as in global x = 5;. When unqualified references are provided, like g += x;, the resolution order will be to first consult the local variable-set, then the global variable-set, if noting was found locally; the local variable-set may be bypassed by qualifying either identifier with the global keyword, like g += (global x); (note that the parentheses are just for legibility’s sake (they’re optional), though this is legal only for expressions, not for assignment-targets). For the precise, there’s also a local qualifier, which does the opposite, improving clarity in cases where variable names are reused and allowing the global table to be ignored when resolving values, which may be good for quick maintenance jobs. Details: r16, r17.

Local sequences will be extended with a series of object-like extensions, using the functioncall syntax (so no grammar changes are necessary):

  • .push(value) adds the value to the end of the sequence
  • .push_front(value) adds the value to the start of the sequence
  • .insert(int, value) inserts the given value at the specified index, pushing all other values back; bounds-exceptions will cause the sequence to grow the necessary number of None references to fill the gap, much like JavaScript’s sparse arrays
  • .pop():value removes and returns the value to the end of the sequence, granting stack-like functionality
  • .pop_front():value removes and returns the value at the start of the sequence, granting queue-like functionality
  • .remove(int):value removes and returns the value at the specified index, granting list-like functionality; bounds-exceptions will return None
  • .count():int provides the number of items in the sequence
  • .get(int):value gets the value at the 0-based index, providing array-like functionality; bounds-exceptions will return None
  • .set(int, value) sets the value at the 0-based index, providing array-like functionality; bounds-exceptions will cause the sequence to grow the necessary number of None references to fill the gap, much like JavaScript’s sparse arrays
  • .find(value):int finds the first occurrence of the given value, testing for equivalence, but using reference (implementation-dependent) as a fallback, for complex objects; None is returned if no match is found
  • .rfind(value):int the same thing as .find(value):int, but it returns the last instance
  • .union(sequence) adds all values in the given sequence to the working sequence, returning the result as a new sequence
  • .copy() performs a shallow copy of the sequence, returning a new sequence; this is preferred over performing a union with an empty sequence for clarity’s sake

The role of the parser will be altered slightly, since set-based node/function operations can be done in the PLY layer quite easily, removing a step that doesn’t have any specific need to be handled externally, thereby increasing clarity and removing a point of debugging tedium for maintenance programmers. Details: r15.

Each operation will be able to set an ‘error’ flag-pair, __error_code:int and __error_msg:string, if something goes wrong during execution. This should be cleaner than relying on a bunch of arbitrary variable-assignment patterns, though those patterns will still need to be defined, since programmers will need to know what to expect. Details: r13, r14.

Leave a Reply

Your email address will not be published. Required fields are marked *