'use strict';
// Dependencies
const fs = require('fs');
const { readFile } = require('fs').promises;
// TODO: rewrite these / use lexical parser
// const lex = require('jade-lexer');
// const parse = require('jade-parser');
const { run } = require('minsc');
// JavaScript & TypeScript ASTs
const AST = require('@webassemblyjs/ast');
const {
Project,
ScriptTarget
} = require('ts-morph');
// Fabric Types
const Entity = require('./entity');
const Hash256 = require('./hash256');
const Machine = require('./machine');
// const Ethereum = require('../services/ethereum');
// TODO: have Lexer review
// TODO: render the following:
// ```purity
// fabric-application
// fabric-grid
// fabric-row
// h1 Hello, world.
// fabric-row
// fabric-column
// fabric-row
// fabric-message-list
// fabric-row
// fabric-message-forge
// fabric-column
// fabric-peers
// ```
// This is an example of a self-contained document. You can add assertions as
// follows:
// ```
// method(check="integrity")
// ```
// This will auto-configure validation base from chain of greatest work.
/**
* Compilers build interfaces for users of Fabric applications.
* @type {Actor}
* @property {AST} ast Compiler's current AST.
* @property {Entity} entity Compiler's current {@link Entity}.
*/
class Compiler {
/**
* Create a new Compiler.
* @param {Object} settings={} Configuration.
* @param {Buffer} settings.body Body of the input program to compile.
* @return {Compiler} Instance of the compiler.
*/
constructor (settings = {}) {
this.settings = Object.assign({
ast: null,
body: null,
type: 'javascript',
inputs: [],
outputs: []
}, settings);
this.entity = new Entity(this.settings);
this.machine = new Machine(this.settings);
this.project = new Project({
compilerOptions: {
target: ScriptTarget.ES2020
}
});
this.ast = null;
this.body = null;
this.screen = null;
this.entities = {};
this.abstracts = {};
return this;
}
get integrity () {
return `sha256-${Hash256.digest(this.body)}`;
}
/**
* Creates a new Compiler instance from a JavaScript contract.
* @param {Buffer} body Content of the JavaScript to evaluate.
* @returns Compiler
*/
static _fromJavaScript (body) {
if (!(body instanceof Buffer)) throw new Error('JavaScript must be passed as a buffer.');
return new Compiler({ body, type: 'javascript' });
}
static _fromMinsc (body) {
if (!(body instanceof Buffer)) throw new Error('JavaScript must be passed as a buffer.');
return new Compiler({ body, type: 'minsc' });
}
static _fromSolidity (body) {
if (!(body instanceof Buffer)) throw new Error('JavaScript must be passed as a buffer.');
return new Compiler({ body, type: 'solidity' });
}
async start () {
const promises = this.settings.inputs.map(x => readFile(x));
const contents = await Promise.all(promises);
const entities = contents.map(x => new Entity(x));
const abstracts = contents.map(x => this._getJavaScriptAST(x));
// Assign Body
const initial = this.settings.body || Buffer.from('', 'utf8');
const body = Buffer.concat([ initial ].concat(contents));
const entity = new Entity(body);
const abstract = this._getJavaScriptAST(body);
this.entities[entity.id] = entity;
this.abstracts[entity.id] = abstract;
// Assign all Entities, Abstracts
for (let i = 0; i < entities.length; i++) {
this.entities[entities[i].id] = entities[i];
this.abstracts[entities[i].id] = abstracts[i];
}
this.body = body;
return this;
}
_getScriptAST (input) {
throw new Error('Not yet supported.');
return null;
}
/**
* Parse a {@link Buffer} of JavaScript into an Abstract Syntax Tree ({@link AST}).
* @param {Buffer} input Input JavaScript to parse.
* @returns {AST}
*/
_getJavaScriptAST (input) {
if (typeof input === 'string') input = Buffer.from(input, 'utf8');
const ast = AST.program(input);
return {
'@type': 'AST',
'@language': 'JavaScript',
input: input,
interpreters: {
'WebAssembly': ast
}
};
}
_getMinscAST (input) {
const output = run(input);
return {
'@type': 'AST',
'@language': 'Minsc',
input: input,
script: output,
interpreters: {
'Minsc': output
}
};
}
_getSolidityAST (input) {
const ethereum = new Ethereum();
const result = ethereum.execute(body);
return {
'@type': 'AST',
'@language': 'Solidity',
input: input,
output: result,
interpreters: {
'EthereumJSVM': result
}
};
}
_fromPath (filename) {
let src = fs.readFileSync(filename, 'utf8');
let tokens = lex(src);
let ast = parse(tokens, { filename, src });
let html = this.render(ast);
return html;
}
render () {
if (this.screen) {
return this._renderToTerminal();
} else {
return this._renderToHTML();
}
}
// TODO: @melnx to refactor into f(x) => y
_renderToTerminal (ast, screen, ui, eventHandlers, depth = 0) {
let result = '';
if (ast.type === 'Block') {
for (let n in ast.nodes) {
result += this.render(ast.nodes[n], screen, ui, eventHandlers, depth);
}
} else if (ast.type === 'Tag') {
// /////////////////////////////////////
let space = ' '.repeat(depth * 2);
// result += depth;
let attrs = [];
let params = {};
for (let a in ast.attrs) {
let attr = ast.attrs[a];
attrs.push(attr.name + '=' + attr.val);
if (attr.val[0] === "'") {
let content = attr.val.substring(1, attr.val.length - 1);
if (content[0] === '{') {
params[attr.name] = JSON.parse(content);
} else {
params[attr.name] = content;
}
} else {
params[attr.name] = JSON.parse(attr.val);
}
}
params.parent = screen;
if (screen) {
let element = blessed[ast.name](params);
for (let p in params) {
if (p.startsWith('on')) {
let handler = eventHandlers[ params[p] ];
if (p.startsWith('onkey')) {
let key = p.substr(5);
element.key([key], handler);
} else {
element.on(p.substr(2), handler);
}
}
}
if (params.id) ui[params.id] = element;
}
var attrsStr = attrs.join(' ');
if (attrsStr) attrsStr = ' ' + attrsStr;
if (ast.selfClosing) {
result += space + '<' + ast.name + attrsStr + '/>\n';
} else {
result += space + '<' + ast.name + attrsStr + '>\n';
if (ast.block) result += this.render(ast.block, screen, ui, eventHandlers, depth + 1);
result += space + '</' + ast.name + '>\n';
}
}
return result;
}
_renderToHTML (state = {}) {
return `<!DOCTYPE html>
<html>
<head>
<title>Fabric</title>
</head>
<body>
<h1>Empty Document</h1>
<p>This document is a placeholder.</p>
<div id="body">
<textarea name="body">${this.body}</textarea>
</div>
<fabric-unsafe-javascript>
<script integrity="${this.integrity}">${this.body}</script>
</fabric-unsafe-javascript>
</body>
</html>`;
}
}
module.exports = Compiler;