npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

interstate-js

v0.0.12

Published

Compiler state manager, tracks variable states and probabilities at compile time - exposing the same kind of power a statically typed langauge would enjoy to your dynamically typed language. This state manager is in theory more powerful than what TypeScri

Downloads

7

Readme

Interstate

Interstate Logo
Compiler state manager (originally written for RapydScript). This state manager is currently designed for RapydScript, and can be used by compilers and linters alike. It's a much more powerful context-tracker for the state of variable declarations and assignments than RapydScript's original context tracking system. The main motivation for this project was to allow RapydScript to match and exceed the power of TypeScript. Interstate can one-up TypeScript's system for the following reasons:

  • Interstate thinks in probabilities rather than exact types, allowing more granular control to developers in terms of inputs.
  • Interstate is designed for a cleaner subset of JavaScript (RapydScript) and hence can be more aggressive about its assumptions.
  • Interstate embraces Pythonic patterns of failing early rather than lax JavaScript patterns TypeScript has to respect.
  • Interstate declutters the language rather than adding more clutter/syntax to it like TypeScript with its C# keywords.

Interstate is to a parser what acceleration is to velocity, effectively a derivative:

| Compiler | Geography | Math | |----------|-----------|------| | Lexer | Position | ƒ | | Parser | Velocity | ƒ' | | Interstate | Acceleration | ƒ'' |

Features of Interstate:

  • Tracks creation and destruction of scopes
  • Auto-generates functions and classes in relevant scope
  • Tracks object inheritance
  • Tracks creation, assignment, and reference of variables, functions, and classes
  • Raises compile-time exceptions when variable, function, or class is used improperly
  • Enforces proper arguments for function calls
  • Enforces proper returns for function calls
  • Allows enforcing a specific subset of permitted types for each variable
  • Resolves return signatures for function calls at compile time
  • Dead-code elimination

Install

npm install interstate-js

Usage

Function Declaration

from interstate import State, Timeline

s = State()                        # create a new state object
s.newScope('function', 'foo')      # start a new function named 'foo'
s.setReturn('Number')              # declare a return statement which returns an object of type Number

# ensure that function takes in Number as first argument and String as second and that all return
# types are String
s.newScope('function', 'bar', {
    inputs: ['Number', 'String'],
    returns: ['String']
})

# terminate current scope, check that return types are met, and create relevant variables
s.endScope()

Class Declaration

s.newScope('class', 'Physicist')   # start declaring a new class
s.setParent('Scientist')           # set parent for the class currently being declared
s.endScope()

# retrieve the timeline for the Physicist class
physicist = s.getTimeline('Physicist')
# use Physicist class to declare an object named 'einstein' of type Physicist
einstein = Timeline('einstein', physicist.addCall({
    inputs: []
}))
# retrieve timeline signature, this is a list of types that this variable has stored
einstein.getSignature()

Function Calls

You could also use onCall to create more advanced type checks (at the time of function call rather than declaration). For example, imagine we had the following code:

def qux(d:Number):
    pass

a = 'foo'
def baz(b:Number, c:Number) -> Number:
    qux(a)
    return b + c

a = 4
baz(1, 2)

For correct qux call we want to ensure that a is a Number at the time of the function call, not at the time of its declaration. We can easily accomplish such a check by populating our state in correct order:

s.newScope('function', 'qux', {
    inputs: ['Number'],
    returns: []
})
s.endScope()
s.setVar('a', 'String')
s.newScope('function', 'baz', {
    inputs: ['Number', 'Number'],
    returns: ['Number']
})

s.onCall(def(callSignature):
		# note that if we use this s.addCall directly at this time, it will fail because 'a' holds a String
    s.addCall('qux', {
        inputs: [s.getTimeline('a')]
    })
)
s.setReturn('Number')
s.endScope()

s.setVar('a', 'Number')             # now it will pass
s.addCall('baz', {                  # this s.addCall will also trigger s.addCall to 'qux'
    inputs: [num, num]
})

Moreover, passing fewer or more arguments than expected to a function call will throw an error. This added safety check allows for much saner behavior than regular JavaScript.

Kwargs Resolution

Interstate can be used to fix function calls with keyword arguments without requiring the run-time kwargs decorator which slows down code significantly, adds a rigidity of not being able to rename variables and makes output code harder to read. Imagine the following example:

def bar(a, b, c):
	...

# enforce proper calls:
bar(c=1, 2, 3)                      # error: Non-keyword argument after keyword argument
bar(1, 2, a=3)                      # error: multiple values for argument 'a'
bar(1, c=2, b=3)                    # ok, but switch 'b' and 'c' in function call

And here is how you would test all 3 cases via Interstate:

s.newScope('function', 'bar')
s.setArgs([{ name: 'a' }, { name: 'b' }, { name: 'c' }])
s.endScope()

s.alignInputs('bar', [
	{ name: 'c', data: 1 },
	{ data: 2 },
	{ data: 3 }
])

s.alignInputs('bar', [
	{ data: 1 },
	{ data: 2 },
	{ name: 'a', data: 3 }
])

s.alignInputs('bar', [
	{ data: 1 },
	{ name: 'c', data: 2 }
	{ name: 'b', data: 3 }
])

Shadowing and Scope Control

The following setup will force the exact same scoping rules as used by RapydScript:

s.newScope('function')
s.setVar('hello', 'String', ['String'])         # in this scope hello can only be string
assert.throws(
    def():                                      # number assignment will fail
        s.setVar('hello', 'Number')
    ,
    /Can't assign value of type/
)
assert.throws(
    def():                                      # can't relax earlier declaration either
        s.setVar('hello', 'Number', ['Number', 'String'])
    ,
    /conflicts with earlier format/
)
s.setVar('hello', 'String')                     # reassignment of same type allowed
# -- nested scope
s.newScope('function')
s.setVar('hello', 'Number')                     # assignment allowed (shadowed variable)
s.endScope()
# -- end nested scope
assert.throws(
    def():                                      # number assignment still fails (shadowing over)
        s.setVar('hello', 'Number')
    ,
    /Can't assign value of type/
)
# -- nested scope #2
s.newScope('function')
s.markNonLocal('hello')                         # this time we disable shadowing
assert.throws(
    def():                                      # number assignment now fails even in nested scope
        s.setVar('hello', 'Number')
    ,
    /Can't assign value of type/
)
s.endScope()
# -- end nested scope
# -- nested scope #3
s.newScope('function')
assert(s.getTimeline('hello').getSignature() == [{type: 'String'}]) # no assignment this time, hence we reference outer scope
s.endScope()
# -- end nested scope
s.endScope()

Note that you aren't restricted to the same scoping rules as RapydScript for your language/compiler if you decide to use Interstate to power it. Simply passing local=False to scope.setVar() will have the scope behave the same way as regular JavaScript.