Skip to content

linux CLI platform #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 41 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
5a43728
fxFindModule: support ../../ as well as ./ and ../
dckc Sep 21, 2019
db40005
add -p x-cli-lin to build simple CLI tools on linux
warner Dec 6, 2018
02b0249
example of a simple command-line tool
warner Dec 6, 2018
3855817
lin_xs_cli: require -> import for main
dckc Nov 23, 2019
9f8aa37
cli-lin: include xsBigInt
dckc Nov 23, 2019
b4a147e
x-cli-lin.mk: C_FLAGS -> XS_C_FLAGS (?)
dckc Nov 23, 2019
b1e2d0c
x-cli-lin: get resources, mc.config.js working
dckc Nov 23, 2019
9b7cb22
export mxPromiseStatus() as fxPromiseIsPending()
dckc Dec 27, 2019
0a6c7e5
http: some support for http upgrade, e.g. websockets
dckc Jan 2, 2020
6d143e4
http: support async replies
dckc Jan 3, 2020
774f920
export mxRejectedStatus as fxPromiseIsRejected()
dckc Jan 11, 2020
9ec77c2
websockets work-around: socket tx buffer 1k -> 4k
dckc Jan 16, 2020
0fa36ea
Merge branch 'promise-pending' into cli-lin
dckc Jan 28, 2020
65212d2
lin_xs_cli: run event loop after calling main()
dckc Dec 26, 2019
6714f67
build-release github workflow for headless linux SDK
dckc Dec 27, 2019
f2a75c0
move headless build target to makefiles/lin
dckc Dec 30, 2019
fc1f1bd
install libgtk-3-dev
dckc Jan 2, 2020
332fbdf
try apt update before installing libgtk-3-dev
dckc Jan 18, 2020
c2caa12
fxFindModule: support ../../ as well as ./ and ../
dckc Sep 21, 2019
f8710ac
http: some support for http upgrade, e.g. websockets
dckc Jan 2, 2020
54ad05e
http: support async replies
dckc Jan 3, 2020
e8c337a
websockets work-around: socket tx buffer 1k -> 4k
dckc Jan 16, 2020
2814d92
Merge branch 'find-mod-grandparent' into cli-lin
dckc Jan 28, 2020
c5abcd4
Merge branch 'http-async-ws' into cli-lin
dckc Jan 28, 2020
e3d70c0
Merge branch 'public' into ag-linux-cli
dckc May 5, 2020
080f59a
lin_xs_cli: send progress diagnostics to stderr
dckc Jul 21, 2020
c7cb9e8
lin_xs_cli: skip check for non-promise
dckc Aug 9, 2020
d5b9a11
Merge branch 'public' into ag-linux-cli
dckc Aug 13, 2020
b94970c
lin_xs_cli: fxAbort() takes a status arg now
dckc Aug 13, 2020
4842c46
reduce ci scope to smoke test
dckc Aug 13, 2020
a1691ec
skip debug build in headless
dckc Aug 22, 2020
4dcdcff
x-cli-lin: use release tools (xsc, ...)
dckc Aug 22, 2020
8b81b50
Merge branch 'public' into ag-linux-cli
dckc Oct 13, 2020
754c011
minimal TextEncoder / TextDecoder API with example
dckc Oct 13, 2020
f2c806f
test, fix TextDecoder.prototype.decode
dckc Oct 13, 2020
b3e3b1e
mconfig: supply RESOURCES_DIR for x-cli-lin
dckc Oct 13, 2020
d836ef4
avoid some allocations
dckc Oct 13, 2020
51518e5
docstrings; document special case for encode("")
dckc Oct 13, 2020
be83ff7
omit the unimplemented method so it can be feature-tested
dckc Oct 13, 2020
3bf3b8c
hex module is no longer used in the example
dckc Oct 13, 2020
4c995ae
Merge minimal utf-8 TextEncoder / TextDecoder
dckc Oct 14, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/smoke-test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: build and smoke test moddable SDK for linux CLI (x-cli-lin)

on: [push]

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v1
- name: Install libgtk-3-dev
run: |
sudo apt-get -y update
sudo apt-get -y install libgtk-3-dev
- name: build moddable headless tools
run: |
export MODDABLE=$PWD
export XS_DIR=$MODDABLE/xs
cd $MODDABLE/build/makefiles/lin
make headless
- name: check that helloworld builds
run: |
export MODDABLE=$PWD
export PATH=$MODDABLE/build/bin/lin/release:$PATH
cd $MODDABLE/examples/helloworld
mcconfig -m -p x-cli-lin
# ISSUE: support scripts with no main()?
# $MODDABLE/build/bin/lin/release/helloworld
test -x $MODDABLE/build/bin/lin/release/helloworld
mcconfig -d -m -p x-cli-lin
test -x $MODDABLE/build/bin/lin/debug/helloworld
8 changes: 7 additions & 1 deletion build/makefiles/lin/makefile
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,13 @@ release:
make GOAL=release -f simulator.mk
make GOAL=release -f tools.mk
$(MODDABLE)/build/bin/lin/release/mcconfig -m -p x-lin $(MODDABLE)/tools/xsbug/manifest.json


headless:
make GOAL=release -f $(XS_DIR)/makefiles/lin/xsc.mk
make GOAL=release -f $(XS_DIR)/makefiles/lin/xsid.mk
make GOAL=release -f $(XS_DIR)/makefiles/lin/xsl.mk
make GOAL=release -f tools.mk

clean:
make clean -f $(XS_DIR)/makefiles/lin/xsc.mk
make clean -f $(XS_DIR)/makefiles/lin/xsid.mk
Expand Down
11 changes: 11 additions & 0 deletions examples/cli/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
trace("top-level executes\n");

export default function main(argv) {
let three = 1 + 2;
let message = "Hello, world";
trace("Hello world, 1+2=" + three + "\n");
trace(`1+2=${1+2}`);
if (argv.length) {
trace("argv[0]: " + argv[0] + "\n");
}
}
8 changes: 8 additions & 0 deletions examples/cli/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"include": "$(MODDABLE)/examples/manifest_base.json",
"modules": {
"*": [
"./main"
]
},
}
85 changes: 85 additions & 0 deletions examples/data/text/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { TextEncoder, TextDecoder } from "text";

const testCases = [
{ label: "empty", bytes: [], text: "" },
{ label: "euro", bytes: [226, 130, 172], text: "€" },
{ label: "CJK", bytes: [240, 160, 174, 183], text: "𠮷" },
{
label: "sample paragraph",
text: "This is a sample paragraph.",
bytes: [
84,
104,
105,
115,
32,
105,
115,
32,
97,
32,
115,
97,
109,
112,
108,
101,
32,
112,
97,
114,
97,
103,
114,
97,
112,
104,
46,
],
},
];

function traceln(s) {
trace(s);
trace("\n");
}

function cmp(a, b) {
if (a.length !== b.length) return false;
for (let pos = 0; pos < a.length; pos++) {
if (a[pos] !== b[pos]) return false;
}
return true;
}

function main() {
traceln("hello!");
const enc = new TextEncoder();
const dec = new TextDecoder();
const data = enc.encode("blort!");
traceln(data);
const text = dec.decode(data);
traceln(text);

for (const { label, bytes, text } of testCases) {
const actual = enc.encode(text);
const actualString = dec.decode(Uint8Array.from(bytes));
if (!cmp(actual, bytes)) {
traceln(
`FAIL: ${label}: expected ${JSON.stringify(bytes)} actual ${
actual.length
} ${JSON.stringify(Array.from(actual))}`
);
} else if (actualString !== text) {
traceln(
`FAIL: ${label}: expected ${JSON.stringify(
text
)} actual ${JSON.stringify(Array.from(actualString))}`
);
} else {
traceln(`PASS: ${label}`);
}
}
}

main();
13 changes: 13 additions & 0 deletions examples/data/text/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"include": [
"$(MODDABLE)/examples/manifest_base.json",
],
"modules": {
"*": [
"./main",
"$(MODDABLE)/modules/data/text/text",
],
},
"preload": [
],
}
39 changes: 39 additions & 0 deletions modules/data/text/text.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* We take advantage of the internal representation of strings
* so that conversion is just copying bytes.
*
* "A string value is a pointer to a UTF-8 C string."
* -- https://github.com/Moddable-OpenSource/moddable/blob/public/documentation/xs/XS%20in%20C.md#strings
**/
#include <stdlib.h>
#include "xs.h"

/**
* Decode text from utf-8 to string
*
* @param {ArrayBuffer} xsArg(0)
* @returns {string}
*/
void xs_utf8_decode(xsMachine *the)
{
char *data = xsToArrayBuffer(xsArg(0));
size_t size = xsGetArrayBufferLength(xsArg(0));
xsResult = xsStringBuffer(data, size + 1);
char *dest = xsToString(xsResult);
dest[size] = 0;
}

/**
* Encode string of text as utf-8 bytes
*
* @param {string} xsArg(0)
* @returns {ArrayBuffer}
*
* WARNING: returned ArrayBuffer will be "detatched" in the 0-length case.
**/
void xs_utf8_encode(xsMachine *the)
{
xsStringValue string = xsToString(xsArg(0));
int length = c_strlen(string);
xsResult = xsArrayBuffer(string, length);
}
72 changes: 72 additions & 0 deletions modules/data/text/text.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* minimal TextDecoder
* No support for encodeInto.
*
* ref https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder
*/
export class TextEncoder {
constructor() {

}
get encoding() {
return 'utf-8';
}
encode(s) {
if (typeof s !== 'string') {
throw new TypeError(typeof s);
}
let arrayBuffer;
let bytes;
// fxArrayBuffer only allocates a chunk if length > 0
// else new Uint8Array(enc.encode(""))
// throws new "detached buffer!"
if (s.length === 0) {
// arrayBuffer = undefined;
bytes = new Uint8Array();
} else {
arrayBuffer = utf8_encode(s);
bytes = new Uint8Array(arrayBuffer);
}
// trace(`encode ${JSON.stringify(s)} -> ArrayBuffer(${arrayBuffer ? arrayBuffer.byteLength : ''}) -> Uint8Array(${bytes.length})\n`);
return bytes;
}
}

const UTF8Names = ["unicode-1-1-utf-8", "utf-8", "utf8"];

/**
* minimal utf-8 TextDecoder
* no support for fatal, stream, etc.
*
* ref https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder
*/
export class TextDecoder {
/**
* @param {string=} utfLabel optional name for UTF-8
* @param {*} options fatal is not supported
*/
constructor(utfLabel, options) {
if (utfLabel & !UTF8Names.includes(utfLabel)) {
throw new TypeError(utfLabel);
}
if (options && options.fatal) {
throw new TypeError('fatal not supported');
}
}
/**
* @param {Uint8Array} bytes
* @param {*} options stream is not supported
*/
decode(bytes, options) {
if (options && options.stream) {
throw new TypeError('stream is unsupported');
}
if (!(bytes instanceof Uint8Array)) {
throw new TypeError('arg must be Uint8Array');
}
return utf8_decode(bytes.buffer);
}
}

function utf8_encode(string) @ "xs_utf8_encode";
function utf8_decode(buffer) @ "xs_utf8_decode";
89 changes: 47 additions & 42 deletions modules/network/http/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,10 @@ function server(message, value, etc) {
delete this.line;

let request = this.callback(Server.headersComplete); // headers complete... let's see what to do with the request body
if ('upgrade' === request) {
return;
}

if (false === request)
delete this.total; // ignore request body and just send response

Expand Down Expand Up @@ -651,53 +655,54 @@ function server(message, value, etc) {
let first;

if (7 === this.state) {
let response = this.callback(Server.prepareResponse); // prepare response

let parts = [];
let status = (!response || (undefined === response.status)) ? 200 : response.status;
let message = (!response || (undefined === response.reason)) ? reason(status) : response.reason.toString();
parts.push("HTTP/1.1 ", status.toString(), " ", message, "\r\n",
"connection: ", "close\r\n");

if (response) {
let byteLength;

for (let i = 0, headers = response.headers; headers && (i < headers.length); i += 2) {
parts.push(headers[i], ": ", headers[i + 1].toString(), "\r\n");
if ("content-length" == headers[i].toLowerCase())
byteLength = parseInt(headers[i + 1]);
}
let responseP = Promise.resolve(this.callback(Server.prepareResponse)); // prepare response
responseP.then(response => {
let parts = [];
let status = (!response || (undefined === response.status)) ? 200 : response.status;
let message = (!response || (undefined === response.reason)) ? reason(status) : response.reason.toString();
parts.push("HTTP/1.1 ", status.toString(), " ", message, "\r\n",
"connection: ", "close\r\n");

if (response) {
let byteLength;

for (let i = 0, headers = response.headers; headers && (i < headers.length); i += 2) {
parts.push(headers[i], ": ", headers[i + 1].toString(), "\r\n");
if ("content-length" == headers[i].toLowerCase())
byteLength = parseInt(headers[i + 1]);
}

this.body = response.body;
if (true === response.body) {
if (undefined === byteLength) {
this.flags = 2;
parts.push("transfer-encoding: chunked\r\n");
this.body = response.body;
if (true === response.body) {
if (undefined === byteLength) {
this.flags = 2;
parts.push("transfer-encoding: chunked\r\n");
}
else
this.flags = 4;
}
else {
this.flags = 1;
let count = 0;
if (this.body)
count = ("string" === typeof this.body) ? this.body.length : this.body.byteLength; //@@ utf-8 hell
parts.push("content-length: ", count.toString(), "\r\n");
}
else
this.flags = 4;
}
else {
this.flags = 1;
let count = 0;
if (this.body)
count = ("string" === typeof this.body) ? this.body.length : this.body.byteLength; //@@ utf-8 hell
parts.push("content-length: ", count.toString(), "\r\n");
}
}
else
parts.push("content-length: 0\r\n");
parts.push("\r\n");
socket.write.apply(socket, parts);
else
parts.push("content-length: 0\r\n");
parts.push("\r\n");
socket.write.apply(socket, parts);

this.state = 8;
first = true;
this.state = 8;
first = true;

if (this.body && (true !== this.body)) {
let count = ("string" === typeof this.body) ? this.body.length : this.body.byteLength;
if (count > (socket.write() - ((2 & this.flags) ? 8 : 0)))
return;
}
if (this.body && (true !== this.body)) {
let count = ("string" === typeof this.body) ? this.body.length : this.body.byteLength;
if (count > (socket.write() - ((2 & this.flags) ? 8 : 0)))
return;
}
});
}
if (8 === this.state) {
let body = this.body;
Expand Down
2 changes: 1 addition & 1 deletion modules/network/socket/lin/modSocket.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
#define kRAW (2)

#define kRxBufferSize 1024
#define kTxBufferSize 1024
#define kTxBufferSize 4096

typedef struct xsSocketRecord xsSocketRecord;
typedef xsSocketRecord *xsSocket;
Expand Down
Loading