0.6.0 • Published 8 months ago

intervoke v0.6.0

Weekly downloads
-
License
ISC
Repository
github
Last release
8 months ago

InterVoke

npm.io

Table of Contents generated with DocToc

InterVoke

Purpose

JavaScript API helper that allows to call methods on objects through properties.

Motivation

I have been using this to build a 'snappy' API for runtime typechecks in JavaScript. Included in this package is a class Word_prompter which splits property names into words (by looking for spaces and underscores), then calls a method producer to retrieve a suitable method for the given phrase, caching results on the way so only first-time accesses need to invoke phrase parsing.

Now, phrase parsing (which is outside of the scope of this package and will depend on one's use case) can allow the type checker to detect that eg.

  • a property empty_list on a type checker isa should determine whether a given argument x (as in isa.empty_list x) satisfies both isa.list x and x.length is 0, or that
  • isa.integer_or_numerical_text x is satisfied when isa.integer x is true or, alternatively, both isa.text x and /[0-9]+/.test x hold.

This can result in very readable APIs, for which InterVoke provides the foundations, namely,

  • providing proxied access to object properties, taking care of all the edge cases
  • providing classes whose instantiations are callable functions (not very difficult but a little tricky)
  • doing property name normalization
  • caching

Name

The name 'InterVoke' is both in line with my /^inter[a-z]+$/ line of packages and, on the other hand, symbolizes quite nicely that this library is all about intercepting method invocations—turning 'invocations' into 'intervocations', in a manner of speaking. Thanks ChatGPT!

Notes

  • all methods and other instance properties whose names starts with a double underscore __ are not proxied and returned directly; this allows users to implement functionality in derived classes while keeping the system's namespace separated from the instances' proxied accessors.

Derived Classes

Word Prompter

  • In itself probably not a very useful class.
  • It serves a base class for Phrase_prompter.
  • All one can do is declaring functions either via the declare class property or the (private) __declare() method.
  • set class property declare to an object with methods that will be __declare()d on initialisation
  • Any access to non-declared properties will cause an error.

Phrase Prompter

  • Intended for new version of InterType; the following notes betray that and are written with the use case of building a runtime type checking library in mind.
  • Phrase_prompter is a derivative of Word_prompter.
  • Like Word_prompter, Phrase_prompter too will split property names into words by splitting on spaces and underscores. Unlike Word_prompter, Phrase_prompter assumes a certain grammar for its accessors, here termed 'sentences' and 'phrases'.
  • Words appearing in accessors are recognized as either
    • nouns like integer, list, text;
    • adjectives like empty, positive;
    • or connectives of and or.
  • The connectives of and or are built-in, no nouns or adjectives are pre-defined.
    • Connectives, notably or, may not be used as words in new names (so it's OK to declare.empty_list or declare.integer_text regardless whether any of empty, list, integer, text are already known or not, but declare.integer_or_text is forbidden because it contains or. Same restriction on use of of may be lifted in the future).
  • Nouns can be added by declare()ing them, as in d.declare.mynoun ... or, equivalently, d.declare 'mynoun', ....
  • Adjectives are either generic or must be declared on the nouns that they can modify (because e.g. empty makes only sense when the noun describes something that can contain values, and negative makes only sense for numbers).
  • Nouns are turned into functions and made properties (of the same name) of their base object (here shown as isa for the sake of exposition); so type integer is accessible as mthod isa.integer().
  • Adjectives are likewise turned into functions, but are made properties of the nouns they are declared on, so if adjective positive is declared for type integer, then its correlated function may be accessed as isa.integer.positive() (as well as by isa.positive_integer()).
  • isa.empty_list x: isa.list.empty x, which implictly starts with isa.list x
  • isa.nonempty_list_of_positive_integer x: isa.list x, then isa.list.nonempty x, then, for each element e, isa.integer_positive e, must hold, that is, ( isa.integer e ) and ( isa.integer.positive e )
  • isa.nonempty_list_or_nonempty_text x: must satisfy ( ( isa.list x ) and ( isa.list.nonempty x ) ) or ( ( isa.text x ) and ( isa.text.nonempty x ) )
  • or has lowest precedence so isa.nonempty_empty_list_or_text x is satisfied even when x is the empty string
  • isa.hinky_dinky_dong x: holds when both isa.dong.hinky x and isa.dong._dinky x hold. The call to isa.dong.hinky x implicitly calls isa.dong x, the call to isa.dong._dinky x skips that test.

Generic and Specific Adjectives

  • Generic adjectives can be used with all types and can not be overridden by a type declaration. They always resolve to the same test for values of all types.
  • There's currently a single generic adjective, optional, which returns true if its argument is null or undefined; if it does return true, evaluation is shortcut at that point (so isa.optional_list null is true although the argument is not a list). In effect, isa.optional_T x behaves like isa.null_or_undefined_or_T x and, equivalently, isa.nothing_or_T x.
  • All other adjectives are specific to their types and can not be used with a type for which they are not declared; thus, isa.positive_list x and isa.empty_integer x produce errors at property access time.

Sentence Structure Diagram

┌──────┐ ┌──┐    ┌──────┐ ┌──────┐    ┌──────┐ ┌──┐
│ adj. │ │n.│    │ adj. │ │ noun │    │ adj. │ │n.│
│      │ │  │    │      │ │      │    │      │ │  │

nonempty_list_of_positive_integers_or_nonempty_text

│(top)      │    │ (elements)    │    │(top)      │
│complement │    │ complement    │    │complement │
└───────────┘    └───────────────┘    │           │
│           │    adjunct         │    │           │
│           └────────────────────┘    │           │
│            disjunct            │    │  disjunct │
└────────────────────────────────┘    └───────────┘
│                    sentence                     │
└─────────────────────────────────────────────────┘
                                   or
         list                                  text
nonempty                              nonempty
              of
                          integers
                 positive
  • A sentence consists of one ore more alternative (noun) phrases.
  • Multiple alternatives can be linked with the connective or.
  • or has least precedence so whatever has been said in the phrase before it has no effect on the phrase that comes after it.
  • A single noun always comes last in a phrase.
  • A noun may be preceded by one or more adjectives.
  • A list of adjectives may start with the special global adjective optional, which indicates that a value of null or undefined will satisfy the condition. Since optional vlaues are essentially 'typeless' in the sense that a null value could stand in for any kind of missing value (much like an empty list satisfies both empty_list_of_strings and empty_list_of_numbers), an optional present in any alternative makes the entire compound optional, so there's no difference between optional_text_or_float, text_or_optional_float, and optional_text_or_optional_float.
  • Sentences that contain one or more undeclared words cause an error.
  • Adjectives that precede a given noun in a phrase must be declared for that noun.
  • A phrase with a noun that is declared to be a collection (a 'collection phrase') may be followed by the connective of and a phrase that describes its elements (an 'element phrase').
  • A phrase that follows an of phrase to which it is connected with an or is understood to describe the 'outer' value, not the element value; this is because or has lowest priority. Therefore, isa.nonempty_list_of_integers_or_text x holds when x is either a list of whole numbers or, alternatively, x is a text, possibly the empty string.
  • To describe alternatives for elements, declare a custom type: declare.frob 'integer_or_text'; isa.list_of_frobs x will hold when all (if any) elements in list x are either integers or texts; this is then equivalent to the longer ( isa.list_of_integers x ) or ( isa.list_of_texts x ).

AST Data Structure

  • An AST is an object with a two properties, alternatives and optional.
  • alternatives is a non-empty list of or clauses ('alternatives'); in case no or was used, the list will hold a single clause.
  • Each clause has
    • a mandatory noun (a string which names the type);
    • an optional list of adjectives (missing where not needed), and
    • an optional elements sub-clause (initiated by the of connective) which in itself is a clause (and may have its own elements sub-clause). Like adjectives, elements will be absent where not needed.
  • optional is true if alternatives has more than one element, and false otherwise.

Note we do not currently support alternatives in elements sub-clauses; if that should be implemented, then the elements property would become a list of alternatives instead of a single clause.

element_clause = {
  noun:         'integer',
  adjectives:   [ 'positive0', ], }

clause = {
  noun:         'list',
  adjectives:   [ 'nonempty', ],
  elements:     element_clause, }

alternatives  = [ clause, ]
ast           = { alternatives, optional: true, }

Glossary

  • Prompter: a class Pr extends Prompter that instantiates pr = new Pr() as a function which allows to be accessed in two ways: classical pr 'acc', p, q, r... or compressed pr.acc p, q, r...
  • Accessor: the key used as first argument to access an attributor as in pr.acc(), sometimes symbolized as acc
  • Phrase: list of 'words'/keys resulting from splitting the accessor by whitespace and underscores. This allows to build complex accessors like isa.text_or_integer 42 (phrase: [ 'text', 'or', 'integer', ]) -->
  • Details: arguments used in a attributor after the accessor. Ex.: In pr.foo_bar 3, 4, 5, foo_bar is the accessor key, [ 'foo', 'bar', ] is the accessor phrase, and 3, 4, 5 are the accessor details.

  • Adjunct: the part(s) of a declaration that come after the noun and the introductory of, as in e.g. list_of_integers. When isa.list_of_integers x is called, it will hold when x is indeed a list; the adjunct, of_integers, will hold if each element of that list, if any, is an integer.

    "An adjunct is an optional, or structurally dispensable, part of a sentence, clause, or phrase that, if removed or discarded, will not structurally affect the remainder of the sentence.It is a modifying form, word, or phrase that depends on another form, word, or phrase ... The adjuncts of a predicate ... provide auxiliary information about the core ... meaning"—Wikipedia

  • Connective: or (and, if it gets implemented, and).

  • Conjunct: "In grammar, a conjunction (abbreviated conj or cnj) is a part of speech that connects words, phrases, or clauses that are called the conjuncts of the conjunctions."—Wikipedia

  • Disjunct: "The conjuncts of the conjunction ‘or’ are called disjuncts¹. They are words or phrases that are connected by ‘or’ and express a choice or an alternative between them. For example, in the sentence “You can have tea or coffee”, tea and coffee are disjuncts of the conjunction ‘or’. // 1. en.wikipedia.org"–Bing AI Chat

  • Complement: The adjectives and nouns of a declaration: "The part after ‘is’ in the sentence ‘x is a list of positive integers’ is called a subject complement. A subject complement is a word or phrase that follows a linking verb (such as ‘is’) and describes or identifies the subject. For example, in the sentence “She is a teacher”, teacher is a subject complement that identifies she. In your sentence, ‘a list of positive integers’ is a subject complement that describes x."—Bing AI Chat

Attribution

To Do

  • docs
  • implement matching property names 'longest first' to allow for overrides that are implementationally simpler than literal translations (eg. in isa.empty_list x, it will be simpler to check first for Array.isArray x, then for x.length is 0 instead of dealing with the different ways that emptiness can be detected in JS (x.length, x.size, ...))
  • clarify terms clause, phrase, adjunct, sentence and so on; also, all of these terms may be applied to strings of underscore-separated words as well as lists of words (so maybe always use phrase_txt vs phrase_lst &c).
  • implement phrase highlighting to be used in error messages \&c; use four colors to distinguish (1) (green💚) tested and OK, (2) (red🍅) tested and not OK, (1) (yellow🍋) not tested, (blue🔵) for structural parts. Example isa.nonempty_list_of_positive_integers [ -4, ] should give 💚nonempty💚_💚list💚_🔵of🔵_🍅positive🍅_💚integers💚 with reverse-colored stretches; isa.optional_list null should give 💚optional💚_🍋list🍋.

Is Done

  • + name generated functions using the NCC
  • + find a good name
  • + In the above example where declare.frob 'integer_or_text' is used to declare a choice type to be used like isa.list_of_frobs x. According to the rules so far, it would indeed be possible to declare.integer_or_text 'integer_or_text', where the tricky part is that this declaration will be the last time that integer_or_text is parsed; subsequent uses will only cause a lookup—which means that using isa.nonempty_list_of_integer_or_text x will mean something different prior to the declaration than it does following the declaration. Solutions:
    • 1) disallow re-using existing names as parts of new names
    • 2) less strictly, mandate use of at least one novel word in new names (a word that is not in itself already a known name) (so could use either_integer_or_text or choose_integer_text)
    • 3) disallow using or (or other connectives, so of) in new names, treating them like PL keywords
    • solution 3) seems reasonable; adjectives + nouns (empty_list) or chains of nouns (integer_text) are not the problem, or is the problem
  • + can we use instance as the cache instead of using a seperate one? Then one could check for target having the property and just return it when found. Maybe use a map or set to simplify lookups.
  • + collect all declarations in the prototype chain
  • + do not return instances-as-functions as it is a useless complication
  • + do not use phrase normalization as it is expendible. If anything, one could later support a method to translate 'natural texts' like 'validate that x is an integer or a text of digits' into very similar API calls like validate.integer_or_text_of_digits x
0.3.0

8 months ago

0.2.0

8 months ago

0.5.0

8 months ago

0.4.0

8 months ago

0.6.0

8 months ago

0.1.0

1 year ago

0.0.1

1 year ago