[oden] OK now everything is TS and it type-checks
This commit is contained in:
parent
3968aabdb1
commit
b6f6d908d2
6 changed files with 168 additions and 158 deletions
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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!");
|
||||||
160
src/script.rs
160
src/script.rs
|
|
@ -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
154
src/script/typescript.rs
Normal 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?"))
|
||||||
|
})
|
||||||
|
}
|
||||||
2
types/graphics-core.d.ts
vendored
2
types/graphics-core.d.ts
vendored
|
|
@ -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(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue