From b6f6d908d2a4da9088268109e59024688c920ac7 Mon Sep 17 00:00:00 2001 From: John Doty Date: Sat, 24 Jun 2023 17:49:41 -0700 Subject: [PATCH] [oden] OK now everything is TS and it type-checks --- oden-js/src/context.rs | 2 +- src/graphics.ts | 6 +- src/{main.js => main.ts} | 2 +- src/script.rs | 160 ++------------------------------------- src/script/typescript.rs | 154 +++++++++++++++++++++++++++++++++++++ types/graphics-core.d.ts | 2 + 6 files changed, 168 insertions(+), 158 deletions(-) rename src/{main.js => main.ts} (75%) create mode 100644 src/script/typescript.rs diff --git a/oden-js/src/context.rs b/oden-js/src/context.rs index 300a5915..22f75654 100644 --- a/oden-js/src/context.rs +++ b/oden-js/src/context.rs @@ -151,7 +151,7 @@ impl ContextRef { pub fn import_module(&self, name: &str, base: &str) -> Result { let name = CString::new(name)?; let base = CString::new(base)?; - let module = unsafe { sys::JS_RunModule(self.ctx, name.as_ptr(), base.as_ptr()) }; + let module = unsafe { sys::JS_RunModule(self.ctx, base.as_ptr(), name.as_ptr()) }; if module.is_null() { return Err(self.exception_error()); } diff --git a/src/graphics.ts b/src/graphics.ts index 944f99c3..b5c067c6 100644 --- a/src/graphics.ts +++ b/src/graphics.ts @@ -1,13 +1,17 @@ import * as core from "graphics-core"; +// Clear the screen to the specified color. r, g, and b should vary from 0 to +// 1. export function cls(r: number, g: number, b: number) { core.cls(r, g, b); } -export function print(...args: string[]) { +// Print a message to the console. +export function print(...args: unknown[]) { core.print(args.join(" ")); } +// Draw a sprite in the rectangle from x,y to export function spr( x: number, y: number, diff --git a/src/main.js b/src/main.ts similarity index 75% rename from src/main.js rename to src/main.ts index fd8099f1..46ad15b9 100644 --- a/src/main.js +++ b/src/main.ts @@ -1,4 +1,4 @@ -import { cls, print, spr } from "./src/graphics.ts"; +import { cls, print, spr } from "./graphics.ts"; export function init() { print("Hello world!"); diff --git a/src/script.rs b/src/script.rs index 80e4cb5a..dc761730 100644 --- a/src/script.rs +++ b/src/script.rs @@ -1,166 +1,16 @@ use oden_js::{ module::loader::{ModuleLoader, ModuleSource}, - Context, ContextRef, Error, Result, Runtime, Value, + Context, ContextRef, Result, Runtime, Value, }; -use std::cell::RefCell; use std::ffi::OsStr; use std::path::Path; -use std::rc::Rc; use std::sync::mpsc::{channel, Receiver}; -use swc_common::{ - self, comments::SingleThreadedComments, errors::Diagnostic, sync::Lrc, FileName, Globals, Mark, - SourceMap, GLOBALS, -}; -use swc_ecma_codegen::{text_writer::JsWriter, Emitter}; -use swc_ecma_parser::{lexer::Lexer, Parser, StringInput, Syntax}; -use swc_ecma_transforms_base::{fixer::fixer, hygiene::hygiene, resolver}; -use swc_ecma_transforms_typescript::strip; -use swc_ecma_visit::FoldWith; - pub mod graphics; use graphics::GraphicsCommand; -struct DiagnosticCollector { - cell: Rc>>, -} - -impl DiagnosticCollector { - pub fn into_handler(self) -> swc_common::errors::Handler { - swc_common::errors::Handler::with_emitter(true, false, Box::new(self)) - } -} - -impl swc_common::errors::Emitter for DiagnosticCollector { - fn emit(&mut self, db: &swc_common::errors::DiagnosticBuilder<'_>) { - use std::ops::Deref; - self.cell.borrow_mut().push(db.deref().clone()); - } -} - -fn is_fatal_swc_diagnostic(diagnostic: &Diagnostic) -> bool { - use swc_common::errors::Level; - match diagnostic.level { - Level::Bug - | Level::Cancelled - | Level::FailureNote - | Level::Fatal - | Level::PhaseFatal - | Level::Error => true, - Level::Help | Level::Note | Level::Warning => false, - } -} -fn format_swc_diagnostic(source_map: &SourceMap, diagnostic: &Diagnostic) -> String { - if let Some(span) = &diagnostic.span.primary_span() { - let file_name = source_map.span_to_filename(*span); - let loc = source_map.lookup_char_pos(span.lo); - format!( - "{} at {}:{}:{}", - diagnostic.message(), - file_name, - loc.line, - loc.col_display + 1, - ) - } else { - diagnostic.message() - } -} - -fn ensure_no_fatal_swc_diagnostics<'a>( - name: &str, - source_map: &SourceMap, - diagnostics: impl Iterator, -) -> Result<()> { - let fatal_diagnostics = diagnostics - .filter(|d| is_fatal_swc_diagnostic(d)) - .collect::>(); - if !fatal_diagnostics.is_empty() { - Err(Error::ParseError( - name.into(), - fatal_diagnostics - .iter() - .map(|d| format_swc_diagnostic(source_map, d)) - .collect::>() - .join("\n\n"), - )) - } else { - Ok(()) - } -} - -fn transpile_to_javascript(path: &str, input: String) -> Result { - // NOTE: This was taken almost verbatim from - // https://github.com/swc-project/swc/blob/main/crates/swc_ecma_transforms_typescript/examples/ts_to_js.rs - // This appears to be similar to what deno_ast does, but this has - // the advantage of actually compiling. :P - let cm: Lrc = Default::default(); - let diagnostics_cell: Rc>> = Rc::new(RefCell::new(Vec::new())); - let handler = DiagnosticCollector { - cell: diagnostics_cell.clone(), - } - .into_handler(); - - let fm = cm.new_source_file(FileName::Custom(path.into()), input.into()); - let comments = SingleThreadedComments::default(); - - let lexer = Lexer::new( - Syntax::Typescript(Default::default()), - Default::default(), - StringInput::from(&*fm), - Some(&comments), - ); - - let mut parser = Parser::new_from(lexer); - - for e in parser.take_errors() { - e.into_diagnostic(&handler).emit(); - } - - let module = parser - .parse_module() - .map_err(|e| e.into_diagnostic(&handler).emit()) - .expect("failed to parse module."); - - let globals = Globals::default(); - GLOBALS.set(&globals, || { - let unresolved_mark = Mark::new(); - let top_level_mark = Mark::new(); - - // Optionally transforms decorators here before the resolver pass - // as it might produce runtime declarations. - - // Conduct identifier scope analysis - let module = module.fold_with(&mut resolver(unresolved_mark, top_level_mark, true)); - - // Remove typescript types - let module = module.fold_with(&mut strip(top_level_mark)); - - // Fix up any identifiers with the same name, but different contexts - let module = module.fold_with(&mut hygiene()); - - // Ensure that we have enough parenthesis. - let module = module.fold_with(&mut fixer(Some(&comments))); - - let mut buf = vec![]; - { - let mut emitter = Emitter { - cfg: swc_ecma_codegen::Config { - minify: false, - ..Default::default() - }, - cm: cm.clone(), - comments: Some(&comments), - wr: JsWriter::new(cm.clone(), "\n", &mut buf, None), - }; - - emitter.emit_module(&module).unwrap(); - } - - let diagnostics = diagnostics_cell.borrow(); - ensure_no_fatal_swc_diagnostics(path, &cm, diagnostics.iter())?; - Ok(String::from_utf8(buf).expect("non-utf8?")) - }) -} +mod typescript; +use typescript::transpile_to_javascript; struct Loader {} @@ -172,6 +22,7 @@ impl Loader { impl ModuleLoader for Loader { fn load(&self, _context: &ContextRef, name: &str) -> Result { + eprintln!("Loading {name}..."); let path = Path::new(name); let contents = std::fs::read_to_string(path)?; let contents = if path.extension().and_then(OsStr::to_str) == Some("ts") { @@ -208,9 +59,8 @@ impl ScriptContext { let gfx = graphics::GraphicsAPI::define(&context, gfx_send) .expect("Graphics module should load without error"); - let js = include_str!("main.js"); let module = context - .eval_module(js, "main.js") + .import_module("./src/main.js", "") .expect("Unable to load main"); let init = module diff --git a/src/script/typescript.rs b/src/script/typescript.rs new file mode 100644 index 00000000..19bfc75b --- /dev/null +++ b/src/script/typescript.rs @@ -0,0 +1,154 @@ +use oden_js::{Error, Result}; +use std::cell::RefCell; +use std::rc::Rc; + +use swc_common::{ + self, comments::SingleThreadedComments, errors::Diagnostic, sync::Lrc, FileName, Globals, Mark, + SourceMap, GLOBALS, +}; +use swc_ecma_codegen::{text_writer::JsWriter, Emitter}; +use swc_ecma_parser::{lexer::Lexer, Parser, StringInput, Syntax}; +use swc_ecma_transforms_base::{fixer::fixer, hygiene::hygiene, resolver}; +use swc_ecma_transforms_typescript::strip; +use swc_ecma_visit::FoldWith; + +struct DiagnosticCollector { + cell: Rc>>, +} + +impl DiagnosticCollector { + pub fn into_handler(self) -> swc_common::errors::Handler { + swc_common::errors::Handler::with_emitter(true, false, Box::new(self)) + } +} + +impl swc_common::errors::Emitter for DiagnosticCollector { + fn emit(&mut self, db: &swc_common::errors::DiagnosticBuilder<'_>) { + use std::ops::Deref; + self.cell.borrow_mut().push(db.deref().clone()); + } +} + +fn is_fatal_swc_diagnostic(diagnostic: &Diagnostic) -> bool { + use swc_common::errors::Level; + match diagnostic.level { + Level::Bug + | Level::Cancelled + | Level::FailureNote + | Level::Fatal + | Level::PhaseFatal + | Level::Error => true, + Level::Help | Level::Note | Level::Warning => false, + } +} +fn format_swc_diagnostic(source_map: &SourceMap, diagnostic: &Diagnostic) -> String { + if let Some(span) = &diagnostic.span.primary_span() { + let file_name = source_map.span_to_filename(*span); + let loc = source_map.lookup_char_pos(span.lo); + format!( + "{} at {}:{}:{}", + diagnostic.message(), + file_name, + loc.line, + loc.col_display + 1, + ) + } else { + diagnostic.message() + } +} + +fn ensure_no_fatal_swc_diagnostics<'a>( + name: &str, + source_map: &SourceMap, + diagnostics: impl Iterator, +) -> Result<()> { + let fatal_diagnostics = diagnostics + .filter(|d| is_fatal_swc_diagnostic(d)) + .collect::>(); + if !fatal_diagnostics.is_empty() { + Err(Error::ParseError( + name.into(), + fatal_diagnostics + .iter() + .map(|d| format_swc_diagnostic(source_map, d)) + .collect::>() + .join("\n\n"), + )) + } else { + Ok(()) + } +} + +pub fn transpile_to_javascript(path: &str, input: String) -> Result { + // NOTE: This was taken almost verbatim from + // https://github.com/swc-project/swc/blob/main/crates/swc_ecma_transforms_typescript/examples/ts_to_js.rs + // This appears to be similar to what deno_ast does, but this has + // the advantage of actually compiling. :P + let cm: Lrc = Default::default(); + let diagnostics_cell: Rc>> = Rc::new(RefCell::new(Vec::new())); + let handler = DiagnosticCollector { + cell: diagnostics_cell.clone(), + } + .into_handler(); + + let fm = cm.new_source_file(FileName::Custom(path.into()), input.into()); + let comments = SingleThreadedComments::default(); + + let lexer = Lexer::new( + Syntax::Typescript(Default::default()), + Default::default(), + StringInput::from(&*fm), + Some(&comments), + ); + + let mut parser = Parser::new_from(lexer); + + for e in parser.take_errors() { + e.into_diagnostic(&handler).emit(); + } + + let module = parser + .parse_module() + .map_err(|e| e.into_diagnostic(&handler).emit()) + .expect("failed to parse module."); + + let globals = Globals::default(); + GLOBALS.set(&globals, || { + let unresolved_mark = Mark::new(); + let top_level_mark = Mark::new(); + + // Optionally transforms decorators here before the resolver pass + // as it might produce runtime declarations. + + // Conduct identifier scope analysis + let module = module.fold_with(&mut resolver(unresolved_mark, top_level_mark, true)); + + // Remove typescript types + let module = module.fold_with(&mut strip(top_level_mark)); + + // Fix up any identifiers with the same name, but different contexts + let module = module.fold_with(&mut hygiene()); + + // Ensure that we have enough parenthesis. + let module = module.fold_with(&mut fixer(Some(&comments))); + + let mut buf = vec![]; + { + let mut emitter = Emitter { + cfg: swc_ecma_codegen::Config { + minify: false, + ..Default::default() + }, + cm: cm.clone(), + comments: Some(&comments), + wr: JsWriter::new(cm.clone(), "\n", &mut buf, None), + }; + + emitter.emit_module(&module).unwrap(); + } + + let diagnostics = diagnostics_cell.borrow(); + ensure_no_fatal_swc_diagnostics(path, &cm, diagnostics.iter())?; + Ok(String::from_utf8(buf).expect("non-utf8?")) + }) +} diff --git a/types/graphics-core.d.ts b/types/graphics-core.d.ts index 41f83ec4..5f79ec8d 100644 --- a/types/graphics-core.d.ts +++ b/types/graphics-core.d.ts @@ -1,3 +1,5 @@ +// These are the functions exposed by the native graphics module. +// export function cls(r: number, g: number, b: number); export function print(msg: string); export function spr(