UI Components that power the pyret ide
This library has not yet been published to npm, so you must install it from a github branch. You can do this with:
npm install --save pcardune/pyret-ide#master-builds
Then usage is as follows:
import PyretIDE from 'pyret-ide';
import yourRuntimeApiLoader from './yourRuntimeApiLoader';
PyretIDE.init({
debug: true,
rootEl: document.getElementById('pyret-ide-root-element'),
runtimeApiLoader: yourRuntimeApiLoader
});
PyretIDE can work with any runtime that conforms to a specific api. The api
consists of three asynchronous functions, parse
, compile
, and execute
that
end up getting asynchrounously composed in PyretIDE's internals. The workflow
looks something like this:
User PyretIDE Parser Compiler Executor
|
| click run
|------------->
|
| parse(src)
|---------------->
|
<------ast-------|
|
| compile(ast)
|------------------------------->
|
|
<-----------bytecode-------------|
|
| execute(bytecode)
|----------------------------------------------->
|
|
<----------------result-------------------------|
|
display |
<-------------|
| result
|
While the parser, compiler, and executor are doing their work asynchronously, PyretIDE shows an appropriate loading indicator to the user. Once each step of the parse/compile/execute process has finished, PyretIDE calls the next step, passing in the result of the last step. By keeping track of the intermediate results of the parse/compile/execute process, PyretIDE has the opportunity to expose these results to the user (e.g. # of tokens that were parsed).
Since the parse
, compile
, and execute
functions might need to do some
asynchronous work on a server or in a web worker, they must return Promise
objects so that PyretIDE
can figure out when they have finished.
Thus, to hook your runtime up to PyretIDE, you must have an object with the following functions:
parse(src)
- the parse function should take a src string parameter that contains the source code of the program to be compiled/executed. Since parsing may take place asychronously (e.g. via a web service or web worker), theparse
function should return aPromise
object that resolves to the abstract syntax tree expected by the compiler. In practice, PyretIDE doesn't actually do anything with the AST that gets returned except pass it right into thecompile()
function, so all you really have to return fromparse()
is some kind of a handle that the compiler can use to look up what it's supposed to be compiling.compile(ast)
- the compile function should take an AST (or a handle for retrieving the AST) and asynchronously compile it into bytecode (or a handle for retrieving the bytecode). Again, just like theparse
function, it should return aPromise
object that resolves to the bytecode.execute(bytecode, stdout, stderr, onResult)
- the execute function should take bytecode (or a handle for retrieving the bytecode) and asychronously execute the bytecode. Again, just like the previous two functions, this should return aPromise
object that resolves to the result of execution. The execute function can optionally take three other functions for flushing information during execution corresponding to stdout, stderr, and any other "result" objects you want. These will get rendered in the REPL window.
For an example of what a (somewhat contrived) runtime api implementation might
look like, see the
stubCompiler
that comes with pyret-ide.
pyret-ide can handle errors generated by the runtime, whether they are
user-caused errors (invalid syntax, etc.) or unexpected compilation errors. The
runtime api should expose these errors by calling reject()
with an instance of
PyretIDE.UserError
for user-caused errors or an instance of Error
for
unexpected exceptions thrown by the runtime itself. For example:
const RuntimeApi = {
parse(src) {
return new Promise((resolve, reject) => {
if (canParse(src)) {
try {
generateAST(src)
} catch (e) {
reject(e);
}
} else {
reject(new PyretIDE.UserError("sorry, but I can't parse the provided src"));
}
});
}
}
Since runtimes can often be large and unwieldly, PyretIDE does not require the
runtime to actually be available before PyretIDE gets initialized. As such, in
order to tell PyretIDE when the runtime is available for use, you must provide an
asynchronous loader function that returns a Promise
object which in turn
resolves to the loaded runtime. For example, it might look like this:
function runtimeApiLoader() {
const RUNTIME_URL = '/path/to/my/runtime.js';
return new Promise((resolve, reject) => {
fetch(RUNTIME_URL).then(response => {
if (response.ok) {
var myRuntime = eval(response.responseText);
resolve(myRuntime);
} else {
reject(new Error("failed to load runtime. response code:", response.status));
}
});
});
}
To connect your webapp to Firebase for authentication, create a project at https://console.firebase.google.com/. Then select 'Add Firebase to your web app', and add the apiKey, authDomain, and databaseURL fields to a .env file in your root folder, following './env.example' as the template. Now you are connected to Firebase!
Automated tests can be run using this command:
npm test
You can also make tests re-run automatically every time you change a file by running:
npm run test-watch
To use React Storybook:
npm run storybook
Then open a browser window to localhost:9001
To add to react storybooks you need to:
-
Write the stories in the
src/components/stories/
directory -
Add that story's filename to the the config file located in the
.storybook
directory
To learn how to write stories refer to this guide