Skip to content

Commit

Permalink
Simple SourceManager
Browse files Browse the repository at this point in the history
Summary:
SourceManager owns a collection of source buffers with names (so they
can be parsed and so it can provide context when printing errors).

This is a very simple implementation - it directly prints error messages
to stderr without any buffering, etc.

The error() method takes `&self` by design - the implementation will use
interior mutability. A SourceManager is intended to be easily shareable.

Reviewed By: avp

Differential Revision: D31311326

fbshipit-source-id: 47d48d2efb3bcf2978d73b524cfd7e8b4122a8be
  • Loading branch information
tmikov authored and facebook-github-bot committed Oct 3, 2021
1 parent 765df80 commit 0dc8937
Show file tree
Hide file tree
Showing 9 changed files with 137 additions and 18 deletions.
3 changes: 2 additions & 1 deletion unsupported/juno/crates/juno/src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/

use crate::source_manager::SourceId;
use std::fmt;
use support::define_str_enum;
use thiserror::Error;
Expand Down Expand Up @@ -144,7 +145,7 @@ pub trait Visitor {
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct SourceRange {
/// Index of the file this range is in.
pub file: u32,
pub file: SourceId,

/// Start of the source range, inclusive.
pub start: SourceLoc,
Expand Down
3 changes: 2 additions & 1 deletion unsupported/juno/crates/juno/src/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use juno::ast::{self, NodePtr};
use juno::gen_js;
use juno::hparser::{self, MagicCommentKind, ParsedJS};
use juno::sourcemap::merge_sourcemaps;
use juno::source_manager::SourceId;
use sourcemap::SourceMap;
use std::fs::File;
use std::io::Write;
Expand Down Expand Up @@ -207,7 +208,7 @@ fn run(opt: &Opt) -> anyhow::Result<TransformStatus> {

// Convert to Juno AST.
let mut ctx = ast::Context::new();
let ast = parsed.to_ast(&mut ctx, 0).unwrap();
let ast = parsed.to_ast(&mut ctx, SourceId::INVALID).unwrap();
let cvt_time = start_time.elapsed();

// We don't need the original parser anymore.
Expand Down
5 changes: 3 additions & 2 deletions unsupported/juno/crates/juno/src/hparser/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

use super::generated_cvt::cvt_node_ptr;
use crate::ast;
use crate::source_manager::SourceId;
use hermes::parser::{
DataRef, HermesParser, NodeLabel, NodeLabelOpt, NodeListOptRef, NodeListRef, NodePtr,
NodePtrOpt, NodeString, NodeStringOpt, SMLoc,
Expand Down Expand Up @@ -42,7 +43,7 @@ pub struct Converter<'parser, 'ctx> {
/// Rust AST context for allocation.
pub ast_context: &'ctx mut ast::Context,
/// The file id to use for the converted coordinates.
pub file_id: u32,
pub file_id: SourceId,

/// A cache to speed up finding locations.
line_cache: FindLineCache<'parser>,
Expand Down Expand Up @@ -91,7 +92,7 @@ impl Converter<'_, '_> {
pub fn new<'parser, 'ctx>(
hparser: &'parser HermesParser<'parser>,
ast_context: &'ctx mut ast::Context,
file_id: u32,
file_id: SourceId,
) -> Converter<'parser, 'ctx> {
Converter {
hparser,
Expand Down
7 changes: 4 additions & 3 deletions unsupported/juno/crates/juno/src/hparser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use std::fmt::Formatter;
use support::NullTerminatedBuf;
use thiserror::Error;

use crate::source_manager::SourceId;
pub use hermes::parser::{MagicCommentKind, ParserDialect, ParserFlags};

pub struct ParsedJS<'a> {
Expand Down Expand Up @@ -59,7 +60,7 @@ impl ParsedJS<'_> {
}

/// Create and return an external representation of the AST, or None if there were parse errors.
pub fn to_ast(&self, ctx: &mut ast::Context, file_id: u32) -> Option<ast::NodePtr> {
pub fn to_ast(&self, ctx: &mut ast::Context, file_id: SourceId) -> Option<ast::NodePtr> {
let mut cvt = Converter::new(&self.parser, ctx, file_id);

self.parser.root().map(|root| convert_ast(&mut cvt, root))
Expand Down Expand Up @@ -89,7 +90,7 @@ pub fn parse_with_file_id(
flags: ParserFlags,
source: &str,
ctx: &mut ast::Context,
file_id: u32,
file_id: SourceId,
) -> Result<ast::NodePtr, ParseError> {
let buf = NullTerminatedBuf::from_str_check(source);
let parsed = ParsedJS::parse(flags, &buf);
Expand All @@ -104,7 +105,7 @@ pub fn parse_with_file_id(
/// This is a simple function that is intended to be used mostly for testing.
/// When there ar errors, it returns only the first error.
pub fn parse(ctx: &mut ast::Context, source: &str) -> Result<ast::NodePtr, ParseError> {
parse_with_file_id(Default::default(), source, ctx, 0)
parse_with_file_id(Default::default(), source, ctx, SourceId::INVALID)
}

#[cfg(test)]
Expand Down
1 change: 1 addition & 0 deletions unsupported/juno/crates/juno/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@
pub mod ast;
pub mod gen_js;
pub mod hparser;
pub mod source_manager;
pub mod sourcemap;
109 changes: 109 additions & 0 deletions unsupported/juno/crates/juno/src/source_manager.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

use crate::ast::SourceRange;
use std::rc::Rc;
use support::NullTerminatedBuf;

/// An opaque value identifying a source registered with SourceManager.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct SourceId(pub u32);

impl SourceId {
pub const INVALID: SourceId = SourceId(u32::MAX);

pub fn is_valid(self) -> bool {
self.0 != Self::INVALID.0
}

fn as_usize(self) -> usize {
self.0 as usize
}
}

/// SourceManager owns a collection of source buffers and their names and handles
/// reporting errors.
#[derive(Debug)]
pub struct SourceManager {
sources: Vec<(String, Rc<NullTerminatedBuf>)>,
}

impl Default for SourceManager {
fn default() -> Self {
SourceManager {
sources: Default::default(),
}
}
}

impl SourceManager {
pub fn new() -> SourceManager {
Default::default()
}

/// Register a source buffer with its name.
pub fn add_source<S: Into<String>>(&mut self, name: S, buf: NullTerminatedBuf) -> SourceId {
assert!(
self.sources.len() < SourceId::INVALID.0 as usize,
"Too many sources",
);
self.sources.push((name.into(), Rc::new(buf)));
SourceId(self.sources.len() as u32 - 1)
}

/// Obtain the name of a previously registered source buffer.
pub fn source_name(&self, source_id: SourceId) -> &str {
self.sources[source_id.as_usize()].0.as_str()
}

/// Obtain a reference to a previously registered source buffer.
pub fn source_buffer(&self, source_id: SourceId) -> &NullTerminatedBuf {
&self.sources[source_id.as_usize()].1
}

/// Obtain a Rc of a previously registered source buffer.
pub fn source_buffer_rc(&self, source_id: SourceId) -> Rc<NullTerminatedBuf> {
Rc::clone(&self.sources[source_id.as_usize()].1)
}

/// Report an error at the specified range in the specified source buffer.
pub fn error<S: Into<String>>(&self, range: SourceRange, msg: S) {
// NOTE: this method deliberately takes immutable `self`. A SourceManager
// should be easily shareable. It will be implemented using interior
// mutability.
eprintln!(
"{}:{}:{}: error: {}",
self.source_name(range.file),
range.start.line,
range.start.col,
msg.into()
);
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn smoke_test() {
let mut sm = SourceManager::new();

let id1 = sm.add_source("buf1", NullTerminatedBuf::from_str_copy("a"));
let id2 = sm.add_source("buf2", NullTerminatedBuf::from_str_copy("bb"));

assert_eq!("buf1", sm.source_name(id1));
assert_eq!("buf2", sm.source_name(id2));

assert_eq!(2, sm.source_buffer(id1).len());
assert_eq!(3, sm.source_buffer(id2).len());

let buf1 = sm.source_buffer_rc(id1);
assert_eq!(2, buf1.len());
assert_eq!(b"a\0", buf1.as_bytes());
}
}
3 changes: 2 additions & 1 deletion unsupported/juno/crates/juno/tests/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

use juno::ast::*;
use juno::source_manager::SourceId;

/// Create a node with a default source range for testing.
/// Use a macro to make it easier to construct nested macros
Expand All @@ -28,7 +29,7 @@ fn test_visit() {
let mut ctx = Context::new();
// Dummy range, we don't care about ranges in this test.
let range = SourceRange {
file: 0,
file: SourceId::INVALID,
start: SourceLoc { line: 1, col: 1 },
end: SourceLoc { line: 1, col: 1 },
};
Expand Down
5 changes: 3 additions & 2 deletions unsupported/juno/crates/juno/tests/ast/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
*/

use juno::ast::*;
use juno::source_manager::SourceId;

#[test]
fn test_valid() {
let mut ctx = Context::new();
let range = SourceRange {
file: 0,
file: SourceId::INVALID,
start: SourceLoc::invalid(),
end: SourceLoc::invalid(),
};
Expand Down Expand Up @@ -56,7 +57,7 @@ fn test_valid() {
fn test_error() {
let mut ctx = Context::new();
let range = SourceRange {
file: 0,
file: SourceId::INVALID,
start: SourceLoc::invalid(),
end: SourceLoc::invalid(),
};
Expand Down
19 changes: 11 additions & 8 deletions unsupported/juno/crates/juno/tests/gen_js/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use juno::ast::*;
use juno::gen_js;
use juno::hparser;
use juno::source_manager::SourceId;
use juno::sourcemap::merge_sourcemaps;

fn do_gen(ctx: &Context, node: NodePtr, pretty: gen_js::Pretty) -> String {
Expand All @@ -24,18 +25,19 @@ fn test_roundtrip_with_flags(flags: hparser::ParserFlags, src1: &str) {
let ctx = &mut context;

for pretty in &[gen_js::Pretty::Yes, gen_js::Pretty::No] {
let ast1 = hparser::parse_with_file_id(flags, src1, ctx, 0).unwrap();
let ast1 = hparser::parse_with_file_id(flags, src1, ctx, SourceId::INVALID).unwrap();
let mut dump: Vec<u8> = vec![];
dump_json(&mut dump, ctx, ast1, juno::ast::Pretty::Yes).unwrap();
let ast1_json = String::from_utf8(dump).expect("Invalid UTF-8 output in test");

let src2 = do_gen(ctx, ast1, *pretty);
let ast2 = hparser::parse_with_file_id(flags, &src2, ctx, 0).unwrap_or_else(|_| {
panic!(
let ast2 = hparser::parse_with_file_id(flags, &src2, ctx, SourceId::INVALID)
.unwrap_or_else(|_| {
panic!(
"Invalid JS generated: Pretty={:?}\nOriginal Source:\n{}\nGenerated Source:\n{}",
pretty, &src1, &src2,
)
});
});
let mut dump: Vec<u8> = vec![];
dump_json(&mut dump, ctx, ast2, juno::ast::Pretty::Yes).unwrap();
let ast2_json = String::from_utf8(dump).expect("Invalid UTF-8 output in test");
Expand Down Expand Up @@ -78,7 +80,7 @@ fn test_roundtrip_jsx(src1: &str) {
fn test_literals() {
let mut ctx = Context::new();
let range = SourceRange {
file: 0,
file: SourceId::INVALID,
start: SourceLoc { line: 1, col: 1 },
end: SourceLoc { line: 1, col: 1 },
};
Expand Down Expand Up @@ -134,7 +136,7 @@ fn test_identifier() {
fn test_binop() {
let mut ctx = Context::new();
let range = SourceRange {
file: 0,
file: SourceId::INVALID,
start: SourceLoc { line: 1, col: 1 },
end: SourceLoc { line: 1, col: 1 },
};
Expand Down Expand Up @@ -448,7 +450,7 @@ fn test_export() {
fn test_types() {
let mut ctx = Context::new();
let range = SourceRange {
file: 0,
file: SourceId::INVALID,
start: SourceLoc { line: 1, col: 1 },
end: SourceLoc { line: 1, col: 1 },
};
Expand Down Expand Up @@ -607,7 +609,8 @@ fn test_sourcemap_merged() {
)
.unwrap();
let mut out: Vec<u8> = vec![];
let node = hparser::parse_with_file_id(Default::default(), input_src, ctx, 0).unwrap();
let node =
hparser::parse_with_file_id(Default::default(), input_src, ctx, SourceId::INVALID).unwrap();
let output_map = generate(&mut out, ctx, node, Pretty::Yes).unwrap();
let output = String::from_utf8(out).expect("Invalid UTF-8 output in test");
assert_eq!(output, "function foo() {\n 1;\n}\n",);
Expand Down

0 comments on commit 0dc8937

Please sign in to comment.