Table of Contents
Language definition
A Hop.js language is a DSL or a complete language that can be used to implement an whole module content or a function body. There are two possibility for using a module MOD implemented in a language LANG.
- When MOD is required, LANG is passed as a second argument to
require
. See chapter HopScript modules. for details on module importations. - The MOD header contains a
"use LANG"
declaration. This second form is possible only when the LANG is an extension of JavaScript, that is to be parsed by the Hop compiler parser.
Language implementation
A language is any JavaScript object that implements a compilation
function associated with the Symbol.compiler
property. This function
accepts two parameters, a resolved name of file to be compiled, and an
optional options
object. If the options
is undefined or if the
options.target
property is undefined, it is to the compiler to
decide where and how to communicate its result to the Hop runtime
system. A compilation result must be an object with two properties:
type
: the type of the result which can either be:filename
: denoting that the compilation result as been stored in a file;json
: denoting that the compilation result is a json;ast
: denoting that the compilation result is an internal form, directly suitable to the Hop compiler;value
: denoting that the compilation result is an arbitrary JavaScript value convertible into json;error
: denoting a compilation failure.
value
: the actual value of the compilation.
For instance, if the language compiler wants to communicate that its result is store into a file, it should return a JavaScript object like:
{ type: "filename", value: "/tmp/foo.tmp/js" }
When the compiler is to decide the file name where to store its result, it might make sense for it to use the standard Hop cache directory, whose name might be obtained with:
require( hop.config ).cacheDir
Example
This example shows how to define a new language and how to require modules implemented in that language. This verfy simple example defines CSV (comma separated value) a new language for literals.
For maximmal efficiency, the CSV loader uses the native Hop parser implemented in Scheme. This example also illustrates how to bind this CSV parser into JavaScript.
Let's start with a sample csv file.
lang/sample.csv
Year,Make,Model,Description,Price
1997,Ford,E350,"ac, abs, moon",3000.00
1999,Chevy,"Venture ""Extended Edition""","",4900.00
1999,Chevy,"Venture ""Extended Edition, Very Large""",,5000.00
1996,Jeep,Grand Cherokee,"MUST SELL!
air, moon roof, loaded",4799.00
The client file requires the sample.csv
file as if it was a regular
Hop.js module, but it specifies that this module is implemented in the
csvg.js
language.
lang/lang.js
"use hopscript";
const csv = require( "./sample.csv", "./csv.js" );
console.log( "csv=", csv );
service lang( o ) {
return <html>
<table>
<tr>${csv[ 0 ].map( h => <th>${h}</th> )}</tr>
${csv.slice( 1 ).map( r => <tr>${r.map( e => <td>${e}</td> )}</tr> )}
</table>
</html>
}
The csv.js
language is implemented as follows:
lang/csv.js
"use hopscript";
const csvloader = require( "./csv.hop" );
const fs = require( "fs" );
exports[ Symbol.compiler ] = (file, options) => {
const val = csvloader.load( file, options );
if( options && options.target ) {
var fd = fs.openSync( options.target, "w" );
try {
var buf = JSON.stringify( val );
fs.write( fd, buf, 0, buf.length );
return {
type: "filename",
value: target,
}
} finally {
fs.closeSync( options.target );
}
} else {
return {
type: "value",
value: val,
}
}
}
The JavaScript language is a mere wrapper of the native Hop csv parser that is implemented as:
lang/csv.hop
(module csv
(library hopscript hop hopwidget nodejs web csv)
(export (hopscript %this this %scope %module)))
;*---------------------------------------------------------------------*/
;* hopscript ... */
;* ------------------------------------------------------------- */
;* This is the function called by JavaScript when the Hop module */
;* is required. It binds the exports field of the newly */
;* allocated module */
;*---------------------------------------------------------------------*/
(define (hopscript %this this %scope %module)
(define (literal->js v)
(cond
((string? v) (js-string->jsstring v))
((number? v) (js-number->jsnumber v))
(else v)))
(with-access::JsGlobalObject %this (js-object)
(let ((exports (js-new0 %this js-object)))
(js-put! %module (& "exports") exports #f %this)
;; bind the load function is the pseudo javascript module
(js-put! exports (& "load")
(js-make-function %this
;; wrap the Hop read-csv records function into a JS function
(lambda (this url options)
(call-with-input-file url
(lambda (p)
(js-vector->jsarray
(apply vector
(map (lambda (row)
(js-vector->jsarray
(apply vector (map literal->js row))
%this))
(read-csv-records p +csv-lexer+)))
%this))))
2 "load")
#f %this))))
Hopc parser plugins
The hopc parser can be extended with plugins that can be used to extend the syntax it analyses. This feature is still experimental and will be described when stabiliez. In the meantime, an example can be found in the implementation of the HipHop language.
Require extension
Hop extends the require
form as follows:
<require> →
require( <ModuleExpr> )
| require( <ModuleExpr>, <LangExpr> )
| require( <ModuleExpr>, <LangExpr>, <CompLangExpr> )
The first form is the original require
form. The second, loads
<ModuleExpr>
defined in language <LangExpr>
. The third, loads
<ModuleExpr>
defined in language <LangExpr>
, passing <CompLangexpr>
to the <LangExpr>
compiler.