[oden] OK now everything is TS and it type-checks

This commit is contained in:
John Doty 2023-06-24 17:49:41 -07:00
parent 3968aabdb1
commit b6f6d908d2
6 changed files with 168 additions and 158 deletions

View file

@ -151,7 +151,7 @@ impl ContextRef {
pub fn import_module(&self, name: &str, base: &str) -> Result<Module> { pub fn import_module(&self, name: &str, base: &str) -> Result<Module> {
let name = CString::new(name)?; let name = CString::new(name)?;
let base = CString::new(base)?; 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() { if module.is_null() {
return Err(self.exception_error()); return Err(self.exception_error());
} }

View file

@ -1,13 +1,17 @@
import * as core from "graphics-core"; 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) { export function cls(r: number, g: number, b: number) {
core.cls(r, g, b); 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(" ")); core.print(args.join(" "));
} }
// Draw a sprite in the rectangle from x,y to
export function spr( export function spr(
x: number, x: number,
y: number, y: number,

View file

@ -1,4 +1,4 @@
import { cls, print, spr } from "./src/graphics.ts"; import { cls, print, spr } from "./graphics.ts";
export function init() { export function init() {
print("Hello world!"); print("Hello world!");

View file

@ -1,166 +1,16 @@
use oden_js::{ use oden_js::{
module::loader::{ModuleLoader, ModuleSource}, 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::ffi::OsStr;
use std::path::Path; use std::path::Path;
use std::rc::Rc;
use std::sync::mpsc::{channel, Receiver}; 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; pub mod graphics;
use graphics::GraphicsCommand; use graphics::GraphicsCommand;
struct DiagnosticCollector { mod typescript;
cell: Rc<RefCell<Vec<Diagnostic>>>, use typescript::transpile_to_javascript;
}
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<Item = &'a Diagnostic>,
) -> Result<()> {
let fatal_diagnostics = diagnostics
.filter(|d| is_fatal_swc_diagnostic(d))
.collect::<Vec<_>>();
if !fatal_diagnostics.is_empty() {
Err(Error::ParseError(
name.into(),
fatal_diagnostics
.iter()
.map(|d| format_swc_diagnostic(source_map, d))
.collect::<Vec<_>>()
.join("\n\n"),
))
} else {
Ok(())
}
}
fn transpile_to_javascript(path: &str, input: String) -> Result<String> {
// 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<SourceMap> = Default::default();
let diagnostics_cell: Rc<RefCell<Vec<Diagnostic>>> = 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?"))
})
}
struct Loader {} struct Loader {}
@ -172,6 +22,7 @@ impl Loader {
impl ModuleLoader for Loader { impl ModuleLoader for Loader {
fn load(&self, _context: &ContextRef, name: &str) -> Result<ModuleSource> { fn load(&self, _context: &ContextRef, name: &str) -> Result<ModuleSource> {
eprintln!("Loading {name}...");
let path = Path::new(name); let path = Path::new(name);
let contents = std::fs::read_to_string(path)?; let contents = std::fs::read_to_string(path)?;
let contents = if path.extension().and_then(OsStr::to_str) == Some("ts") { 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) let gfx = graphics::GraphicsAPI::define(&context, gfx_send)
.expect("Graphics module should load without error"); .expect("Graphics module should load without error");
let js = include_str!("main.js");
let module = context let module = context
.eval_module(js, "main.js") .import_module("./src/main.js", "")
.expect("Unable to load main"); .expect("Unable to load main");
let init = module let init = module

154
src/script/typescript.rs Normal file
View file

@ -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<RefCell<Vec<Diagnostic>>>,
}
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<Item = &'a Diagnostic>,
) -> Result<()> {
let fatal_diagnostics = diagnostics
.filter(|d| is_fatal_swc_diagnostic(d))
.collect::<Vec<_>>();
if !fatal_diagnostics.is_empty() {
Err(Error::ParseError(
name.into(),
fatal_diagnostics
.iter()
.map(|d| format_swc_diagnostic(source_map, d))
.collect::<Vec<_>>()
.join("\n\n"),
))
} else {
Ok(())
}
}
pub fn transpile_to_javascript(path: &str, input: String) -> Result<String> {
// 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<SourceMap> = Default::default();
let diagnostics_cell: Rc<RefCell<Vec<Diagnostic>>> = 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?"))
})
}

View file

@ -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 cls(r: number, g: number, b: number);
export function print(msg: string); export function print(msg: string);
export function spr( export function spr(