Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- - Add an `ASSERT0()` macro, for things like `strcmp(3)` that return-0-on-success
- ✓ Test that `Magazine->set()`’s argument, and the return value of `Magazine->get()`’s setter callback, are the
- right `kind`
- ✓ Remove the `bytes` arguments to all the `String` methods; determine length from `'\0'`.
- ✓ Remove concept of extension; no need for `string` to be able to change length, thus, no need to store the
- number of ‘available’ bytes.
- ✓ Implement `String->embody()` to acquire a `string` from the appropriate `magazine`
- ✓ Switch the `magazine` tests to using `String->allocate()`, since it’s not going to attempt to use the uniques
- store, and thus no recursive dependency will be created any more
- ✗ Assemble a reverse mapping of `pthread_t` ID types to `thread` pointers, so we can get the current `thread` via
- `pthread_self()`
- - Doubly-link `pool`s back to the `unit` they belong to
- - Provide a way to get the “current” IU: get current `thread` via Pthreads, get the `pool` that the `thread`
- belongs to, get the IU that that `pool` belongs to
- - Get current IU’s `magazine` uniques-store for `string`s, and use that if the `magazine` passed to
- `String->embody()` is `NULL`
- - Scrap current `routine` and `execution`, and start from scratch.
- - We have plans to make to make this possible… what system, exactly, are we going to set in place for all this?
- Specifics are needed! The changes re: futures-y stuff, `ghost`s and possibly `fork`s and so on… everything’s
- a bit up-in-the-air.
- - Properly test `Thread->allocate()` by wrapping a `pthread_getspecific(3)` call into a native `routine` and
- adding it to our testee-thread’s pool’s work queue
- - Properly test `Thread->work()` by adding a file-static variable, setting it in a native `routine` and adding
- it to our testee-thread’s pool’s work queue
- - Properly test `Thread->current()` by wrapping a call to it into a native `routine` and
- adding it to our testee-thread’s pool’s work queue
- ✓ Figure out how to properly namespace *types*. Structs are safe, but…
- - Internalize `Paws.h` includes
- - Cest ‘Cets’
- - Store `ASSERT()` arguments in a string, if it fails
- ✓ normalize line returns between namespaced functions and struct methods
- ✓ replace `LL` namespace with `ll` where appropriate in the tests
- ✓ move the `ll_size` typedef into ll.h
- ✓ use `#if defined()` instead of `#ifdef`, and `#if !defined()` instead of `#ifndef`
- ✓ collapse `ll a_ll;\na_ll = …` into one statement
- ✓ re-indent preprocessor statements to align to tab stops
- - Replace /*-*/ with // where sensible
- - ERROR! HANDLING!
- ✓ Cest-out all existing code
- - basic numeric methods
- ✓ `infrastructure string` impl
- - memory management
- - routines
- - Some sort of remove function on `ll`s and `list`s
- ✓ Get rid of `ll_ssize` and negative indices entirely… *always* use unsigned indices, and provide a separate
- function for counting from the end
- ✓ separate into various files
- ✓ make `thing` a struct with a pointer-union, so we can tell what is being stored (a ‘tagged union’)
- ✓ make ll a doubly-linked list
- ✓ change `e`, it’s a stupid name
- ✓ fix bus error with new `thing` struct
- - move `run` somewhere persistent
- - add an alias for `gdb`
- - use clang overloading
- ✓ do we need stdlib?
- ✓ add a useless-header warning to Types.h
- ✓ remove useless include-guards in .c files
- ✓ make `thing`s const
- ✓ get rid of the unnecessary ‘_methods’ in struct’s tags
- - look into using an `enum` as the return value of `main()`, for error handling.
- - look into `__attribute__((nonnull))`
- - How do I link an `execution` to the related `routine`?
- ✓ Move all datatypes into a common subdir
- ✗ Remove `Core.h`
- ✗ Move `EXTERNALIZE` def into a more central location (Paws.h?)
- ✓ Move forward declarations of non-datatypes into their respective types
- ✓ Move `ast.*` in with `routine`
- ✓ Move `ll.*` in with `list`
- ✓ Remove all faux-‘inheritance’
- ✓ Simplify the `thing` mess
- ? Use casts instead of `thing`s where possible (almost certainly only use `thing`s as return values of `list`
- functions)
- ? Provide `DEFINE`s to make `a_thing.pointer.list` into `a_thing.list`
- - Provide `AS_LIST()`, `AS_ROUTINE()` etc macros to use on `thing`s
- ✓ Fix back to indented `E()`
- ✓ Re-hardwrap everything at 113 columns
- - Figure out a way to use `constructor`s again, instead of chaining registrations together
- ✓ RE-ASSIGN HEADER INCLUDES!!!
- - Indirect most calls through the libspace lookup chain
- - Lookups
- - `assignation`
- - `tuple`?
- - Synchronous, native routines
- - Native routines
- - Synchronous executions
- ✗ Move pointer marker in Types.h to be right-aligned
- - Make root-level routine a `lobby`
- - Create `allocate` and `liberate` methods for each data type
- ✗ Remove the unnecessary `struct Paws extern Paws;` and add `extern Paws;` to the end of the original struct
- declaration. Do the same for all the other classes that do similar.
- - Externalize all function names
- - Should I be using `realloc()`?
- - Should probably replace `foo--`s with `--foo`s.
- - So much documentation left to do…
- - why are there two naughties created
- immediately?
- - why is `routine._get(1)` undefined?
- - list return value on failed gets/lookups
- - undeclared object (do we need undefined
- object interp-side?)
- - implement a `name` routine, and `namer`
- relationship, for debugging
- - replace errors with functions that
- create errors, so we can get backtraces
- - make most of the native functions
- friendly to being stored on routine
- objects, and then expose them to
- libspace
- - ensure we use lookup’d libspace routine
- calls instead of function calls where
- possible, to allow for more overriding
- - go through and make style more
- consistent
- - make Error strings on the next line, and
- longer
- - change `list.length()` so we can make
- `string.length()`
- - enclosing scopes
- - libspace `beget`
- - remove support for
- `definition.beget({key:value})`, since
- it doesn’t work
- - do we need a third (fourth?) element in
- definitions, for metadata? why not just
- store it on the naughty?
- - what about ‘directionality’ instead of
- push/pop/shift/unshift on lists? Treat
- them as *either* FILO or LILO, and have
- only two methods… so lists are *either*
- a stack or a queue, not both at once.
- Would be good for APIs…
- - If you *really* need to work with the
- wrong end, you could `apply` the
- routine from the opposing prototype…
- - sexy syntax for applying. `foo bar`
- calls your *own* `bar`, but what about
- `baz [qux] apply(foo)` or whatever?
- That’s really ugly for applying somebody
- else’s `qux` to yourself
- - interpreter loop!!!!!!
- - Move lenses into their own files
- - Date lens
- - RegEx lens
- - Function lens
- - Error lens
- - Do not continue codes during indentation
- - Clean up API (`unapplyCodes() and such
- should *not* be public!`)
- - Can we D.R.Y. the CSI, codes.join, SGR
- sequence out into the `[un]applyCodes()`
- functions?
- ✓ Hilight escape codes in strights in a
- ‘hi’ variant
- ✓ Support undefined, null
- ✓ Treat natives differently from wrapper
- objects; support separate lenses for the
- two
- ✓ Perhaps try a `typeof` before climbing
- into prototypes and properties? Then
- we could have an 'undefined',
- 'string', 'number', etc
- ✓ stack ‘seen’ objects, recognize
- recursion
- - track length, intelligently decide
- between printing sub-elements on
- newlines and printing them inline
- - how do we expose this functionality to
- lenses?
- - combine *really* short elements onto
- one line
- - can we `diff` objects? retain old state,
- compare to new state. show changes?
- ✓ how do I recursively peer into objects,
- yet preform different actions on them?
- i.e. if we start with an ANSI terminal
- printing, each recursive object should
- print as ANSI with Yarn… but if we start
- with a toString-ing, each recursive
- object should print *without* ANSI…
- ✓ some sort of way to pass a ‘recurse()’
- or ‘sub()’ function to the lenses,
- that we can then exchange out based on
- what we actually want to *do*?
- ✓ but then how do the lenses know
- whether to use ANSI or not, etc?
- ✓ lenses return yarns or strings,
- yarns will be styled later on
- ✓ use names instead of styles? or names
- as an alternative to styles?
- - a ‘style’ for adding indentation, i.e.
- for printing `Function` objects, or Paws
- ASTs… could define a style of four extra
- spaces
- - can we print `Function` objects more
- intelligently? Less than printing the
- entire sourcecode, more than printing
- the word ‘function.’ Perhaps we can suss
- out arguments and the return value.
- 1 ¦ operator “operand*operand”
- 2 ¦ replace “foo bar” “2*2”
- 3 something { what (foo bar baz) * 8 }
- 2 * (2) ⇥ 1
- 2 ¦ replace “foo bar” “^ ^ ^^^”
- what (foo bar baz) * (8) ⇥ 1
- 3 something { ^^^^ ^^^^^^^^^^^^^ ^ ^^^ }
- 2 * (2) ⇥ 1
- what (^ ^ ^^^ baz) * (8) ⇥ 2
- 3 something { ^^^^ ^^^^^^^^^^^^^ ^ ^^^ }
- - `.foo(a_list)` called
- - needs a value from `a_list`: calls `.lookup(a_list, a_string)`
- - needs lookup routine, calls `.metalookup(a_list)`
- - `.at()` the `0, 1, 2`th entry of `a_list` to get `a_metalookup_routine`
- - `.synchronous_execute(a_metalookup_routine)`
- - Create `a_result`, and wrap in a pointer to a location to which the
- result value should be written (LOCATION)
- - Create `an_execution` from `a_metalookup_routine`
- - Set `an_execution`’s arguments to `a_string`, and caller to `a_result`
- - `.execute(an_execution)`
- - Check if `an_execution->routine` is native or not
- - If native, `an_execution->routine->native(an_execution)`
- ~
- - native metalookup implementation does its stuff, acquires `a_lookup_routine`
- - native impl calls `Routine.execute(an_execution->result)`
- ~
- Notes:
- ======
- - The
- - Character position data never *exists* for intermediate ASTs: all character
- positions refer to the position in the **original document**. This means
- multiple nodes might point at the same character positions in the
- original document (this is by design; for instance, multiple instantiations
- the same preprocessor command.)
- More Paws
- =========
- ### Big TODO
- - Unicode and semanticity
- - Old-school mathematical operators instead of faux ASCII programming ones
- - Infix operators
- - Nothingness (undeclared, undefined, and null)
- - Federationist compilation
- - Co-operative, networked compilation-unit discovery
- - Asynchronicity
- ### Little TODO
- - `is` keymethod (`foo is bar` calls `foo bar?`, tries to return booly)
- - boolyness (things know how to make themselves booly)
- - Whitespace (U+0020 ‘SPACE’ or U+000A ‘LINE FEED’, no Windows support, why?)
- Scope
- -----
- /scope/
- : a /namespace/, connected to knowledge of the enclosing /scope/.
- /namespace/
- : a list of associations, of /name/s to objects.
- /name/
- : any literal series of non-/whitespace/ characters
- ### `scope` function
- A new /scope/ is explicitly introduced with the /`scope`/ function.
- /statement/s inside the /block/ attached to that function dereference /name/s
- using a new /namespace/ attached directly to it.
- /block/
- : any series of /procedural/ /statement/s
- /statement/
- : a
- scope
- a ↼ 1, b ↼
- scope
- a ↼ 2, b ↼ 2, c ↼ 2
- a = 2 & b = 2 & c = 2
- a = 1 & b is undefined & c is undeclared
- ### Lookups
- How we run through `foo ↼ 123`
- - `foo` is dereferenced in `locals namespace`; no such name exists, so the
- `namespace` returns an `undefined`, with `the.undefined scope` attached to
- `locals`, and `the.undefined name` attached to the missing name
- - `↼` is dereferenced in `the.undefined namespace`; it dereferences to a
- function
- - `the.undefined ↼ infix?` is `true`, so we look ahead and deal with `123`
- - `123` is a literal, so we don’t have to do anything with it
- - We call `the.undefined ↼(123)`
- - The implementation of `↼` does something akin to
- `my scope __set(my name, 123)`
- - preform high-level processing to break the document down into routines,
- expressions, and preprocessing directives
- - process the operators in each expression
- ¦ operator “value ? routine : routine” (left, medium) “infrastructure ?:”
- ¦ operator “value−value” (left, medium)
- ¦ operator “value×value” (left, medium)
- 2 − 3 × 2 × 2 − 5
- 2 − (3 × 2 × 2) − 5
- 2 − (8 × (2)) − (5)
- ¦ operator “value↑value” (right, medium)
- 5 ↑ 4 ↑ 3 ↑ 2
- 5 ↑ (4 ↑ (3 ↑ 2))
- 5 ↑ (4 ↑ (3 ↑ (2)))
- ¦ operator “value ? routine : routine” (right, low) “infrastructure ?:”
- ¦ operator “value:value” (right, high)
- something ? then : else.maybe ? foo : bar
- something ? then : (else.maybe ? foo : bar)
- something ? then.maybe ? foo : bar : else
- something ? foo : bar : else
- “Senseless example, because `(bar: baz)` will always evaluate as true… but
- here for testing purposes nonetheless.”;
- foo ? bar : baz ? qux : quux : corge : grault
- foo ? ((bar: baz) ? (qux: quux) : corge) : grault
- - Multiple frameworks in Paws.o:
- - Object system: Provides data types and ‘object’ APIs; handles internal
- interactions between items in the ‘world,’ such as lookups
- - Interpreter: Handles ‘worlds,’ specifically, ‘interpretation units.’
- Comprises the garbage collection system, IU federation/
- networking components, the routine-threading model, and (of
- course) the actual procedural AST interpreter.
- - Parser: Comprises the lexer and parser, and provides APIs to access and
- modify the AST. Handles conversion of a I/O stream to a token
- stream, and subsequently conversion of that token stream to an
- AST. Must provide intermediate stages for the final portion…
- - Preprocessor: Responsible for turning a Paws document (with operators,
- macros, constants, and such) into a µPaws document (sans
- operators, consisting entirely of routine literals and word
- lists)
- Provided binaries:
- - `paws`, a combination wrapper for all of the above stages: takes a full-on
- Paws source file, preprocesses it, and interprets the result.
- - `ppaws` (the ‘Preprocessor for Paws’), a direct interface to the
- preprocessor and parser portions. Takes a full-on Paws source file, and
- spits out a valid µPaws document, with all operators and macros handled out
- - `upaws`, the C interpreter itself, a direct interface to the parser,
- interpreter, and object system. Expects a µPaws document; will not invoke
- the preprocessor, and thus will not properly handle operators or macros.
- Can expect in the future:
- - `lassie`, a REPL-on-steroids… more accurately, an interactive interpreter
- tied to an object system interrogator
- - `cpaws`, a bytecode-compiler to Paws objects
- - `vpaws`, a bytecode-VM for Paws objects
- Preprocessor details:
- - Since there are no comments, nor illegal characters in names, we have to use
- valid names as our preprocessor directives. This has the side effect of
- making our un-preprocessed Paws files valid µPaws files… they simply won’t
- execute as expected (given that there will probably be a lot of weird,
- unexpected lookups.)
- - Most likely, use ‘magic strings,’ since that’s how comments normally work.
- Again, a side effect of making the preprocessor directives accessible from
- the processed µPaws, if a library author really wants to grab them for some
- reason.
- Macro example:
- “=macro ‘FOO BAR’ baz”;
- “ The following will become `something baz something.else`: ”;
- something FOO BAR something.else
- Operators will be defined via preprocessor directives in this format:
- `=operator ‘«identifier»’ «arity» [«associativity»] [«priority»] [«expansion»]`
- - «identifier» is the word or sentence of source code that will be treated as
- an operator
- - «arity» is the number of arguments the operator takes (can be a numeral, or
- a word following the ‘unary,’ ‘binary,’ ‘ternary’ pattern)
- - «associativity» is the direction in which the preprocessor will search for
- words to attach the operator to (can be either ‘left,’ ‘right,’ or ‘none’;
- defaults to ‘left’)
- - «priority» determines how the operator is handled in proximity to other
- operators; can be a positive or negative numeric value, with higher (more
- numerically positive) values being processed earlier (more ‘inner’). Can be
- a numeral; one of ‘low,’ ‘medium,’ ‘high,’ (which correspond to -100, 0,
- and +100, respectively); or one of the previous extremes with a number of
- instances of the word ‘very,’ which indicates an additional power of ten
- (i.e. ‘very very high’ is equivalent to +100e2, or +10,000)
- - «expansion», if present, determines an alternative expression that will be
- used to resolve the operator
- After order/grouping is determined, the preprocessor turns each operator and
- its relative expressions into a single ‘indirected lookup’ (usually, actually,
- a routine call… but not necessarily) expression, depending upon whether an
- expansion was defined for the operator or not.
- If no «expression» was defined for an operator, then an operator-lookup is
- preformed on the first argument to the operator (which is the ‘first’ argument
- is determined by the «associativity»); the rest of the arguments are passed to
- the routine call serially: if `foo` has no «expression» and is left-
- associative, then `bar foo baz` becomes `bar foo (baz)`, i.e. a call to
- `bar`’s `foo` routine, with `baz` as an argument. This means that unary
- operators become argument-less function calls: right-«associativity» unary-
- «arity» `√` turns `√something` into `something √ ()`.
- If an «expression» *is* defined, then it is used in place of that lookup. For
- instance, if our `foo` operator *did* define an «expression», let’s say
- `a b c`, then our `bar foo baz` would be processed into `a b c (~(bar) ,(baz))`
- Operator example:
- “=operator ‘+’ binary left medium”;
- “=operator ‘√’ unary right high mathematics √”;
- “ The following will become `mathematics √ (foo) + (bar)`: ”;
- √foo + bar
- ------------------------------------------------------------------------------
- - multiple preprocessor runs
- - maintain source information for each preprocessor run-through, and final
- interpretation stage
- - file, line number
- - line index of first character of literal
- - nesting level (within routine literals / expressions)
- - file/line number/index of each routine/expression? how?
- Preprocessor declaration initiators:
- ¦ operator
- ::operator (for ASCII compat)
- So, preprocessor runs through; every time it sees a processing instruction, it
- stores that instruction along with the place it found the instruction.
- ¦ operator “∫”, “(”, “)” (ternary, postfix, left, medium) “mathematics ∫”
- “Becomes `mathematics ∫ (~ (4) , (10) , (foo bar))`:”
- 4∫10 (foo bar)
- ¦ operator “?”, “:” (ternary, postfix, left, medium, routine) “infrastructure ?:”
- ¦ operator “value ? routine : routine” (left, medium) “infrastructure ?:”
- “Becomes `infrastructure ?: (~ {@(foo is.awesome)} , {@(then do)} , {@(else do)})`:”
- foo is.awesome ? then do : else do
- (Can we somehow declare *individual arguments* that should be routines instead
- of values? Turning *every argument* into a routine literal is a bit meh…)
- what about routines ‘falling forward?’ We want execution to always proceed in
- a ‘forward’ direction, correct… no return values, means no returning, means no
- falling ‘backwards’ to your call*er*. But blocking calls with a sync
- abstraction pretty much do that ANYWAY, which breaks our own rules…
- … so, instead, we could *completely kill* the calling routine. We already need
- a ‘called routine’ object, that stores the locals for a given execution,
- right? We could also, somehow, store the execution-state (that is, *where* in
- the execution it is); then, we don’t even *need* to maintain old interpreters!
- We just pass the caller ITSELF to the callee AS A RESULT-ROUTINE! Then, when
- the called routine has a value to hand back, it calls-forward to this
- partially-executed routine, with the result.
- The arguments to a partially-executed routine *wouldn’t* be stored in `@` or
- whatever; instead, they would be stored as a result-value of the position
- where the callee was called.
- cloudhead says we can ‘unroll the stack…’ I think I understand…
- also, new possible semantics, libside: a routine can ‘result’ (continue its
- caller) multiple times, with different ‘results.’ Imagine a for-each construct
- that simply results multiple times! Compare:
- routine {
- (1, 1, 2, 3, 5, 8, 13, 21) for.each { I/O standard out write(@) }
- } execute
- routine {
- I/O standard out write((1, 1, 2, 3, 5, 8, 13, 21) each)
- } execute
- - Federated IUs will have to, somehow, ‘share’ lookups?
- - Perhaps use the same system as, uh, everything else: failed lookups
- fall-through to the ‘parent’ environment.
- - What about overriding/assigning stuff? Then again, inheritance has the
- same ‘problem’. So maybe it isn’t really a problem.
- - Lists may have a GULID (Globally Unique List-ID) attached to them. When
- interpretation units federate, they may ‘decide’ (?) that two objects are,
- in fact, the ‘same object’ for the purposes of execution.
- So… ‘remote’ objects will simply have another `lookup()` semantic: before
- following *any other* lookup procedure, they will preform a lookup on their
- ‘local proxy’ (which only remote objects have). This local proxy overrides
- `get()` and `set()` such that those are passed through a tunnel to the
- ‘owning’ IU.
- This should, actually, handle *any* situation where multiple IUs come into
- contact. The only thing that changes is how the tunnel operates:
- - ‘snug’ IUs: same interpreter process (i.e. included files and packages)
- - ‘near’ IUs: separate, but nearby, interpreter processes (same machine, or a
- machine on a very, very fast local subnet, i.e. fast-LAN… such
- that it can be treated as the same machine)
- - ‘far’ IUs: separate, and far away, interpreter processes (different
- machine, different network, possibly different interpreter… i.e.
- server IU on Paws.o, and client IU in a browser on Paws.js)
- These tunnels cover all sorts of IU-interaction scenarios:
- - localized sub-interpretation-units, such as an equivalent to `eval()`
- - ‘file’ interpretation units… such as an equivalent to `require()` or a
- packaging system
- - multiple ‘server’ processes, handling some sort of distributed task
- - client-server IU communication
- -foo
- foo+bar
- |foo|
- foo a bar b
- foo?bar:baz
- /foo/bar/
- foo a bar b baz c
- - ‘look up’ the operator *on* what?
- - what if what you’re looking it up on
- changes at runtime? can’t look it up on
- something new… or, can, but it may not
- be an operator there
- Okay, once it’s a parse-tree static…
- foo ? bar : baz
- [operator, ['?', ':'],
- [identifier, 'foo'],
- [identifier, 'bar'],
- [identifier, 'baz']]
- ? :
- ↙ ↓ ↘
- foo bar baz
- … it would be looked up as `(‘?’,‘:’)`
- on `foo`, regardless of the content of
- `foo`.
- Paws
- ====
- Take all the good bits of JavaScript. Leave out the ideological dependence on
- the DOM, leave out the horrible insecurities regarding the prototyping system,
- leave out the stupid psuedo–types–that–pretend–to–be–objects.
- Now mix in a little Ruby: C extensions (speed when we need it, and the whole
- ridiculous pile of C libraries at our disposal), intelligent handling of
- nested scope and `self`, absolutely every thing in the system behaves properly
- as is expected of a true thing (even if it’s implemented as a non–thing on the
- backend, like Numeric).
- Finally, garnish it with a few oddities from around the world: Io’s lobby
- instead of any sort of global scope, Node.js’s obsession with eventual
- programming and refusal to block for *anything*…
- Wrap it up in a beautiful hybrid syntax, a bastardization of JavaScript’s
- object notation, Io’s whitespace–based expressions, Python’s meaningful
- indentation, and Ruby’s raw sex appeal.
- Welcome to Paws.
- Look & feel
- -----------
- Paws likes to feel pretty. Everybody knows that negative space is pretty.
- a.thing = {widget: "foobar", whatsit: 2}
- a.thing widget = "metasyntactic variable"
- a.thing > another.thing
- print( another.thing widget # metasyntactic variable
- Unlike, well, every other language, we open up names to all sorts of stuff:
- periods, apostrophes, even Unicode characters. Better yet, slots aren’t
- restricted in the very few ways that names are; you can name a slot after
- *any* string (even the empty string, `''`!) Of course, you can only directly
- reference a slot via the space operator if that
- Prototypal inheritance
- ----------------------
- A child of a thing is just an empty thing whose `isa` field points at the thing
- it is a copy of. Missing slots are proxied to the parent thing.
- foo = {} # <foo>
- foo isa # <thing>
- foo thing = 123 # 123
- foo > bar # <bar>
- bar isa # <foo>
- bar thing # 123
- bar thing = 456 # 456
- bar > gaz # <gaz>
- gaz isa # <bar>
- gaz thing # 456
- bar delete thing
- gaz thing # 123
- - Details on references
- - Syntax for resulting & space-operator-wait() (square brackets?)
- - `foo bar baz qux quux corge grault`, if `qux()` calls three callbacks, how
- do we say we want
- - foo(bar: routine, gaz~ routine)[gaz, bar]
- “quux gets anything passed to @last() by qux”
- foo bar baz qux quux corge grault
- “quux gets anything passed to @result() by qux”
- foo bar baz qux(arg, result~ routine, foo: routine) quux corge grault
- “quux gets anything passed to @result() by qux”
- foo bar baz qux(result.one~, result.two~) quux corge graulta
- Thoughts
- ========
- - a ‘suzerain thread’ is launched by the OS when you run the interpreter. A thread becomes the suzerain by
- calling the initial interpreter-initialization function, whatever that will be.
- - The suzerain will be frozen once it spins off the root routine-interpreter thread; it will only be unfrozen
- when a process-relevant task has to be preformed (exiting, or handling signals maybe? dunno).
- - Problematic: Recursive ownership. If the `owner` is a manifold-singleton, like everything else in Paws… then
- it’s not enough to cause exclusive execution for `atomic` `routine`s against the *`owner`*… because said owner
- might simply be a call-literal `list`, implying *multiple* `owner`s. In addition, even if there is a single
- `owner`… we have to worry about `routine`s that try to access ‘child objects’ of that `owner` that are at risk
- for mutation! Compound the two issues, and you have to worry about the entire sector of the object-space
- referenced by a `routine`’s `owner`.
- - Possible: Climb entire object space below a `routine`’s `owner`, and exclude from execution any `routine`
- whose `owner` conflicts at any point. Ew.
- - Possible: declare, libside, which `definition`s you are ‘responsible’ for (it’s like `owner`ship… for things
- instead of `routine`s: if you are `responsible` for one of your children, then it is also atomically locked
- against when you are atomically locked against)
- take a metapointer (hah, fuck you, Zhivago) to an AST node; then update the reference as we iterate
- that way, we can be `execution`-agnostic in all of `routine`’s code, and then leave it up to `execution`’s code
- to handle relationships between the routine and the execution
- I don’t like that we have a *specific type of data* to hold A) a list of stuff, including arguments and caller,
- and B) a pointer into the AST… but then we don’t utilize that as an argument when we need exactly those two
- things. On the other hand, we can’t exactly properly *construct* one of those types using a `routine` without
- getting ridiculously recursive…
- so, *either*…
- - I can call a constructor-type routine after allocating, which makes it more ‘libspace friendly’, *or*
- - pass a semantic `execution` object to `Routine.interpret()`
- - … what if I put it in `Execution.interpret()`? But that seems so fugly/unsemantic *too*…
- Things an `execution` stores:
- - interpretation state, i.e. pointer into the AST representing where to (re)start interpretation
- - the (list of) argument(s) passed in
- - the ‘instantiated’ locals-scope of the routine (falls upwards to the closed-over scopes of the execution in
- which the routine was created, etc)
- - the thing the routine was ‘called on’ (you know, `this`)
- THINGS TO CONSIDER:
- - how the hell do I create a many-to-one relationship in a function call? How do you have *multiple* functions as
- your `caller`?
- - how do you have multiple `this`es? or rather, I suppose, `these`? Perhaps we can allow a lookup on a `call`
- literal to simultaneously fall through to all of its children, and then … execute the result(s?) with multiple
- items in `these`?
- - What about executing an `execution` against multiple routines? How do we store the execution status (node
- pointer) and each
- - In the spirit of ‘single-multiple everything,’ what about multiple scopes? Perhaps, treat a lookup against
- multiple scopes just like any multiple-lookup: pass `these` as the scopes that metalookup identically
- - There’s a lot of places where we’re wrapping multiple things up in one thing, and trying to make it easy to be
- agnostic about whether that thing is a thing, or multiple things. Should we have a centralized concept to
- handle this (really common) situation, other than just the `call` literal?
- - Penetrating lookups; several things (`definition`s, call-literals, `ghost`s and `fork`s) are intended to be
- “transparent”: what about having them be transparent on the request-side, not the receiver-side? That is,
- lookups ‘fall through’ unless A) they are marked to not penetrate that specific kind of thing, or B) there is a
- “backstop” routine on that kind of thing (a lá `definition ↼()`).
- - Can we abstract first-class `fork`s out to being *every* `list`? That sort of turns every object into an “event
- emitter” a lá Node.js: that is, every time an element is added to a list, the places that `fork()`ed against
- that list, get immediately branched with that new element. It’s threaded eventing, on every object in the
- system.
- - So, how would that tie into `routine`s, resulting, `execution`s, `ghost`s? Essentially, at that point, every
- object in the system might as well be a `ghost`: instead of `routine`s returning anything special, we can
- simply grab an empty list from them, that `result`s will be placed into as they become available.
- - Why are numbers globally unique?
- - Why store numbers as strings?
- - How do we normalize numbers into a single string representation, to make them globally unique (i.e. ‘1234567’
- and ‘1.234567E6’ should obviously normalize into the same `numeric`)?
- Problems
- ========
- - There’s some confusion around the interaction of the asynchronicity (`execution`s) and concurrency (ownership)
- systems. Does an atomic `routine`, one that needs exclusive write-access to a thing for a while, retain
- exclusive access to that thing once it’s blocked? (remember, blocking means it’s gone, in our system).
- - If so, isn’t there a possibility of it being access-locked forever? If a blocking call goes out, destroying
- the caller, and the caller has locked some thing… and then the caller is never called back to, then access
- will never be released
- - If not, then while the caller is blocked (gone), other things might access the locked thing, completely
- defeating the purpose of atomicity and ownership (because the thing you’re prefomring multiple,
- interdependant actions on, may be introspected, or even modified, before you’re through… specifically,
- during the call out)
- - This is even a problem with non-blocking routines; specifically, if you exercise an execution situated
- halfway through a routine, things may have modified/accessed half-complete data you owned
- - any staged or queued `execution` implies a state of lock based on ownership
- - if a staged `execution` is unstaged for any reason *other* than having reached the end of the `routine` it’s
- being exercised against, its ownership-mask is perpetuated
- - a perpetuated mask has the exact same effect as a staged routine’s mask; that is to say that a list of
- perpetuated masks is stored somewhere, and checked against in the same way as the staged routines’ masks when
- new routines are to be staged
- - when that unstaged `execution` is exercised *or* forked into a new `execution`, the mask is
- …
- - uh, instead… we seperate the ‘exercise stage’ and ‘lock stage’: lock stage is checked before an execution is
- staged? however, once staged, it is staged to *both*. Unstaging in the process of execution only removes it
- from the exercise stage; remains on lock stage, which in effect locks all the resources the execution *was*
- utilizing as if the execution was still in play
- - what about *forking* executions?
- - could lock-stage all forks of a lock-staged execution
- - conflict eachother?
- - then, when execution is exercised again at a later point, place it back on the exercise queue… and ensure it
- won’t conflict with *itself*
- - wait: executions should expire once they realize the entire routine, anyway. calling them would do nothing once
- their pointers point to the end of the AST9
- …
- - okay, one more stab…
- - screw applying an execution to multiple routines, it complicates things too much!
- - first off, three “periods of life” of an execution:
- 1. pre-staging (has never been staged; points to root scope-node of the routine)
- 2. post-staging (is staged or has previously been staged; points to somewhere within the `routine`)
- 3. invalidated (has completed execution of the `routine`, or has been manually invalidated; points to NULL)
- - so…
- A. once an execution has been staged… until it is completed (invalidated), it must remain in the locktables.
- B. forking a staged or previously-staged execution (that is, any execution in the locktables) must also enter
- the fork-execution into the locktables
- C. forking is meaningless in the context of a pre-staging execution (‘forking’ would just be creating a new
- execution for the same routine) or invalidated execution (forks would also be invalid), so…
- Z. we simply need to put every forked execution into the locktables, and remove them when they individually
- realize
- - This all mixes very badly with atomicity: essentially, it boils down to “storing executions for long periods of
- time also locks resources for long periods of time, preventing atomic access for long periods of time”: you
- won’t be able to *atomically* modify resources which are locked by momios (executions in reefersleep :D)
- - problem isn’t really *difficult* to solve… you simply either avoid locking data with routines which are
- likely to be forked in complex and time-spanning ways, or avoid passing around your execution when you *do*
- need to atomically modify data.
- - If necessary, you can wrap the atomically-modifying bits of your routine in an atomic subroutine, before or
- after the execution-point
- - I have yet to think of a situation where atomic operation’s *and* momio’s code have to be interleaved… but
- such situations *might* exist. I’m not sure how to deal with them.
- so, more …
- Problems
- ========
- - fuck synch routines
- - I mean seriously. that’s just causing endless problems with, well, everything. If we can’t store the C-stack
- behind a given stage’s *actual pthread*… which we certainly can’t easily do… then there’s no way to provide
- an `execution`, for a synch-routine, that can later be exercised again! If you unstaged (ceased) the
- synch execution, then you’d never be able to re-instate the C-stack involving the calling execution below
- the synch execution… gah! And then you’d never be able to, you know, FUCKING RETURN to the caller, which was
- the point of synch routines. And we can’t just dictate that you can’t get a later-exercisable `execution`
- for synch-routines, because that’s the entire spheil behind `ghost`s, right? Calling `ramify()` inside the
- `lookup()` routine?
- - calling complex/asynchronous shit from native routines
- - we *gotta* have some sort of convenience toolset for calling routines from C-side, right? But, without synch-
- routine support, how do we *do* that? Like, lookups. How the fuck do we perform a basic lookup… saying, for
- instance, we wanted to provide an interpreter hook into `get()`: we’d need to provide a native `get()`, but
- then instead of directly *calling* that, we need to preform a lookup for `get` and then call that the full-on
- libside-friendly way, so that `get` can be overridden. In that situation, how do we *perform* that damn
- lookup for `get`?
- - okay, ever since I introduced ghosts/futures/wtfever, I’ve been confused as to which way resulting works:
- - old-world call-the-execution-to-resume-realization, wherein the ISV of the originating call becomes the
- argument to `exercise()`
- - new-world add-results-to-a-`result`-ghost-as-you-realize, where the ISV of the originating call is the
- `ghost` that will be filled with results
- - the old-world way is still useful; obviously, that’s how `ramify()` and similar have to operate
- - the new-world way, to some extent, can be… implemented? in terms of the old-world way?
- - could we dictate that *all* executions work old-world… and then have a libspace routine that *wraps* stuff
- in the new-world way: that is, it `exercise()`s the caller before running your shit, with a new `ghost`…
- and then realizes *your* stuff, after having `exercise()`d the caller, with the `ghost`: at this point,
- from within your own stuff, you utilize some sort of convenience method, `result()`, to add stuff to the
- ghosts
- - this way, we wouldn’t need blocking routines: *all* routines are blocking, until you call the caller’s
- `execution`, which means you can implement non-blocking routines that return a ghost entirely in libspace:
- non-blocking ↼ { this calling execution exercise(routine {
- owner result ↼ infrastructure ghost allocate()
- this calling execution exercise(owner result)
- owner exercise() }) }
- then, from within the routine you wrap as `(a_routine)non-blocking()()` or sth., and the result will be
- something you can call and get a `ghost` from.
- Getting rid of arguments
- ========================
- What about *completely getting rid of* arguments and owners to *routines*?
- At the end of routine execution, there’s … nothing. No return value. That seems “out of balance” with the fact
- that at the *start* of routine execution, we have parameters…
- … in addition, we originally had “multiple arguments and multiple returns” while actually only having a single
- argument and a single return value, and allowing each one to be a list. Now we no longer have returns, instead
- having results… and while each result is singular, with the possibility of having multiple elements added… there
- *are* multiple results throughout the function.
- So, why don’t we sort of go full-on coroutine style, and make the only way to get parameters into the routine be
- via the executions? You could still get traditional “arguments” this way, by having the routine immedtiately call
- its caller (thus giving the caller a handle on an *execution*, which it can then give arguments) or something.
- HOW DO I CONSTRUCT?
- - have to allocate the underlying structure first
- - call a native routine that does libspace `list` configuration *as well as* `node` assignment
- Other ideas:
- ------------
- Multi-use parser framework: break parsing down into stages, each stage can be a specific, restricted, ‘type’
- allowing for for more judicious processing. For instance, one stage might break down a Paws document’s routine-
- structure, while another might parse individual lines into subexpressions, and yet another might break
- subexpressions into words. Each stage would be given callbacks and calls into other stages8
- - package installation for Node.js?
- - packages are just .tar.gz
- - separate systems for ÔinstallationâÕ (really, just sourcing of new packages from a sort of database, repository, or package list; downloading of said packages; and expanding them into a libraryPath) and dependency resolution, outside of the acquire system itself
- - perhaps use same system for the two of the above, so the installation process can preform recursive dependency installation as well
- - gemcutter for Node.js?
- - API?
- - S3?
- - caliper (hosted metric_fu)
Add Comment
Please, Sign In to add comment