- Overview
- Installation
- Getting started
- Module: graviton
- Module: graviton.grut
- JavaScript FFI and JSiSE (JavaScript in S-Expression)
Graviton is a library to provide Web-based UI for a standalone Gauche program. You can make UI with HTML, CSS, and JavaScript and integrate the UI into your program.
Graviton also provides JavaScript FFI with S-expression syntax so that you can embed JavaScript code naturally in your program.
You need a modern web browser to access Graviton's Web-based UI. However, Graviton provides an Electron-based client (graviton-player) to interact with the UI. Your program can act as a native GUI application using graviton-player.
CAVEAT: Graviton is a kind of "Web application framework." However, it is designed for one or a few clients. Graviton has no particular restrictions on the number of clients, but it may not be efficient when many clients use it.
You need the latest version of Gauche and Gauche-makiki.
$ ./configure
$ make
$ make install
If you want to install graviton-player, the latest node and npm are required.
$ make build-player
$ make install-player
If you use WSL, the configure script sets graviton-player architecture to win32-x64. You need to install Wine to build the windows binary on WSL.
If you want to use linux-x64 graviton-player with WSLg, you need to specify "--with-wsl=" (no args to this option) for the configure script (./configure --with-wsl=
).
This simple program opens the "Hello, world" window. This window is closed if you press any keys.
(use graviton)
(use text.html-lite)
(define (main args)
(with-window (grv-window :body (html:body "Hello,world"))
()
;; Waits for a keyup event.
(jsevent-await window "keyup" ())
;; Closes this window if possible.
(close-window)))
grv-window
defines a window UI with HTML. with-window
opens the specified window (or just waits for a request from the web browser if you don't install graviton-player) and invokes a code after the window is opened.
Here is an example of calling JavaScript.
(use graviton)
(use text.html-lite)
(define (main args)
(with-window (grv-window :body (html:body (html:div :id "div-block")))
()
;; Sets "Hello, world" in the div element.
(let1 div (document'get-element-by-id "div-block")
(set! (~ div'inner-text) "Hello, world"))
;; Waits for a keyup event.
(jsevent-await window "keyup" ())
;; Closes this window if possible.
(close-window)))
Graviton provides <jsobject>
class, a proxy to a JavaScript object. You can call a method of the object with (jsobject 'method args ...)
and can access a property using Gauche's slot accessor (e.g. (slot-ref jsobject 'property)
).
window
and document
in Scheme are pre-defined global objects representing window
and document
in JavaScript.
NOTE:
You may feel weird about the usage of document
in the example code. This program can accept multiple requests, so actual JavaScript document
objects must be different. In fact, window
and document
are not <jsobject>
. They are <jsobject-provier>
which can return <jsobject>
in run-time. The returned objects are different in the different connections. You can use <jsobject-provider>
on behalf of <jsobject>
.
Here is an example embedding JavaScript code.
(use graviton)
(use text.html-lite)
(define (main args)
(with-window (grv-window :body (html:body (html:div :id "div-block")))
()
;; Sets "Hello, world" in the div element.
(jslet ((text "Hello, world"))
(let1 div (document.getElementById "div-block")
(set! div.innerText text)))
;; Waits for a keyup event.
(jsevent-await window "keyup" ())
;; Closes this window if possible.
(close-window)))
This code is equivalent to the previous example. The difference is that JavaScript code sets the text "Hello, world". You can embed a JavaScript code with (jslet (form ...) body ...)
macro. The body is JavaScript in S-expression. It is translated to an actual JavaScript in compile-time and sent to the client when it accesses this page.
If you want to return values from JavaScript, you can use (jslet/await (form ...) body ...)
macro.
(use graviton)
(use text.html-lite)
(define (main args)
(with-window (grv-window :body (html:body (html:div :id "div-block")))
()
;; Sets "Hello, world" in the div element and prints the body in HTML.
(print (jslet/await ((text "Hello, world"))
(let1 div (document.getElementById "div-block")
(set! div.innerText text)
(respond document.body.innerHTML))))
;; Waits for a keyup event.
(jsevent-await window "keyup" ())
;; Closes this window if possible.
(close-window)))
Calling JavaScript is asynchronous, so jslet/await
doesn't block a thread. jslet/await
creates a continuation, and the continuation is called after the JavaScript code returns values. Until then, the thread can do other things. For example, there is an event handler in this thread. The handler can run in this thread before the values return.
There are several code examples under examples/
of this repository. They might be helpful to understand Graviton.
(grv-config :key host protocol client port access-log error-log iframe-window?)
-
Configures Graviton.
host
- Hostname of this server. The default is "localhost".
protocol
- Protocol ("http" or "https") to connect this server. The default is "http".
mode
-
Operation mode of Graviton.
'browser
- The program opens a main window with Web browser automatically, and it exits if the window is closed. graviton uses the system default Web browser. If you set BROWSER environment variable, the browser is used instead of the system default.
'player
- The program opens a main window with graviton-player automatically, and it exits if the window is closed.
'server
- The program waits for the client requests, and can accept requests from multiple clients.
#f
(default)-
'player
if graviton-player is installed. Otherwise,'browser
.
port
-
Port number to receive requests. If you specify 0, a free port will be assigned automatically.
The default is 0 if the client type is
'player
, 8080 if the client type is'browser
. access-log
error-log
-
The destination of log.
#f
(no log),#t
(stdout),string
(filename) or<log-drain>
object. The default is#f
. iframe-window?
-
Whether iframe is used or not to open a new window in the program.
#t
uses iframe, so the new window is opened inside the main window.#f
useswindow.open
, so the new window opens independently of the main window. The default is#f
if the client type is'player
, #t if the client type is'browser
.
(grv-config-parameter name)
-
Returns the current value of the setting. name must be one of these symbols,
port
,host
,protocol
,client
,access-log
,error-log
oriframe-window?
. (client-is-player)
-
Returns
#t
if the current client is graviton-player. (client-is-browser)
-
Returns
#t
if the current client is Web browser.
When Graviton receives a request from the client, it creates a new window instance and assigns a thread (called "worker") to the window instance. The code running in the worker can operate the associated window only.
(grv-window :key title css js head body width height resizable? show?)
-
Returns a window.
title
- The title of this window.
css
-
CSS used in this window. You can specify the filename or SxCSS. If you want to use multiple css, you can pass them by repeated
:css
keyword parameters, like(grv-window :css "first.css" :css "second.css")
. js
-
JavaScript filename used in this window. If you want to load multiple JavaScript files, you can pass them by repeated
:js
keyword parameters. head
- <head> element of this window.
body
- The contents in <body> element of this window.
width
- The width of this window.
height
- The height of this window.
resizable?
- Whether this window is resizable or not.
show?
-
Whether this window is visible or not. The default is
#t
. This option works only for graviton-player.
(with-window window (elements ...) body ...)
-
Opens window, then execute body ... in a newly created worker.
elements ... are IDs of HTML elements, which will be bound to the same names of local variables before the execution.
#f
will be bound to the variable if the element is not found. (grv-title)
- Returns the title of the current window.
(grv-title-set! title)
- Updates the title of the current widnow.
(close-window)
- Closes the current window, and terminates workers related to the window.
Graviton provides <window-parameter>
to hold values per window instance. One <window-parameter>
object can hold each window instance's value, so you can define it as a global variable and refers to it from each window.
(make-window-parameter value ...)
-
Creates
<window-parameter>
object with the initial values value .... (make-window-parameter* thunk)
-
Creates
<window-parameter>
object, but the initialization is delayed until a window instance is created. When a new window instance is created, the<window-parameter>
object will be initialized with the return values of thunk. (window-parameter-atomic-ref window-context window-parameter proc)
- Calls proc with the current values of window-parameter in window-context, while locking window-parameter.
(window-parameter-atomic-update! window-context window-parameter proc)
- Calls proc with the current values of window-parameter in window-context while locking window-parameter, and updates the values in window-parameter by the returned values from proc.
(window-parameter-ref window-context window-parameter)
- Returns the current values in window-parameter.
(window-parameter-set! window-context window-parameter vals...)
- Sets vals... to window-parameter in window-context.
(object-apply (window-parameter <window-parameter>) value ...)
-
Returns the values in window-parameter if no value ... are specified. Otherwise, updates the values in window-parameter with value ....
(setter object-apply)
is also defined, so you can use it in generalized set!.
(query-parameters)
- Returns query parameters of the current request.
(user-agent)
- Returns User-Agent of the current request.
(bind-url-path url-path file-path)
(bind-url-path url-path proc)
-
Binds file-path to url-path, so that the file is accessible with the URL.
You can also bind a procedure proc to url-path. proc is called when the URL is requested, and returns the result of proc as the response. proc must returns a body and the content-type.
(file->url filename :optional content-type)
-
Allocates an URL for the specified filename. If the URL is already allocated for the file, the same URL returns.
If content-type is omitted, it will be estimated from filename.
(data->url data content-type)
- Allocates an URL for the data.
(json->url json)
- Allocates an URL for the json. json must be a list or a vector.
(sxml->url sxml)
- Allocates an URL for the sxml.
(autoload-css css-path ...)
- Specifies CSS paths css-path ... to be loaded in all window instances.
<jsobject>
represents a JavaScript object. jslet/await
can return the object if the returned value is a non-basic JavaScript object. You can call the object's method and access the object's property with it. You can also pass the object to JavaScript world with jslet
again.
Graviton defines several JavaScript classes (see below for the pre-defined class list). You can use "kebab-case" symbol as method and property name for the pre-defined classes.
For example, you can call document.getElementById(...)
using (document'get-element-by-id ...)
and refer document.innerHTML
using (slot-ref document 'inner-html)
.
(object-apply (jsobj <jsobject>) (method <symbol>) arg ...)
- Calls method of jsobj. method is a kebab-case symbol.
(object-apply (jsobj <jsobject>) (method <string>) [:result] arg ...)
-
Calls method of jsobj, but method is the original JavaScript method name. You can use this style for a non-predefined JavaScript class's method call. If you want to get the result of the method, you need to set
:result
keyword. (ref (jsobj <jsobject>) (property <symbol>))
((setter ref) (jsobj <jsobject>) (property <symbol>) value)
-
Same as
(slot-ref jsobj property)
and(slot-set! jsobj property value)
. property is a kebab-case symbol. (ref (jsobj <jsobject>) (property-name <string>))
((setter ref) (jsobj <jsobject>) (property-name <string>) value)
-
Refers the object property with the original JavaScript property name. You can use them for an object of non-predefined JavaScript classes. For example, you can access
foo.barBaz
with(ref foo "barBaz")
. window
refers towindow
in JavaScript.document
refers todocument
in JavaScript.audio-context
refers to an instance ofAudioContext
. This object is automatically created by Graviton. You can use it for WebAudio API.<event-target>
forEventTarget
<node>
forNode
<node-list>
forNodeList
<css-style-declaration>
forCSSStyleDeclaration
<element>
forElement
<html-element>
forHTMLElement
<document>
forDocument
<html-body-element>
forHTMLBodyElement
<html-image-element>
forHTMLImageElement
<window>
forWindow
<screen>
forScreen
<blob>
forBlob
<html-media-element>
forHTMLMediaElement
<html-audio-element>
forHTMLAudioElement
<html-video-element>
forHTMLVideoElement
<event>
forEvent
<html-canvas-element>
forHTMLCanvasElement
<canvas-rendering-context>
forCanvasRenderingContext
<image-data>
forImageData
<canvas-gradient>
forCanvasGradient
<canvas-pattern>
forCanvasPattern
<text-metrics>
forTextMetrics
<dom-matrix>
forDOMMatrix
<base-audio-context>
forBaseAudioContext
<audio-context>
forAudioContext
<offline-audio-context>
forOfflineAudioContext
<audio-node>
forAudioNode
<analyser-node>
forAnalyserNode
<audio-buffer>
forAudioBuffer
<audio-scheduled-source-node>
forAudioScheduledSourceNode
<audio-buffer-source-node>
forAudioBufferSourceNode
<audio-destination-node>
forAudioDestinationNode
<audio-listener>
forAudioListener
<audio-param>
forAudioParam
<biquad-filter-node>
forBiquadFilterNode
<channel-merger-node>
forChannelMergerNode
<channel-splitter-node>
forChannelSplitterNode
<constant-source-node>
forConstantSourceNode
<convolver-node>
forConvolverNode
<delay-node>
forDelayNode
<dynamic-compressor-node>
forDynamicCompressorNode
<gain-node>
forGainNode
<iir-filter-node>
forIIRFilterNode
<media-element-audio-source-node>
forMediaElementAudioSourceNode
<media-stream-audio-destination-node>
forMediaStreamAudioDestinationNode
<media-stream-audio-source-node>
forMediaStreamAudioSourceNode
<offline-audio-completion-event>
forOfflineAudioCompletionEvent
<oscillator-node>
forOscillatorNode
<panner-node>
forPannerNode
<periodic-wave>
forPeriodicWave
<stereo-panner-node>
forStereoPannerNode
<wave-shaper-node>
forWaveShaperNode
(f32vector->dom-matrix f32vec)
-
Converts
<f32vector>
to<dom-matrix>
. (f64vector->dom-matrix f64vec)
-
Converts
<f64vector>
to<dom-matrix>
. (dom-matrix-copy dom-matrix)
-
Copies
<dom-matrix>
object. (image-data-update! image-data data)
-
Updates the data of image-data with data. image-data is a instance of
<image-data>
, and data must be<u8vector>
.This function is identical to
imageData.data.set(data)
in JavaScript. Writing this property-method chain is wordy in the current Graviton JavaScript object syntax. It is provided for your convenience. (jsevent-callback-set! (jsobj <jsobject>) event-type prop-specs proc :key (use-capture? #f))
-
Sets a listener proc to event-type of jsobj. You can specify a list of properties with prop-specs. proc will be called with the values of the properties, so that no additional JavaScript FFI request/response are needed.
proc-specs must be a list of property names. For example, '("code" "key") means event.code and event.key. These values are passed to proc. You can also use a property chain, like '("relatedTarget.id"), an index reference, like '("results[0]"), or their combination.
use-capture? is the same to
EventTarget.addEventListener
. If you want to capture the event in bubbling phase, sets it to#t
. (jsevent-callback-delete! (jsobj <jsobject>) event-type :key (use-capture? #f))
- Deletes the listener of the specified event.
(on-jsevent jsobj event-type (arg ...) body ...)
(on-jsevent jsobj (event-type :use-capture? use-capture?) (arg ...) body ...)
-
This is a convenient macro to set an event listener. When an event is triggered, the properties of the event are bound to arg ..., then body ... are executed.
The property of the event will be bound to corresponding arg. For example,
event.key
will be bound to arg if arg iskey
. You can also use a kebab-case symbol likerelated-target
.event.relatedTarget
will be bound in this case. If you want to specify the property name explicitly, use(arg "eventName")
style. (request-animation-frame-callback! proc)
-
Registers proc as a callback before repaint. proc takes one argument, the current time in millisecond.
This function is equivalent to
window.requestAnimationFrame(proc)
. (cancel-animation-frame-callback! proc)
- Removes proc from repaint callback.
(on-animation-frame (arg) body ...)
(on-animation-frame :priority priority (arg) body ...)
- This is a convenient macro to register an animation frame callback.
(jsevent-await jsobj event-type prop-specs :key use-capture?)
-
Waits for event-type of jsobj, and returns the properties of the event, which are specified by prop-specs.
If prop-specs is
'()
, returns#<undef>
. (make-worker thunk :key name size)
-
Makes new worker. thunk will run when the worker starts.
You can specify these keyword parameters.
name
- The name of the worker.
size
- The number of threads. The default is 1.
(worker-run worker)
- Executes worker.
(grv-worker [:name name] [:size size] body ...)
-
This is a handy macro to make and run new worker. The keyword parameters are the same as
make-worker
. (current-worker)
- Returns the current worker in which the current code runs.
(main-worker)
- Returns the main worker.
(worker-close worker)
- Stops receiving messages of worker. Pending messages will be processed.
(worker-shutdown worker)
- Stops receiving messages of worker, and discards pending messages.
(worker-active? worker)
-
Returns whether worker can receive a message or not.
(worker-active? worker)
returns#f
after(worker-close worker)
is called. (current-priority)
- Returns the current priority of the current running code.
(add-message-handler! message proc :key priority)
-
Registers a message handler proc for message.
The received messages will be processed from higher priority. You can specify priority of this event. priority must be one of
'low
,'default
or'high
. (delete-message-handler! message)
- Removes a message handler for message.
(define-message message (arg ...) [:priority priority] body ...)
-
This is a handy macro to define a message handler, same as
add-message-handler!
. (object-apply (worker <grv-worker>) message arg ...)
-
Sends message to worker, and returns
<grv-promise>
object which will hold the results of the message handler. You extract the results with(await promise)
. (asleep time)
(asleep sec)
- Suspends the current running code until the time time is reached or the number of seconds sec elapses.
(when-time-passed sec body ...)
-
Executes body ... when the number of seconds sec passed after the last execution.
For example, body ... will run every 0.1 second in this case.
(while #t (when-time-passed 0.1 body ...))
(await promise)
- Waits until promise has values, then returns the values.
(disable-async-wait)
(disable-async-wait bool)
-
disable-async-wait
is a parameter to control whetherawait
can yield the right of execution to other code or not. If this parameter is#t
,await
blocks the current thread until values are set in<grv-promise>
.The default is
#f
(can yield, don't block), and you don't need to change the parameter. However, you may change the parameter to#t
if someone calls(reset ...)
. Graviton's asynchronous mechanism is built on a partial continuation in worker, but another partial contitnuation can break the asynchronous mechanism (for example, implicit delimited contiations byScm_Eval
). (make-channel)
- Makes a channel.
(channel-send channel value ...)
- Adds value ... to channel.
(channel-recv channel :optional fallback)
-
Retrieves a value from channel.
If no values exist in it, waits until a value is added or fallback is returned.
If channel is closed, returns
<eof-object>
. (channel-close channel)
- Closes channel.
(channel-closed? channel)
- Returns whether channel is closed or not.
(concurrent body ...)
-
Executes body ... in concurrent. It means the body will be executed in the current worker after the current code yields the execution.
This macro returns
<grv-promise>
, so that you can get the results of body ... from it.NOTE: body ... may be executed in parallel if the current worker has multiple threads.
(concurrent/await body ...)
-
It is identical to
(await (concurrent body ...))
. (parallel body ...)
-
Executes body ... in parallel. It means the body is executed in newly created worker.
This macro returns
<grv-promise>
, so that you can get the results of body ... from it. (parallel/await body ...)
-
It is identical to
(await (parallel body ...)
. (grv-repl :optional reader evaluator printer prompter repl-group)
- Starts read-eval-print loop. You must start this REPL in a worker. If multiple REPLs are started, one of them is active in a REPL group. If repl-group is omitted, the default REPL group, which is globally defined, will be used.
(make-repl-group)
- Makes a new REPL group.
(next-repl :optional repl-group)
- Activates the next REPL in repl-group. If repl-group is omitted, the current REPL's group will be used.
(list-repl :optional repl-group)
- Returns a list of REPL in repl-group. If repl-group is omitted, the current REPL's group will be used.
(select-repl repl)
- Activates the specified REPL. The repl must be in a group of the current REPL.
(load-image url :key on-error)
-
Loads an image from url and returns
<html-image-element>
object (which isHTMLImageElement
in JavaScript).The keyword argument
:on-error
can be a keyword:error
(default) or#f
. If it's the former, an error is signaled when the image can't be loaded from the URL. If it's the latter,load-image
just returns#f
. (load-audio url :key on-error)
-
Loads an audio data from url and returns
<html-audio-element>
object (which isHTMLAudioElement
in JavaScript).The keyword argument
:on-error
can be a keyword:error
(default) or#f
. If it's the former, an error is signaled when the audio data can't be loaded from the URL. If it's the latter,load-audio
just returns#f
. (alist->style alist)
-
Returns a string which is for the style attribute of HTML element. alist is an alist, the key is an style attribute name and the value is the attribute's value. If the value is
#f
, the style attribute will be ignored. (grut-canvas-window width height :key id title background-color window-width window-height resizable? fit margin
-
Creates
<grv-window>
which has a<canvas>
element. width and height are the resolution of the canvas.id
-
The element ID of this
<canvas>
element. The default is "canvas".
You can specify a list of IDs to make multiple layered canvases. The first ID points to the backmost canvas, and the last ID points to the foreground canvas. title
- The title of this window.
background-color
- The background color of this window.
window-width
- The width of this window.
window-height
- The height of this window.
resizable?
-
Whether this window is resizable or not. The default is
#t
. fit
-
How to fit the size and position of this
<canvas>
element to this window. The value must be one of these values.'contain
(default)- The canvas is expanded or shrank to fit the window with keeping the aspect ratio. If the canvas's aspect ratio and the window's aspect ratio are different, the canvas will be "letterboxed".
'cover
- The canvas is expanded or shrank to fit the window with keeping the aspect ratio. If the canvas's aspect ratio and the window's aspect ratio are different, the canvas will be clipped to fit.
'fill
- The canvas is expanded or shrank to fit the window without keeping the canvas's orignal aspect ratio. The entire canvas will completely fill the window.
'none
- The canvas will not be resized.
margin
-
The margin of this
<canvas>
element.
(grut-text-window :key id title column row font font-size color background-color window-width window-height resizable? fit scrollbar? padding
-
Creates
<grv-window>
which has a<grut-text>
element.id
-
The element ID of this
<grut-text>
element. The default is "text-console".
You can specify a list of IDs to make multiple layered text elements. The first ID points to the backmost text, and the last ID points to the foreground text. title
- The title of this window.
column
-
The number of columns of this
<grut-text>
element. The width of<grut-text>
will be computed with this value and the font size. row
-
The number of rows of this
<grut-text>
element. The height of<grut-text>
will be computed with this value and the font size. font
-
The font of this
<grut-text>
element. font-size
-
The font size of this
<grut-text>
element. color
-
The text color of this
<grut-text>
element. The default is "white". background-color
- The background color of this window. The default is "black".
window-width
- The width of this window.
window-height
- The height of this window.
resizable?
-
Whether this window is resizable or not. The default is
#t
. fit
-
How to fit the size and position of this
<grut-text>
element to this window. The available values are the same asgrut-canvas-window
. scrollbar?
-
Whether the vertical scrollbar is shown or not. The default is
#f
(the scroll bar is hidden). The horizontal scrollbar is always hidden. padding
-
The padding of this this
<grut-text>
element.
(grut-text+canvas-window width height :key text-id canvas-id title column row font font-size color background-color window-width window-height resizable? fit scrollbar? margin padding
-
Creates
<grv-window>
which has a<grut-text>
element and a<canvas>
element. width and height are the resolution of the canvas. text-id
-
The element ID of this
<grut-text>
element. The default is "text-console".
You can specify a list of IDs to make multiple layered text elements likegrut-text-window
. canvas-id
-
The element ID of this
<canvas>
element. The default is "canvas". You can specify a list of IDs to make multiple layered canvases likegrut-canvas-window
. title
- The title of this window.
column
-
The number of columns of this
<grut-text>
element. The width of<grut-text>
will be computed with this value and the font size. row
-
The number of rows of this
<grut-text>
element. The height of<grut-text>
will be computed with this value and the font size. font
-
The font of this
<grut-text>
element. font-size
-
The font size of this
<grut-text>
element. color
-
The text color of this
<grut-text>
element. The default is "white". background-color
- The background color of this window. The default is "black".
window-width
- The width of this window.
window-height
- The height of this window.
resizable?
-
Whether this window is resizable or not. The default is
#t
. fit
-
How to fit the size and position of this
<grut-text>
and<canvas>
element to this window. The available values are the same asgrut-canvas-window
. scrollbar?
-
Whether the vertical scrollbar is shown or not. The default is
#f
(the scroll bar is hidden). The horizontal scrollbar is always hidden. margin
-
The margin of this
<canvas>
element. padding
-
The padding of this this
<grut-text>
element.
Graviton defines these JavaScript objects as global.
NOTE: In the current browser implementation, AudioContext is initially in the "suspended" state and must be enabled by user interaction. You don't need to implement it because Graviton takes care of this operation.
These classes inherit <jsobject>
, and their hierarchy is the same as the corresponding JavaScript class hierarchy.
Graviton provides these functions for Canvas.
You can use (event-target'add-event-listener ...)
for JavaScript event handling. A scheme procedure can be passed to the listener, so you can catch the event in Scheme world.
However, the procedure will be called with a JavaScript event object. If you need a value in the event, it needs addtional JavaScript FFI requests and needs to wait for the response. Graviton provides several event utilities to reduce such FFI request/response.
Graviton provides Worker (<grv-worker>
) to support an asynchronous mechanism. Worker can run code, and the code can yield its execution to other code.
One worker is created when a window is opened. It is called "main worker". You can create a worker yourselves to run code in the background.
Worker also has a messaging mechanism. Each worker can communicate with other workers.
<channel>
is a queue which supports Graviton's asynchronous mechanism. You can use it for inter-worker communication.
One of the motivations to develop Graviton is to quickly make an old-style computer UI (the days of CUI, i.e., text console + single graphics screen). graviton.grut, which stands for "GRaviton Utility Toolkit," provides miscellaneous utilities to support such UI.
(play-mml track mml ...)
-
Plays music that is described in mml (Music Macro Language). track is a keyword in which the music is played. track and mml is a pair, and you can specify multiple track and mml pairs (for example,
(play-mml :track1 '(c d e) :track2 '(e f g))
).The Music Macro Language is a list of these elements.
note symbol (c, d, e, f, g, a, b and qualifiers)
-
c
,d
,e
,f
,g
,a
andb
correspond to a scale. You can concatitate them to describe a chord. For example,'ceg
means Cmajor.+
and-
means sharp and flat.'c+
is "C sharp", and'b-
means "B flat".
You can add a number and a dot after the note symbol to describe the length.'c4
means a quarter note, and'c8.
means dotted eighth note. If the length is omitted, the default length is used.
The notes can be concatinated with&
for slur and tie like'c&c
and'c&d
. 'rlength
-
Rest.
'r4
means a quarter rest. If length is omitted, the default length is used. 'xlength
- Noise.
:tempo bpm
'tbpm
- Tempo. bpm can't be omitted.
:length bpm
'llength
- Changes the default length of notes.
:octave bpm
'ooctave
- Changes the octave.
'>
- Increases the octave.
'<
- Decreases the octave.
:volume volume
'vvolume
- Changes the volume. volume must be a number from 0 to 1.
:stereo-pan pan
'ppan
- Controls a pan. pan is a number from -1 to 1, -1 means full left pan, and 1 means full right pan.
:gate/step ratio
'qratio
- Changes a ratio of gate time (the sound is actually sounded) and the sound length. ratio is a number from 0 to 1. The default is 7/8.
:wave-form wave-type
:wave-form (cosine-terms sine-terms)
-
Changes the wave shape of sounds. You can specify the shape with a symbol or cosine and sine-terms of the wave.
The wave type symbol is'sine
,'square
,'sawtooth
or'triangle
.
cosine-terms and sine-terms must be<f32vector>
, and their lengths must be equal. :adsr (attack decay sustain release)
- Specifies the envelope in ADSR. attack, decay and release are time in second. sustain is a volume level from 0 to 1.
:detune cents
- Specifies detuning in cents.
(beep frequency [duration])
-
Generates a sound of frequency and duration in second. If duration is omitted, the default length by
:length
is used. (sound audio-buffer [start end duration])
- Generates a sound audio-buffer. If start is specified, the sound loops from start (in second) to end (the default end is the end of the sound). duration (in second) specifies the length of the looped sound.
(compile-mml mml)
-
Compiles mml. You can pass the returned value to
play-music
to play it. (play-music track music ...)
- Plays the compiled music object.
(resume-track track ...)
- Resumes track, which is paused.
(resume-all-tracks)
- Resumes all tracks.
(pause-track track ...)
- Pauses track.
(pause-all-tracks)
- Pauses all tracks.
(stop-track track ...)
- Stops playing the specified track.
(stop-all-tracks)
- Stops playing all tracks.
(wait-track track ...)
- Waits until the playing finishes in the specified track.
(wait-all-tracks)
- Waits until the playing finishes in all tracks.
(play-beep frequency length :key wave-form volume)
-
Plays a sound of frequency and length. length is in second.
The default wave-form is
'sine
. (play-sound url :key start end duration volume)
(play-sound audio-buffer :key start end duration volume)
- Plays a sound of the specified audio-buffer or the downloaded data from url. You can loop the sound from start to end in duration.
(load-audio-buffer url :key on-error)
-
Loads audio data from url, and returns audio-buffer object. You can play it with
play-sound
or the MML command(sound audio-buffer ...)
.The keyword argument
:on-error
can be a keyword:error
(default) or#f
. If it's the former, an error is signaled when the audio data can't be loaded from the URL. If it's the latter,load-audio-buffer
just returns#f
.
(copy-text-to-clipboard text)
- Copies text into the clipboard.
<speech-synthesis-voice>
represents a voice for the speech synthesis (it is identical to SpeechSynthesisVoice
in JavaScript). The class has these slots.
default
(boolean)- Whether the voice is the defalut voice or not.
lang
(string)- The BCP 47 language tag of this voice.
local-service
(boolean)- Whether the voice is supplied by a local speech synthesizer service or not.
name
(string)- The human-readable name of this voice.
voice-uri
(string)- The type of URI and location of the speech synthesis service for this voice.
These functions are provided for Speech Synthesis.
(query-all-voices :key lang name default local-service voice-uri wait?)
-
Returns a list of voices that matches the specified conditions. The conditions can be specified with
lang name default local-service voice-uri
. They are associated with the slots of<speech-synthesis-voice>
.If the slot is boolean, the associated keyword parameter can takes a boolean value. The voices that matches the keyword parameter value will be returned.
If the slot is string, the associated keyword parameter can take a string or a regular expression. The voices whose parameter contains the string or matches the regular expression will be returned.
wait?
(default is#t
)-
Waits for the background voice loading. Some browsers loads voices in background, so
SpeechSynthesis.getVoices()
in JavaScript may not return any voices until the load is completed. Ifwait?
is#t
, this function waits until the getVoices() method returns voices. If no voices are available,query-all-voices
will not be returned.
(query-voice :key lang name default local-service voice-uri wait?)
-
Returns one of the voices that matches the specified conditions. The keyword parameters are the same as
query-all-voices
. (speak text :key voice)
- Speaks text with voice. If voice is omitted, the default voice will be used.
(pause-speech)
- Pauses the current speech.
(resume-speech)
- Resumes the paused speech.
(cancel-speech)
- Cancels the current speech.
graviton.grut module provides <grut-text>
element that represents a text console. You can input a text using line edit and output a text. You can also control the text console using ANSI escape sequence.
<grut-text>
is a <jsobject>
, and it also inherits <virtual-output-port>
. You can write a text to it, so that the text will be displayed in the text console element (for example, (format text-console "Hello, world")
).
<grut-text>
can accept generic functions of text.console
.
html:grut-text
-
Constructs a
<grut-text>
element. You can use it as a part of HTML document tree bytext.html-lite
module. (set-line-style! text-console row style value)
- Sets the HTML style of the line row to value.
(scroll-up text-console :optional n)
- Scrolls up text-console by n lines. The default n is 1.
(scroll-down text-console :optional n)
- Scrolls down text-console by n lines. The default n is 1.
(scroll-to text-console row :optional align-to-top)
-
Scrolls to the line row. If align-to-top is
#t
, the line row will be aligned at the top. Otherwise, the line will be aligned at the bottom. The default value of align-to-top is#t
. (compute-character-position&size text-console column row)
- Returns the rectangle in the viewport which contains the character at (column, row). This function returns 4 values (x, y, width and height).
(get-input-text text-console :optional wait?)
-
Returns a string in the input queue of text-console.
If the input queue is empty and wait? is
#f
(default),#f
is returned.If wait? is
#t
, this function waits for an input event if the queue is empty.
(make-keymap :optional parent)
- Makes a keymap. If a parent keymap is specified, the newly created keymap inherits the parent.
(global-keymap)
- Returns the global keymap, which is effective at first.
(bind-key keymap key action :key use-clipboard-text?)
-
Assigns action to key in keymap.
key is a key value of KeyboardEvent. If you want to specify modifier keys, Alt, Meta, Ctrl and Shift, you need to add prefixes "A-", "M-", "C-" and "S-" in this order. For example, if you want to specify "Ctrl+Shift+PageDown", the key is "C-S-PageDown".
You can also specify continuous keys like "Ctrl+X Ctrl+S" in Emacs. In this case, the key is "C-x C-s".
action must be a string or
<procedure>
. If action is a string, the string will be inserted if the key is pressed. If action is a procedure, the procedure will be called with<input-context>
. You can get information of line editing from the context.If you want to use a clipboard text, you need to specify
#t
to:use-clipboard-text?
keyword parameter. You can get the clipboard text from the<input-context>
. (switch-keymap input-context keymap)
- Switches the current keymap to keymap.
(read-text/edit text-console :key prompt keymap input-continues initial-text cursor-column cursor-row on-change on-input data-alist)
-
Reads a text that the user inputs on text-console. The user can use line edit in the input.
prompt
- The prompt string of this input. The default is an empty string.
keymap
- The keymap used in this input.
input-continues
-
The procedure that determines whether the input continues or finishes. This procedure is called with input-context when Enter is pressed. If it returns
#f
, the input finishes andread-text/edit
returns the input text. Otherwise, the input continues.
If input-continues is#f
(default), the input finishes when Enter is pressed. If you don't need multiple line edit, you don't need to specify input-continues. initial-text
- The ininitial text of this input.
cursor-column
- The column number of the initial cursor position.
cursor-column
- The row number of the initial cursor position.
on-change
-
The procedure that is called with
<input-context>
when the input text is changed. on-input
-
The procedure that is called with
<input-context>
when the user types any keys. data-alist
-
The data that will be passed to
<input-context>
. You can get the data withinput-context-data-get
and modify it withinput-context-data-put!
.
(clipboard-text input-context)
-
Returns the text in the clipboard. If no text is available,
#f
is returned. (input-context-text-line input-context :optional row)
- Returns the line of the input text at line row. The default row is the current line.
(input-context-text-content input-context)
- Returns the whole text of the input text.
(input-context-data-get input-context key :optional default)
-
Searches key in input-context, and returns its value if found. Otherwise, returns default (the default is
#f
). (input-context-data-put! input-context key value)
- Puts key with value in input-context.
(input-context-data-delete! input-context key)
- Deletes key in input-context.
(edit:backward-delete-char input-context)
- Deletes the previous character.
(edit:beginning-of-edit-area input-context)
- Moves the cursor to the beginning of the input area.
(edit:beginning-of-line input-context)
- Moves the cursor to the beginning of the current line.
(edit:cancel-edit input-context)
-
Cancels the input text.
read-text/edit
returns#f
. (edit:copy input-context)
- Copies the selected text into the clipboard.
(edit:cut input-context)
- Copies the selected text into the clipboard, and deletes the text.
(edit:delete-char input-context)
- Deletes the current character.
(edit:end-of-edit-area input-context)
- Moves the cursor to the end of the input area.
(edit:end-of-line input-context)
- Moves the cursor to the end of the current line.
(edit:forward-char input-context)
- Moves the cursor one character forward.
(edit:insert-string input-context string)
- Inserts string at the current cursor position.
(edit:newline-or-commit input-context)
-
Finishes the input, and returns the input text if
input-continues
that is specified byread-text/edit
returns#f
. Otherwise, inserts a newline. (edit:next-line input-context)
- Moves the cursor vertically down one line.
(edit:page-up input-context)
- Moves the cursor vertically up one page.
(edit:page-down input-context)
- Moves the cursor vertically down one page.
(edit:paste input-context)
- Pastes a text in the clipboard.
(edit:previous-char input-context)
- Moves the cursor one character backward.
(edit:previous-line input-context)
- Moves the cursor vertically up one line.
(edit:select-all input-context)
- Selects all text in the current input area.
Graviton provides S-expression JavaScript (JSiSE) syntax like CiSE of Gauche. You can use it in the macros described below.
Basically, (func arg1 arg2 ...)
in JSiSE is translated to func(arg1, arg2, ...)
in JavaScript. A symbol in JSiSE is an identifier or a variable name in JavaScript. A dot "." in a JSiSE symbol has a special meaning, which means property reference of an object. It is the same as JavaScript. So (console.log "Hello, world")
is console.log("Hello, world")
, and (set! foo.bar 1)
is foo.bar=1
.
(define-jsvar var [value])
-
Defines a JavaScript variable var in the current JavaScript module. This JavaScript module is created implicitly per Gauche module. This variable var can't be seen from other modules. If value is omitted, the variable is initialized with
undefined
. (define-jsfn (func args ...) body ...)
- Defines a JavaScript function func in the current JavaScript module.
(inline-js body ...)
- Embeds JavaScript code in the toplevel of the current JavaScript module. This code runs when the module is loaded.
(import-js module-path :as name)
(import-js module-path [:only (only-export ...)] [:rename ((rename-export alias) ...)])
-
Imports the JavaScript module from module-path into the current module.
The former statement is translated to
import * as name from module-path
, it will import all the contents of the module as name.The latter statement is translated to
import {only-export, rename-export as alias, ...} from module-path
, it will import the selected contents of the module. In the latter statement, you need to specify at least one of:only
or:rename
keyword. (jslet ((jsvar [value]) ...) body ...)
-
Embeds JavaScript code. value is an object in Scheme world. It is transferred to JavaScript world and bound to jsvar.
jslet
doesn't return a meaningful value. Currently, it returns#<undef>
.If value is omitted, an object which is referenced by the same name jsvar in Scheme world, is bound.
In run-time,
jslet
sends a request to the client (browser or graviton-player) to execute body .... But the request is buffered until the current continuation finishes. If you want to send the request immediately, call(asleep 0)
. (jslet/async ((jsvar [value]) ...) body ...)
(jslet/await ((jsvar [value]) ...) body ...)
-
Basically,
jslet/async
andjslet/await
are the same asjslet
, but they can return values. You can use(respond val ...)
macro to return values to Scheme world.jslet/async
returns<grv-promise>
object immediately, and you need to use(await promise)
to get the values.jslet/await
is identical to(await (jslet/async ...))
.await
doesn't block the current thread. The continuation is called after the JavaScript code returns values. Until then, the thread can execute other code (event handler or other waiting continuations, etc.).Like
jslet
, the request to execute body ... is buffered. Callingawait
will send the buffered requests because it finishes the current continuation.jslet/await
always callsawait
, so the buffered requests are sent atjslet/await
location.
The following sections explain the syntax of JSiSE.
#t
meanstrue
.#f
meansfalse
.null
meansnull
.undefined
or#<undef>
meansundefined
.#(v0 v1 v2 ...)
means a vector[v0, v1, v2, ...]
."string"
means a string.
(vector val ...)
- It means a vector
[val, ...]
. (vector-ref vec i)
- It means
vec[i]
. (vector-set! vec i val)
- It means
vec[i]=val
.
(object (key . val) ...)
- It means a JavaScript object
{key: val, ...}
. key must be a string. (new class args ...)
(make class args ...)
- It means
new class(args, ...)
. (~ expr attr ...)
(ref expr attr ...)
- It means a property reference. If the attribute attr is a symbol, it is translated to
(expr).attr
. Otherwise, it is translated to(expr)[attr]
. You can pass multiple attr for reference chain.
(if expr then-body [else-body])
- It means
if (expr) { then-body } else { else-body }
. Thisif
macro is available in an expression. In that case, theif
is tralslated to(expr) ? (then-body) : (else-body)
. (cond (expr then ...) ... [(else else-body ...)])
- It is translated to continuous if-else statements. This
cond
macro is available in an expression likeif
macro. (dotimes (var num-expr) body ...)
- It repeats body ... for a number of times num-expr. var indicates the number of the loop (starts from 0).
(when expr body ...)
- It means
if (expr) { body... }
. (unless expr body ...)
- It means
if (!expr) { body ...}
. (case key ((data0 data1 ...) body ...) [(else else-body ...)])
- It means
switch (key) { case data0: case data1: ... { body ... } break; default: else-body ...}
.
(dotimes (var num-expr) body ...)
- It is equivalent to Scheme's
dotimes
. It repeats body for a number of times. (while expr body ...)
- It means
while (expr){body ...}
. (for-each proc coll)
- It means
coll.forEach(proc)
. (return [val])
- It means
return [val]
. (begin body ...)
- It means
{ body ... }
.
(let ((var expr) ...) body ...)
(let* ((var expr) ...) body ...)
-
let
andlet*
are equivalent to Scheme'slet
andlet*
. They creates a local scope where var ... are bound to the value of expr ..., then evaluates body .... (let1 var expr body ...)
-
let1
is a convenient macro for only one variable. It is the same as(let ((var expr)) body ...)
. (rlet1 var expr body ...)
-
rlet1
binds the value of expr to var, then evaluates body .... After that, it returns var.
(set! var expr)
-
It means
var=expr
. (inc! var [delta])
-
It means
var+=delta
. The default value of delta is 1. (dec! var [delta])
-
It means
var-=delta
. The default value of delta is 1.
-
(not v)
means!v
. -
(or v0 v1 ...)
meansv0 || v1 || ...
. -
(and v0 v1 ...)
meansv0 && v1 && ...
. -
(lognot v)
means~v
. -
(logior v0 v1 ...)
meansv0 | v1 | ...
. -
(logand v0 v1 ...)
meansv0 & v1 & ...
. -
(logxor v0 v1 ...)
meansv0 ^ v1 ^ ...
. -
(equal? v0 v1 ...)
meansv0 === v1 === ...
. -
(< v0 v1 ...)
meansv0 < v1 < ...
. -
(<= v0 v1 ...)
meansv0 <= v1 <= ...
. -
(> v0 v1 ...)
meansv0 > v1 > ...
. -
(>= v0 v1 ...)
meansv0 >= v1 >= ...
. -
(+ v0 v1 ...)
meansv0 + v1 + ...
. -
(- v)
means-v
. -
(- v0 v1 ...)
meansv0 - v1 - ...
. -
(* v0 v1 ...)
meansv0 * v1 * ...
. -
(/ v0 v1 ...)
meansv0 / v1 / ...
. -
(modulo v0 v1)
meansv0 % v1
. -
(ash n count)
means<<
(if count is positive or zero) or>>
(if count is negative). -
(pre++ var)
means++var
. -
(pre-- var)
means--var
. -
(post++ var)
meansvar++
. -
(post-- var)
meansvar--
. -
(lambda (arg ...) body ...)
meansfunction (arg, ...) { body ... }
. -
(is-a? expr class)
meansexpr instanceof class
.
(respond val ...)
- It returns the values val ... to Scheme world.
(define var val)
(define (func args ...) body ...)
-
They are translated to
let var = val
andlet func = function(args, ...) { body ... }
. (import var name)
(import (var ...) name)
-
They translated to
const {var, ...} = require(name)
.
NOTE: define
and import
are intended to define a global object in inline-js
.