If you're interested in getting involved (or even just learning more about TI-JS), you've come to the right place.
Other resources:
- README.md: project overview
- ROADMAP.md: planned work
If you run into an issue, file it here. Make sure to include tails about what you're trying to do, and why.
Before making pull requests, reach out to the maintainer via email.
Once you've published a pull request, automatic checks will make sure tests pass and linting looks good. If there are any issues, fix them and update your PR.
If you're making updates to the grammar or adding new functionality, make sure to update the ROADMAP.md.
Make sure you have node installed then run:
npm install
You can now then run any of the scripts in package.json. Here's a list of the more useful commands:
Command | Description |
---|---|
npm run bt |
Build and test. Also updates API documentation. Will fail if there are lint errors. |
npm run lint:fix |
Fixes simple lint errors. |
npm run next |
Creates a new version, pushes it to npm, and pushes to github. Will fail if there are lint warnings or unresolved API changes. If this happens, run npm run bt . |
npm start |
Starts up a dev server at http://localhost:9080/playground/ |
npm run cov |
Print test coverage results. |
You're unlikely to use the other commands directly, but here they are:
Command | Description |
---|---|
npm run clean |
Cleans build directory. |
npm run build |
Builds with webpack. |
npm test |
Runs tests. Note that you'll want to build first, so just use npm run bt instead. |
npm run lint |
Print lint warnings and errors. Fails if there lint errors. |
npm run lint:prod |
Same as above, fail even on warnings. |
npm run api:extractor |
Runs the API extractor. |
npm run api:post |
Copy API output to build output. |
npm run api:prod |
Fails if there have been changes to the TypeScript API without corresponding updates to the API file. |
npm run api |
Updates the API file from the TypeScript API. |
npm run bump |
Increments the patch version. |
npm run bt:prod |
Build and test, but fail if there lint warnings or unconfirmed API changes. |
npm run push |
Builds and publishes to NPM. |
npm run ci:github |
Runs prod build for github CI. |
Finally, there are encoding related commands. Again, these shouldn't be needed directly.
Command | Description |
---|---|
encoding:install |
Install the node packages needed for encoding. |
encoding:build |
Build the encoding table. |
encoding:copy |
Copy the encoding table to the ./src/gen folder. |
encoding:bc |
Build and copy. |
encoding:hash:update |
Write the git hash of the encoding directory to ./src/gen. |
encoding:hash:dirty |
Exit with an error code if the hash in ./src/gen has changed. |
encoding:source:dirty |
Exit with an error code if /encoding has changed. |
encoding:dirty |
Exit with an error code if either of the above have changed. |
encoding:dirty:ci |
Exit with an error code if hash has changed (for CI). |
encoding:warn |
Print a warning that the build will be slowed due to encoding build. |
encoding:error |
Print an error message that encoding changes need to be handled, then exit with an error code. |
encoding:run |
If there are encoding changes, run the encoding build. |
encoding:run:prod |
If there are encoding changes, exit with an error code. |
encoding:run:ci |
If there are encoding changes, exit with an error code (for CI). |
Note that unless you're the maintainer, you won't have permissions to push directly to npm or github.
File | Category | Description |
---|---|---|
.github/ | 🔨 repo | CI, github actions |
api/ | 🔎 api | latest API goes here |
docs/ | 📚 docs | hosts the website |
sample/ | 💡 sample | sample projects that use this library |
src/ | 💻 code | the source code |
tests/ | 🔬 tests | the test framework |
web/ | 🔬 tests | dev tools, plus the actual test cases |
.eslintignore | 🧹 lint | files not to lint |
.eslintrc.js | 🧹 lint | lint config and overrides |
.gitignore | 🔨 repo | files not to track |
.taprc | 🔬 tests | code coverage config |
api-extractor.json | 🔎 api | API extraction config |
CONTRIBUTING.md | 📚 docs | how to contribute |
LICENSE | 📚 docs | license information |
package-lock.json | 📦 node | npm lock file |
package.json | 📦 node | node scripts and package dependencies |
README.md | 📚 docs | the readme |
tsconfig.json | 📜 typescript | typescript config and rules |
tsdoc.json | 🔎 api | API extraction integration with typscript |
webpack.config.js | 🏗 webpack | common build config |
webpack.dev.js | 🏗 webpack | dev build config |
webpack.prod.js | 🏗 webpack | prod build config |
While developing, you can use the following dev tools:
Tool | Description |
---|---|
tests | runs unit tests |
playground | debug programs and view the AST |
The prod versions are linked above, but you can get dev builds with npm start
.
Test cases are stored as a JS object in web/js/testCases.js.
You can run them in two ways:
npm run bt
: this will build and run tests using Node-Tap. This is integrated using tests/e2e.js.npm run start
: this will build and run a devserver, which will let you view the test results at http://localhost:9080/tests/. You can view the production build of that site here.
TAP also gives coverage information, which you can view using npm run cov
.
The .taprc defines the minimum code coverage rules.
The library is typescript compatible, which means its types are exported in a ti.d.ts
file.
Normally this means handcrafting that file, but the API Extractor helps automate that.
For example, the Variable
type in types.ts:
/**
* @alpha
*/
export type Variable =
NumberVariable
| StringVariable
| ListVariable
Compiles to this in the ti.api.md:
// @alpha (undocumented)
type Variable = NumberVariable | StringVariable | ListVariable;
Which then becomes this in ti.d.ts
:
/**
* @alpha
*/
declare type Variable = NumberVariable | StringVariable | ListVariable;
Note that we're in prerelease, so everything is @alpha
and undocumented.
See the Commands table for the commands relevant to API extraction. In general, double check that the API isn't updated unintentionally.
This project uses semantic versioning.
Since we're in prerelease, the major version is 0
and there's no changelog.
Releases simply increment the patch version.
The project will move into release once there's at least one interesting use case. This will probably be demonstrating a real program (written for the calculator) running in the browser.
In addition to the library, there are various directories with their own build processes:
Directory | Description |
---|---|
docs/ | hosts the website |
sample/node/ | demos library usage with a node app |
sample/node-ts/ | demos library usage with a node app, using typescript |
sample/web/ | demos library usage with a web app |
sample/web-ts/ | demos library usage with a web app, using typescript |
web/ | website with dev tools |
View their README files to learn more.
Here's an overview of how the library works:
Component | Description |
---|---|
exec | top-level helper |
parser | converts strings to objects that represent the AST |
runtime | sets up device memory and executes the AST |
statement | evaluates individual statements |
iolib | helps manage input and output |
daemon | executes statements in a tight loop |
inject | provides different implementations for web and node |
The library is available for both web apps (client side) and node apps (server side).
However, node and web don't have the same capabilities. For example, in a web app you can use window.postMessage
to write a non-blocking loop, but there's no window
in node.
To solve this, we use dependency injection:
Component | Description |
---|---|
🟪 looper | this defines a Looper interface. |
🟪 inject | has getLooper and setLooper functions |
🟦 looper.web | implements Looper using window . |
🟧 looper.node | implements Looper using Worker . |
🟦 inject.web | calls setLooper with the web implementation |
🟧 inject.node | calls setLooper with the node implementation |
🟦 web.ts | calls init on inject.web , and exports common.ts |
🟧 node.ts | calls init on inject.node , and exports common.ts |
⬜ common.ts | defines the library, including... |
⬜ daemon.ts | calls getLooper on inject |
If you're wondering we this isn't as simple as in if
statement,
the reason is that in some cases these are build time dependencies.
That means we need two separate builds, defined in webpack.config.js:
const web = merge(common, {
target: 'web',
entry: './src/web.ts',
output: {
path: path.resolve(__dirname, 'dist/web'),
filename: 'ti.js',
},
})
const node = merge(common, {
target: 'node',
entry: './src/node.ts',
output: {
path: path.resolve(__dirname, 'dist/node'),
filename: 'ti.js',
},
externals: [nodeExternals()],
})
Incidentially, that's why node can do require('ti-js')
while web has to do require('ti-js/dist/web/ti')
- we can only define one default output path.
Input and output is mostly handled by iolib.ts.
Currently this supports using DOM elements for IO, but not anything like a real calculator. This will need to be revamped in order to support real programs.
TI-BASIC does not use ASCII, of course. Instead, it its own encoding commonly exported to computers in .8XP (or .83P) format.
For example, the For(
token is 4 bytes in ASCII, but only a single byte in TI-BASIC encoding.
To keep things simple, this library only deals with ASCII. This means characters like θ
are represented as &{theta}
.
However, some strings will automatically be converted (for example, ->
will always be interpreted as &{->}
.)
There is not yet a way to opt out.
See the encoding subproject for creating a more strict character mapping.