From 3e51605bc9ca98522fc21a0673e690105f48da98 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 13 Jul 2018 03:24:07 -0400 Subject: [PATCH] Execute JS for the first time in Rust rewrite. Implements code_fetch handler in Rust. Add ability to embed string assets (for typescript declaration files) Remove deno_cc and deno_cc_nosnapshot targets. --- .travis.yml | 8 +- BUILD.gn | 35 +----- js/assets.ts | 51 +++++++++ js/main.ts | 17 +-- js/os.ts | 32 +++--- js/runtime.ts | 43 ++++++-- js/util.ts | 2 +- src/binding.cc | 12 +++ src/handlers.h | 10 +- src/handlers.rs | 241 +++++++++++++++++++++++++++++++++++++++--- src/internal.h | 3 + src/main.cc | 104 ------------------ src/main.rs | 10 +- src/reply.cc | 120 +++++++++++++++++++++ src/reply.h | 29 +++++ testdata/001_hello.js | 1 + testdata/002_hello.ts | 1 + 17 files changed, 514 insertions(+), 205 deletions(-) create mode 100644 js/assets.ts delete mode 100644 src/main.cc create mode 100644 src/reply.cc create mode 100644 src/reply.h create mode 100644 testdata/001_hello.js create mode 100644 testdata/002_hello.ts diff --git a/.travis.yml b/.travis.yml index 2d978997ae..1549df3f44 100644 --- a/.travis.yml +++ b/.travis.yml @@ -47,7 +47,7 @@ script: - ./tools/lint.py - $BUILD_PATH/test_cc - $BUILD_PATH/handlers_test - - $BUILD_PATH/deno_cc foo bar - - $BUILD_PATH/deno_cc_nosnapshot foo bar - - $BUILD_PATH/deno meow - - $BUILD_PATH/deno_nosnapshot meow + - $BUILD_PATH/deno ./testdata/001_hello.js + - $BUILD_PATH/deno ./testdata/002_hello.ts + - $BUILD_PATH/deno_nosnapshot ./testdata/001_hello.js + - $BUILD_PATH/deno_nosnapshot ./testdata/002_hello.ts diff --git a/BUILD.gn b/BUILD.gn index e77f6bd01e..33cb4f7bd4 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -8,8 +8,6 @@ group("all") { testonly = true deps = [ ":deno", - ":deno_cc", - ":deno_cc_nosnapshot", ":deno_nosnapshot", ":handlers_test", ":test_cc", @@ -61,35 +59,6 @@ rust_test("handlers_test") { ] } -executable("deno_cc") { - sources = [ - "src/main.cc", - ] - deps = [ - ":flatbufferjs", - ":handlers", - ":libdeno", - ":msg_cpp", - ] - configs += [ ":deno_config" ] -} - -# This target is for fast incremental development. -# When modifying the javascript runtime, this target will not go through the -# extra process of building a snapshot and instead load the bundle from disk. -executable("deno_cc_nosnapshot") { - sources = [ - "src/main.cc", - ] - deps = [ - ":flatbufferjs", - ":handlers", - ":libdeno_nosnapshot", - ":msg_cpp", - ] - configs += [ ":deno_config" ] -} - executable("test_cc") { testonly = true sources = [ @@ -154,9 +123,12 @@ v8_source_set("deno_bindings") { sources = [ "src/flatbuffer_builder.cc", "src/flatbuffer_builder.h", + "src/reply.cc", + "src/reply.h", ] deps = [ ":deno_base", + ":handlers", ":msg_cpp", ] public_deps = [ @@ -184,6 +156,7 @@ flatbuffer("msg_cpp") { run_node("bundle") { out_dir = "$target_gen_dir/bundle/" sources = [ + "js/assets.ts", "js/console.ts", "js/deno.d.ts", "js/dispatch.ts", diff --git a/js/assets.ts b/js/assets.ts new file mode 100644 index 0000000000..850fc6c725 --- /dev/null +++ b/js/assets.ts @@ -0,0 +1,51 @@ +// Copyright 2018 Ryan Dahl +// All rights reserved. MIT License. + +// This file is formatted as it is because we are using the fact that Parcel +// statically evaluates fs.readFileSync. +import { readFileSync } from "fs"; + +// tslint:disable:max-line-length +// prettier-ignore +export const assetSourceCode: { [key: string]: string } = { + "deno.d.ts": readFileSync(__dirname + "/deno.d.ts", "utf8"), + "lib.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/lib.d.ts", "utf8"), + //"lib.dom.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/lib.dom.d.ts", "utf8"), + "lib.dom.iterable.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/lib.dom.iterable.d.ts", "utf8"), + "lib.es2015.collection.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/lib.es2015.collection.d.ts", "utf8"), + "lib.es2015.core.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/lib.es2015.core.d.ts", "utf8"), + //"lib.es2015.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/lib.es2015.d.ts", "utf8"), + "lib.es2015.generator.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/lib.es2015.generator.d.ts", "utf8"), + "lib.es2015.iterable.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/lib.es2015.iterable.d.ts", "utf8"), + "lib.es2015.promise.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/lib.es2015.promise.d.ts", "utf8"), + "lib.es2015.proxy.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/lib.es2015.proxy.d.ts", "utf8"), + "lib.es2015.reflect.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/lib.es2015.reflect.d.ts", "utf8"), + "lib.es2015.symbol.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/lib.es2015.symbol.d.ts", "utf8"), + "lib.es2015.symbol.wellknown.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts", "utf8"), + "lib.es2016.array.include.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/lib.es2016.array.include.d.ts", "utf8"), + //"lib.es2016.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/lib.es2016.d.ts", "utf8"), + //"lib.es2016.full.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/lib.es2016.full.d.ts", "utf8"), + //"lib.es2017.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/lib.es2017.d.ts", "utf8"), + //"lib.es2017.full.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/lib.es2017.full.d.ts", "utf8"), + "lib.es2017.intl.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/lib.es2017.intl.d.ts", "utf8"), + "lib.es2017.object.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/lib.es2017.object.d.ts", "utf8"), + "lib.es2017.sharedmemory.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts", "utf8"), + "lib.es2017.string.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/lib.es2017.string.d.ts", "utf8"), + "lib.es2017.typedarrays.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts", "utf8"), + "lib.es2018.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/lib.es2018.d.ts", "utf8"), + //"lib.es2018.full.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/lib.es2018.full.d.ts", "utf8"), + "lib.es2018.promise.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/lib.es2018.promise.d.ts", "utf8"), + "lib.es2018.regexp.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/lib.es2018.regexp.d.ts", "utf8"), + //"lib.es5.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/lib.es5.d.ts", "utf8"), + //"lib.es6.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/lib.es6.d.ts", "utf8"), + "lib.esnext.array.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/lib.esnext.array.d.ts", "utf8"), + "lib.esnext.asynciterable.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/lib.esnext.asynciterable.d.ts", "utf8"), + "lib.esnext.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/lib.esnext.d.ts", "utf8"), + //"lib.esnext.full.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/lib.esnext.full.d.ts", "utf8"), + //"lib.scripthost.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/lib.scripthost.d.ts", "utf8"), + //"lib.webworker.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/lib.webworker.d.ts", "utf8"), + //"protocol.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/protocol.d.ts", "utf8"), + //"tsserverlibrary.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/tsserverlibrary.d.ts", "utf8"), + "typescript.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/typescript.d.ts", "utf8"), + //"typescriptServices.d.ts": readFileSync(__dirname + "/../third_party/node_modules/typescript/lib/typescriptServices.d.ts", "utf8"), +}; diff --git a/js/main.ts b/js/main.ts index 379b69c97b..c140304ef3 100644 --- a/js/main.ts +++ b/js/main.ts @@ -1,12 +1,9 @@ // tslint:disable-next-line:no-reference /// -import * as ts from "typescript"; - import { flatbuffers } from "flatbuffers"; import { deno as fbs } from "./msg_generated"; -import { assert } from "./util"; - -// import * as runtime from "./runtime"; +import { assert, log } from "./util"; +import * as runtime from "./runtime"; const globalEval = eval; const window = globalEval("this"); @@ -31,8 +28,6 @@ function startMsg(cmdId: number): Uint8Array { } window["denoMain"] = () => { - deno.print(`ts.version: ${ts.version}`); - // First we send an empty "Start" message to let the privlaged side know we // are ready. The response should be a "StartRes" message containing the CLI // argv and other info. @@ -55,17 +50,15 @@ window["denoMain"] = () => { assert(base.msg(startResMsg) != null); const cwd = startResMsg.cwd(); - deno.print(`cwd: ${cwd}`); + log("cwd", cwd); const argv: string[] = []; for (let i = 0; i < startResMsg.argvLength(); i++) { argv.push(startResMsg.argv(i)); } - deno.print(`argv ${argv}`); + log("argv", argv); - /* TODO(ry) Uncomment to test further message passing. - const inputFn = argv[0]; + const inputFn = argv[1]; const mod = runtime.resolveModule(inputFn, `${cwd}/`); mod.compileAndRun(); - */ }; diff --git a/js/os.ts b/js/os.ts index 0c8ebede98..e4263009fe 100644 --- a/js/os.ts +++ b/js/os.ts @@ -2,7 +2,8 @@ // All rights reserved. MIT License. import { ModuleInfo } from "./types"; import { deno as fbs } from "./msg_generated"; -import { assert, typedArrayToArrayBuffer } from "./util"; +import { assert } from "./util"; +import * as util from "./util"; import { flatbuffers } from "flatbuffers"; export function exit(exitCode = 0): void { @@ -19,8 +20,7 @@ export function codeFetch( moduleSpecifier: string, containingFile: string ): ModuleInfo { - console.log("Hello from codeFetch"); - + util.log("os.ts codeFetch", moduleSpecifier, containingFile); // Send CodeFetch message const builder = new flatbuffers.Builder(); const moduleSpecifier_ = builder.createString(moduleSpecifier); @@ -33,23 +33,23 @@ export function codeFetch( fbs.Base.addMsg(builder, msg); fbs.Base.addMsgType(builder, fbs.Any.CodeFetch); builder.finish(fbs.Base.endBase(builder)); - const payload = typedArrayToArrayBuffer(builder.asUint8Array()); - const resBuf = deno.send("x", payload); - - console.log("CodeFetch sent"); - + const resBuf = deno.send(builder.asUint8Array()); // Process CodeFetchRes const bb = new flatbuffers.ByteBuffer(new Uint8Array(resBuf)); const baseRes = fbs.Base.getRootAsBase(bb); + if (fbs.Any.NONE === baseRes.msgType()) { + throw Error(baseRes.error()); + } assert(fbs.Any.CodeFetchRes === baseRes.msgType()); const codeFetchRes = new fbs.CodeFetchRes(); assert(baseRes.msg(codeFetchRes) != null); - return { + const r = { moduleName: codeFetchRes.moduleName(), filename: codeFetchRes.filename(), sourceCode: codeFetchRes.sourceCode(), outputCode: codeFetchRes.outputCode() }; + return r; } export function codeCache( @@ -57,28 +57,22 @@ export function codeCache( sourceCode: string, outputCode: string ): void { + util.log("os.ts codeCache", filename, sourceCode, outputCode); const builder = new flatbuffers.Builder(); - const filename_ = builder.createString(filename); const sourceCode_ = builder.createString(sourceCode); const outputCode_ = builder.createString(outputCode); - fbs.CodeCache.startCodeCache(builder); fbs.CodeCache.addFilename(builder, filename_); fbs.CodeCache.addSourceCode(builder, sourceCode_); fbs.CodeCache.addOutputCode(builder, outputCode_); const msg = fbs.CodeCache.endCodeCache(builder); - fbs.Base.startBase(builder); fbs.Base.addMsg(builder, msg); + fbs.Base.addMsgType(builder, fbs.Any.CodeCache); builder.finish(fbs.Base.endBase(builder)); - - // Maybe need to do another step? - // Base.finishBaseBuffer(builder, base); - - const payload = typedArrayToArrayBuffer(builder.asUint8Array()); - const resBuf = deno.send("x", payload); - assert(resBuf === null); + const resBuf = deno.send(builder.asUint8Array()); + assert(resBuf == null); } export function readFileSync(filename: string): Uint8Array { diff --git a/js/runtime.ts b/js/runtime.ts index 9570dda1c9..d234cdd858 100644 --- a/js/runtime.ts +++ b/js/runtime.ts @@ -11,6 +11,7 @@ import * as ts from "typescript"; import * as util from "./util"; import { log } from "./util"; +import { assetSourceCode } from "./assets"; import * as os from "./os"; //import * as sourceMaps from "./v8_source_maps"; import { window, globalEval } from "./globals"; @@ -22,9 +23,14 @@ const EOL = "\n"; export type AmdFactory = (...args: any[]) => undefined | object; export type AmdDefine = (deps: string[], factory: AmdFactory) => void; -/* // Uncaught exceptions are sent to window.onerror by the privlaged binding. -window.onerror = (message, source, lineno, colno, error) => { +window.onerror = ( + message: string, + source: string, + lineno: number, + colno: number, + error: Error +) => { // TODO Currently there is a bug in v8_source_maps.ts that causes a segfault // if it is used within window.onerror. To workaround we uninstall the // Error.prepareStackTrace handler. Users will get unmapped stack traces on @@ -33,7 +39,6 @@ window.onerror = (message, source, lineno, colno, error) => { console.log(error.message, error.stack); os.exit(1); }; -*/ /* export function setup(mainJs: string, mainMap: string): void { @@ -147,11 +152,31 @@ export function resolveModule( ): null | FileModule { util.log("resolveModule", { moduleSpecifier, containingFile }); util.assert(moduleSpecifier != null && moduleSpecifier.length > 0); - // We ask golang to sourceCodeFetch. It will load the sourceCode and if - // there is any outputCode cached, it will return that as well. - const fetchResponse = os.codeFetch(moduleSpecifier, containingFile); - const { filename, sourceCode, outputCode } = fetchResponse; - if (sourceCode.length === 0) { + let filename: string, sourceCode: string, outputCode: string; + if ( + moduleSpecifier.startsWith("/$asset$/") || + containingFile.startsWith("/$asset$/") + ) { + // Assets are compiled into the runtime javascript bundle. + const assetName = moduleSpecifier.split("/").pop(); + sourceCode = assetSourceCode[assetName]; + filename = "/$asset$/" + assetName; + } else { + // We query Rust with a CodeFetch message. It will load the sourceCode, and + // if there is any outputCode cached, will return that as well. + let fetchResponse; + try { + fetchResponse = os.codeFetch(moduleSpecifier, containingFile); + } catch (e) { + // TODO Only catch "no such file or directory" errors. Need error codes. + util.log("os.codeFetch error ignored", e.message); + return null; + } + filename = fetchResponse.filename; + sourceCode = fetchResponse.sourceCode; + outputCode = fetchResponse.outputCode; + } + if (sourceCode == null || sourceCode.length === 0) { return null; } util.log("resolveModule sourceCode length ", sourceCode.length); @@ -293,7 +318,7 @@ class TypeScriptHost implements ts.LanguageServiceHost { } getDefaultLibFileName(options: ts.CompilerOptions): string { - const fn = ts.getDefaultLibFileName(options); + const fn = "lib.d.ts"; // ts.getDefaultLibFileName(options); util.log("getDefaultLibFileName", fn); const m = resolveModule(fn, "/$asset$/"); return m.fileName; diff --git a/js/util.ts b/js/util.ts index 9f87ab63d4..7216f94913 100644 --- a/js/util.ts +++ b/js/util.ts @@ -2,7 +2,7 @@ // All rights reserved. MIT License. //import { debug } from "./main"; -const debug = true; +const debug = false; import { TypedArray } from "./types"; diff --git a/src/binding.cc b/src/binding.cc index 2c4b6f7227..2067f10238 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -301,8 +301,20 @@ void deno_init() { const char* deno_v8_version() { return v8::V8::GetVersion(); } +// TODO(ry) Remove these when we call deno_reply_start from Rust. +static char** global_argv; +static int global_argc; +char** deno_argv() { return global_argv; } +int deno_argc() { return global_argc; } + void deno_set_flags(int* argc, char** argv) { v8::V8::SetFlagsFromCommandLine(argc, argv, true); + // TODO(ry) Remove these when we call deno_reply_start from Rust. + global_argc = *argc; + global_argv = reinterpret_cast(malloc(*argc * sizeof(char*))); + for (int i = 0; i < *argc; i++) { + global_argv[i] = strdup(argv[i]); + } } const char* deno_last_exception(Deno* d) { return d->last_exception.c_str(); } diff --git a/src/handlers.h b/src/handlers.h index b398eca61e..a95bc7265e 100644 --- a/src/handlers.h +++ b/src/handlers.h @@ -2,10 +2,12 @@ // All rights reserved. MIT License. #ifndef HANDLERS_H_ #define HANDLERS_H_ -extern "C" { -#include -void handle_code_fetch(uint32_t cmd_id, const char* module_specifier, +#include +#include "deno.h" + +extern "C" { +void handle_code_fetch(Deno* d, uint32_t cmd_id, const char* module_specifier, const char* containing_file); -} +} // extern "C" #endif // HANDLERS_H_ diff --git a/src/handlers.rs b/src/handlers.rs index 5e386df312..4c4a791ea4 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -1,36 +1,247 @@ // Copyright 2018 Ryan Dahl // All rights reserved. MIT License. extern crate libc; +#[macro_use] +extern crate log; extern crate url; use libc::c_char; +use libc::uint32_t; use std::ffi::CStr; +use std::ffi::CString; +use std::fs::File; +use std::io::Read; +use std::path::Path; use url::Url; -fn string_from_ptr(ptr: *const c_char) -> String { - let cstr = unsafe { CStr::from_ptr(ptr as *const i8) }; - String::from(cstr.to_str().unwrap()) +// TODO(ry) Share this with the def in src/main.rs. +#[repr(C)] +pub struct DenoC { + _unused: [u8; 0], } +// TODO(ry) Share this extern block with those in main.rs. +// See src/reply.h +extern "C" { + pub fn deno_reply_error(d: *const DenoC, cmd_id: uint32_t, msg: *const c_char); + pub fn deno_reply_null(d: *const DenoC, cmd_id: uint32_t); + pub fn deno_reply_code_fetch( + d: *const DenoC, + cmd_id: uint32_t, + module_name: *const c_char, + filename: *const c_char, + source_code: *const c_char, + output_code: *const c_char, + ); +} + +// TODO(ry) SRC_DIR is just a placeholder for future caching functionality. +static SRC_DIR: &str = "/Users/rld/.deno/src/"; +const ASSET_PREFIX: &str = "/$asset$/"; + #[test] fn test_url() { let issue_list_url = Url::parse("https://github.com/rust-lang").unwrap(); assert!(issue_list_url.scheme() == "https"); } -#[no_mangle] -pub extern "C" fn handle_code_fetch( - cmd_id: u32, - module_specifier: *const c_char, - containing_file: *const c_char, -) { - let module_specifier = string_from_ptr(module_specifier); - let containing_file = string_from_ptr(containing_file); +fn string_from_ptr(ptr: *const c_char) -> String { + let cstr = unsafe { CStr::from_ptr(ptr as *const i8) }; + String::from(cstr.to_str().unwrap()) +} - println!( - "handle_code_fetch. cmd_id = {} module_specifier = {} containing_file = {}", - cmd_id, module_specifier, containing_file +fn as_cstring(s: &String) -> CString { + CString::new(s.as_str()).unwrap() +} + +// Prototype: https://github.com/ry/deno/blob/golang/os.go#L56-L68 +#[allow(dead_code)] +fn src_file_to_url>(filename: P) -> String { + assert!(SRC_DIR.len() > 0, "SRC_DIR shouldn't be empty"); + + let filename = filename.as_ref().to_path_buf(); + let src = (SRC_DIR.as_ref() as &Path).to_path_buf(); + + if filename.starts_with(&src) { + let rest = filename.strip_prefix(&src).unwrap(); + "http://".to_string() + rest.to_str().unwrap() + } else { + String::from(filename.to_str().unwrap()) + } +} + +#[test] +fn test_src_file_to_url() { + assert_eq!("hello", src_file_to_url("hello")); + assert_eq!("/hello", src_file_to_url("/hello")); + let x = SRC_DIR.to_string() + "hello"; + assert_eq!("http://hello", src_file_to_url(&x)); + let x = SRC_DIR.to_string() + "/hello"; + assert_eq!("http://hello", src_file_to_url(&x)); +} + +// Prototype: https://github.com/ry/deno/blob/golang/os.go#L70-L98 +// Returns (module name, local filename) +fn resolve_module( + module_specifier: &String, + containing_file: &String, +) -> Result<(String, String), url::ParseError> { + info!( + "resolve_module before module_specifier {} containing_file {}", + module_specifier, containing_file ); - unimplemented!(); + //let module_specifier = src_file_to_url(module_specifier); + //let containing_file = src_file_to_url(containing_file); + //let base_url = Url::parse(&containing_file)?; + + let j: Url = if containing_file.as_str().ends_with("/") { + let base = Url::from_directory_path(&containing_file).unwrap(); + base.join(module_specifier)? + } else if containing_file == "." { + Url::from_file_path(module_specifier).unwrap() + } else { + let base = Url::from_file_path(&containing_file).unwrap(); + base.join(module_specifier)? + }; + + let p = j.to_file_path() + .unwrap() + .into_os_string() + .into_string() + .unwrap(); + + let module_name = p.to_string(); + let filename = p.to_string(); + + Ok((module_name, filename)) +} + +// https://github.com/ry/deno/blob/golang/os_test.go#L16-L87 +#[test] +fn test_resolve_module() { + let test_cases = [ + ( + "./subdir/print_hello.ts", + "/Users/rld/go/src/github.com/ry/deno/testdata/006_url_imports.ts", + "/Users/rld/go/src/github.com/ry/deno/testdata/subdir/print_hello.ts", + "/Users/rld/go/src/github.com/ry/deno/testdata/subdir/print_hello.ts", + ), + ( + "testdata/001_hello.js", + "/Users/rld/go/src/github.com/ry/deno/", + "/Users/rld/go/src/github.com/ry/deno/testdata/001_hello.js", + "/Users/rld/go/src/github.com/ry/deno/testdata/001_hello.js", + ), + ( + "/Users/rld/src/deno/hello.js", + ".", + "/Users/rld/src/deno/hello.js", + "/Users/rld/src/deno/hello.js", + ), + /* + ( + "http://localhost:4545/testdata/subdir/print_hello.ts", + "/Users/rld/go/src/github.com/ry/deno/testdata/006_url_imports.ts", + "http://localhost:4545/testdata/subdir/print_hello.ts", + path.Join(SrcDir, "localhost:4545/testdata/subdir/print_hello.ts"), + ), + ( + path.Join(SrcDir, "unpkg.com/liltest@0.0.5/index.ts"), + ".", + "http://unpkg.com/liltest@0.0.5/index.ts", + path.Join(SrcDir, "unpkg.com/liltest@0.0.5/index.ts"), + ), + ( + "./util", + path.Join(SrcDir, "unpkg.com/liltest@0.0.5/index.ts"), + "http://unpkg.com/liltest@0.0.5/util", + path.Join(SrcDir, "unpkg.com/liltest@0.0.5/util"), + ), + */ + ]; + for &test in test_cases.iter() { + let module_specifier = String::from(test.0); + let containing_file = String::from(test.1); + let (module_name, filename) = resolve_module(&module_specifier, &containing_file).unwrap(); + assert_eq!(module_name, test.2); + assert_eq!(filename, test.3); + } +} + +// https://github.com/ry/deno/blob/golang/os.go#L100-L154 +#[no_mangle] +pub extern "C" fn handle_code_fetch( + d: *const DenoC, + cmd_id: u32, + module_specifier_: *const c_char, + containing_file_: *const c_char, +) { + // TODO(ry) Move this to main. + log::set_max_level(log::LevelFilter::Debug); + + let module_specifier = string_from_ptr(module_specifier_); + let containing_file = string_from_ptr(containing_file_); + + let result = resolve_module(&module_specifier, &containing_file); + if result.is_err() { + let err = result.unwrap_err(); + let errmsg = format!("{} {} {}", err, module_specifier, containing_file); + let errmsg_c = as_cstring(&errmsg); + unsafe { deno_reply_error(d, cmd_id, errmsg_c.as_ptr()) }; + return; + } + let (module_name, filename) = result.unwrap(); + + let mut source_code = String::new(); + + debug!( + "code_fetch. module_name = {} module_specifier = {} containing_file = {} filename = {}", + module_name, module_specifier, containing_file, filename + ); + + if is_remote(&module_name) { + unimplemented!(); + } else if module_name.starts_with(ASSET_PREFIX) { + assert!(false, "Asset resolution should be done in JS, not Rust."); + } else { + assert!( + module_name == filename, + "if a module isn't remote, it should have the same filename" + ); + let result = File::open(&filename); + if result.is_err() { + let err = result.unwrap_err(); + let errmsg = format!("{} {}", err, filename); + let errmsg_c = as_cstring(&errmsg); + unsafe { deno_reply_error(d, cmd_id, errmsg_c.as_ptr()) }; + return; + } + let mut f = result.unwrap(); + let result = f.read_to_string(&mut source_code); + if result.is_err() { + let err = result.unwrap_err(); + let errmsg = format!("{} {}", err, filename); + let errmsg_c = as_cstring(&errmsg); + unsafe { deno_reply_error(d, cmd_id, errmsg_c.as_ptr()) }; + return; + } + } + + let output_code = String::new(); //load_output_code_cache(filename, source_code); + + unsafe { + deno_reply_code_fetch( + d, + cmd_id, + as_cstring(&module_name).as_ptr(), + as_cstring(&filename).as_ptr(), + as_cstring(&source_code).as_ptr(), + as_cstring(&output_code).as_ptr(), + ) + } +} + +fn is_remote(_module_name: &String) -> bool { + false } diff --git a/src/internal.h b/src/internal.h index 0ec0ea416b..914079d9a7 100644 --- a/src/internal.h +++ b/src/internal.h @@ -18,6 +18,9 @@ struct deno_s { deno_recv_cb cb; void* data; }; +// TODO(ry) Remove these when we call deno_reply_start from Rust. +char** deno_argv(); +int deno_argc(); } namespace deno { diff --git a/src/main.cc b/src/main.cc deleted file mode 100644 index 81b8a27103..0000000000 --- a/src/main.cc +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2018 Ryan Dahl -// All rights reserved. MIT License. -#include -#include -#include - -#ifdef _WIN32 -#include -#else -#include -#endif - -#include "deno.h" -#include "flatbuffer_builder.h" -#include "flatbuffers/flatbuffers.h" -#include "src/handlers.h" -#include "src/msg_generated.h" -#include "third_party/v8/src/base/logging.h" - -namespace deno { - -static char** global_argv; -static int global_argc; - -// Sends StartRes message -void HandleStart(Deno* d, uint32_t cmd_id) { - deno::FlatBufferBuilder builder; - - char cwdbuf[1024]; - // TODO(piscisaureus): support unicode on windows. - getcwd(cwdbuf, sizeof(cwdbuf)); - auto start_cwd = builder.CreateString(cwdbuf); - - std::vector> args; - for (int i = 0; i < global_argc; ++i) { - args.push_back(builder.CreateString(global_argv[i])); - } - - auto start_argv = builder.CreateVector(args); - auto start_msg = CreateStartRes(builder, start_cwd, start_argv); - auto base = CreateBase(builder, cmd_id, 0, Any_StartRes, start_msg.Union()); - builder.Finish(base); - deno_set_response(d, builder.ExportBuf()); -} - -void HandleCodeFetch(Deno* d, uint32_t cmd_id, const CodeFetch* msg) { - auto module_specifier = msg->module_specifier()->c_str(); - auto containing_file = msg->containing_file()->c_str(); - printf("HandleCodeFetch module_specifier = %s containing_file = %s\n", - module_specifier, containing_file); - // Call into rust. - handle_code_fetch(cmd_id, module_specifier, containing_file); -} - -void MessagesFromJS(Deno* d, deno_buf buf) { - flatbuffers::Verifier verifier(buf.data_ptr, buf.data_len); - DCHECK(verifier.VerifyBuffer()); - - auto base = flatbuffers::GetRoot(buf.data_ptr); - auto cmd_id = base->cmdId(); - auto msg_type = base->msg_type(); - const char* msg_type_name = EnumNamesAny()[msg_type]; - printf("MessagesFromJS cmd_id = %d, msg_type = %d, msg_type_name = %s\n", - cmd_id, msg_type, msg_type_name); - switch (msg_type) { - case Any_Start: - HandleStart(d, base->cmdId()); - break; - - case Any_CodeFetch: - HandleCodeFetch(d, base->cmdId(), base->msg_as_CodeFetch()); - break; - - case Any_NONE: - CHECK(false && "Got message with msg_type == Any_NONE"); - break; - - default: - printf("Unhandled message %s\n", msg_type_name); - CHECK(false && "Unhandled message"); - break; - } -} - -int deno_main(int argc, char** argv) { - deno_init(); - - deno_set_flags(&argc, argv); - global_argv = argv; - global_argc = argc; - - Deno* d = deno_new(NULL, MessagesFromJS); - bool r = deno_execute(d, "deno_main.js", "denoMain();"); - if (!r) { - printf("%s\n", deno_last_exception(d)); - exit(1); - } - deno_delete(d); - return 0; -} - -} // namespace deno - -int main(int argc, char** argv) { return deno::deno_main(argc, argv); } diff --git a/src/main.rs b/src/main.rs index 030859d5ec..10b1aef81f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,7 +20,7 @@ struct DenoC { _unused: [u8; 0], } -type DenoRecvCb = extern "C" fn(d: *const DenoC, buf: deno_buf); +type DenoRecvCb = unsafe extern "C" fn(d: *const DenoC, buf: deno_buf); #[link(name = "deno", kind = "static")] extern "C" { @@ -36,6 +36,8 @@ extern "C" { fn deno_set_response(d: *const DenoC, buf: deno_buf); fn deno_execute(d: *const DenoC, js_filename: *const c_char, js_source: *const c_char) -> c_int; + + fn deno_handle_msg_from_js(d: *const DenoC, buf: deno_buf); } // Pass the command line arguments to v8. @@ -73,10 +75,6 @@ fn set_flags() -> Vec { .collect::>() } -extern "C" fn on_message(_d: *const DenoC, _buf: deno_buf) { - println!("got message in rust"); -} - type DenoException<'a> = &'a str; struct Deno { @@ -85,7 +83,7 @@ struct Deno { impl Deno { fn new() -> Deno { - let ptr = unsafe { deno_new(ptr::null(), on_message) }; + let ptr = unsafe { deno_new(ptr::null(), deno_handle_msg_from_js) }; Deno { ptr: ptr } } diff --git a/src/reply.cc b/src/reply.cc new file mode 100644 index 0000000000..7043292fd1 --- /dev/null +++ b/src/reply.cc @@ -0,0 +1,120 @@ +// Copyright 2018 Ryan Dahl +// All rights reserved. MIT License. + +// When Rust Flatbuffer support is complete this file should be ported +// to Rust and removed: https://github.com/google/flatbuffers/pull/3894 +#include +// getcwd +#ifdef _WIN32 +#include +#else +#include +#endif + +#include "flatbuffers/flatbuffers.h" +#include "src/deno.h" +#include "src/flatbuffer_builder.h" +#include "src/handlers.h" +#include "src/internal.h" +#include "src/msg_generated.h" +#include "src/reply.h" +#include "third_party/v8/src/base/logging.h" + +extern "C" { + +void deno_reply_error(Deno* d, uint32_t cmd_id, const char* error_msg) { + // printf("deno_reply_error: %s\n", error_msg); + deno::FlatBufferBuilder builder; + auto error_msg_ = error_msg ? builder.CreateString(error_msg) : 0; + auto base = deno::CreateBase(builder, cmd_id, error_msg_); + builder.Finish(base); + deno_set_response(d, builder.ExportBuf()); +} + +void deno_reply_null(Deno* d, uint32_t cmd_id) { + deno_reply_error(d, cmd_id, nullptr); +} + +void deno_reply_code_fetch(Deno* d, uint32_t cmd_id, const char* module_name, + const char* filename, const char* source_code, + const char* output_code) { + deno::FlatBufferBuilder builder; + auto module_name_ = builder.CreateString(module_name); + auto filename_ = builder.CreateString(filename); + auto source_code_ = builder.CreateString(source_code); + auto output_code_ = builder.CreateString(output_code); + auto code_fetch_res = deno::CreateCodeFetchRes( + builder, module_name_, filename_, source_code_, output_code_); + auto base = deno::CreateBase(builder, cmd_id, 0, deno::Any_CodeFetchRes, + code_fetch_res.Union()); + builder.Finish(base); + deno_set_response(d, builder.ExportBuf()); +} + +void deno_reply_start(Deno* d, uint32_t cmd_id, int argc, char* argv[], + char* cwd) { + deno::FlatBufferBuilder builder; + auto start_cwd = builder.CreateString(cwd); + std::vector> args; + for (int i = 0; i < argc; ++i) { + args.push_back(builder.CreateString(argv[i])); + } + auto start_argv = builder.CreateVector(args); + auto start_msg = deno::CreateStartRes(builder, start_cwd, start_argv); + auto base = deno::CreateBase(builder, cmd_id, 0, deno::Any_StartRes, + start_msg.Union()); + builder.Finish(base); + deno_set_response(d, builder.ExportBuf()); +} + +void deno_handle_msg_from_js(Deno* d, deno_buf buf) { + flatbuffers::Verifier verifier(buf.data_ptr, buf.data_len); + DCHECK(verifier.VerifyBuffer()); + + auto base = flatbuffers::GetRoot(buf.data_ptr); + auto cmd_id = base->cmdId(); + auto msg_type = base->msg_type(); + const char* msg_type_name = deno::EnumNamesAny()[msg_type]; + switch (msg_type) { + case deno::Any_Start: { + char cwdbuf[1024]; + // TODO(piscisaureus): support unicode on windows. + getcwd(cwdbuf, sizeof(cwdbuf)); + deno_reply_start(d, cmd_id, deno_argc(), deno_argv(), cwdbuf); + break; + } + + case deno::Any_CodeFetch: { + auto msg = base->msg_as_CodeFetch(); + auto module_specifier = msg->module_specifier()->c_str(); + auto containing_file = msg->containing_file()->c_str(); + handle_code_fetch(d, cmd_id, module_specifier, containing_file); + break; + } + + case deno::Any_CodeCache: { + // TODO(ry) Call into rust. + /* + auto filename = msg->filename()->c_str(); + auto source_code = msg->source_code()->c_str(); + auto output_code = msg->output_code()->c_str(); + printf( + "HandleCodeCache (not implemeneted) filename %s source_code %s " + "output_code %s\n", + filename, source_code, output_code); + */ + break; + } + + case deno::Any_NONE: + CHECK(false && "Got message with msg_type == Any_NONE"); + break; + + default: + printf("Unhandled message %s\n", msg_type_name); + CHECK(false && "Unhandled message"); + break; + } +} + +} // extern "C" diff --git a/src/reply.h b/src/reply.h new file mode 100644 index 0000000000..aa2e61f8c9 --- /dev/null +++ b/src/reply.h @@ -0,0 +1,29 @@ +// Copyright 2018 Ryan Dahl +// All rights reserved. MIT License. + +// TODO(ry) This library handles parsing and sending Flatbuffers. It's written +// in C++ because flatbuffer support for Rust is not quite there. However, once +// flatbuffers are supported in Rust, all of this code should be ported back to +// Rust. + +#ifndef REPLY_H_ +#define REPLY_H_ + +#include +#include "deno.h" + +extern "C" { + +void deno_reply_null(Deno* d, uint32_t cmd_id); +void deno_reply_error(Deno* d, uint32_t cmd_id, const char* error_msg); + +void deno_reply_start(Deno* d, uint32_t cmd_id, int argc, char* argv[], + char* cwd); +void deno_reply_code_fetch(Deno* d, uint32_t cmd_id, const char* module_name, + const char* filename, const char* source_code, + const char* output_code); + +// Parse incoming messages with C++ Flatbuffers, call into rust handlers. +void deno_handle_msg_from_js(Deno* d, deno_buf buf); +} +#endif // REPLY_H_ diff --git a/testdata/001_hello.js b/testdata/001_hello.js new file mode 100644 index 0000000000..accefceba6 --- /dev/null +++ b/testdata/001_hello.js @@ -0,0 +1 @@ +console.log("Hello World"); diff --git a/testdata/002_hello.ts b/testdata/002_hello.ts new file mode 100644 index 0000000000..accefceba6 --- /dev/null +++ b/testdata/002_hello.ts @@ -0,0 +1 @@ +console.log("Hello World");