pub struct ParsedJS { /* private fields */ }Expand description
A successful parse: the AST arena, the source manager, and the Program
node, owned together.
The AST is only valid while its arena is alive, so this value owns the
arena; dropping it frees the AST. Read the tree with
with_program, or dump it with
to_estree_json.
Not Send. The arena uses Cell/UnsafeCell and the GCLock that
guards it is thread-local by design, so a ParsedJS cannot be moved to
another thread — parse on the thread that will read the AST. (The name
keeps the crate’s JSParserImpl/JSLexer casing rather than Rust’s
ParsedJs; that is deliberate, for consistency inside the port.)
Implementations§
Source§impl ParsedJS
impl ParsedJS
Sourcepub fn with_program<R, F>(&mut self, f: F) -> R
pub fn with_program<R, F>(&mut self, f: F) -> R
Run f with the arena locked and the Program node in hand.
This is the read path for the AST: walk it with an
hermes_ast::visitor::Visitor, match on Node arms, or read
Node::kind. References into the arena cannot escape the closure —
their lifetime ends with the lock — so return owned data instead. The
one thing that can escape is an hermes_ast::context::NodeRc, which is
refcounted rather than borrowed; dropping this ParsedJS while such a
handle is still alive panics inside Context::drop.
The bound is higher-ranked because Node is invariant in its
lifetime: a walker (hermes_ast::visitor::Visitor<'gc>) needs the node
reference and the node’s own lifetime to be the same 'gc, which only
a for<'gc> closure can promise.
§Why &mut self for a read
Reading the AST takes the arena lock, and
Context::lock takes
&mut self: the GCLock holds a &mut Context, which is what stops
Context::gc — the mark-and-sweep
that would invalidate every outstanding &Node — from running while
the tree is being read. So &mut here means “exclusive view of the
arena”, not “the AST is modified”; nothing in this method writes to the
tree. Consequently a ParsedJS cannot be read through a shared
reference: to share a parse, collect what you need inside the closure
and hand out the owned result.
§Panics
Panics if another GCLock is active on this thread — in particular
if with_program is called from inside another with_program.
Examples found in repository?
52fn main() {
53 let flags = ParseFlags::default();
54 let mut parsed = parse(SOURCE, flags).expect("snippet must parse");
55
56 // The AST is only reachable while the arena is locked, so collect owned
57 // data inside the closure and return it.
58 let counts = parsed.with_program(|_gc, program| {
59 let mut hist = Histogram {
60 counts: HashMap::new(),
61 };
62 hist.visit_node(program);
63 hist.counts
64 });
65
66 let mut rows: Vec<(NodeKind, usize)> = counts.into_iter().collect();
67 // Most frequent first; ties broken by kind name for a stable listing.
68 rows.sort_by(|a, b| {
69 b.1.cmp(&a.1)
70 .then_with(|| format!("{:?}", a.0).cmp(&format!("{:?}", b.0)))
71 });
72
73 let total: usize = rows.iter().map(|(_, n)| n).sum();
74 println!("{total} nodes, {} distinct kinds", rows.len());
75 for (kind, n) in rows {
76 println!("{:<24} {:>3} {}", format!("{kind:?}"), n, "#".repeat(n));
77 }
78}Sourcepub fn transform_program<R, F>(&mut self, f: F) -> Rwhere
F: for<'gc> FnOnce(&'gc GCLock<'static, '_>, &'gc Node<'gc>, &mut SourceErrorManager) -> (&'gc Node<'gc>, R),
pub fn transform_program<R, F>(&mut self, f: F) -> Rwhere
F: for<'gc> FnOnce(&'gc GCLock<'static, '_>, &'gc Node<'gc>, &mut SourceErrorManager) -> (&'gc Node<'gc>, R),
Run f with the arena locked, the Program node, and the source
manager, then adopt the node f returns as this ParsedJS’s program.
This is with_program for a pass that rewrites
the tree. A transforming visitor cannot mutate a node in place — the
arena hands out shared references — so it rebuilds the ancestors of
whatever it rewrote and returns a new root; that root is the one
carrying the pass’s results, and keeping the old one would silently
read a stale tree. Returning it from the closure is how the new root
gets re-pinned here, in the one place that can do it without dropping
the arena or leaving the pin dangling: the old pin is released only
after the new one exists, and the arena is never touched in between.
Returning the node f was given is fine and means “unchanged”.
The &mut SourceErrorManager is the pass’s diagnostic sink, and it is
the same one the parse used: a pass reports through it, and the
messages join the parse’s in diagnostics.
This exists for hermes-sema’s resolve façade, which is a
transforming visitor over exactly this shape; nothing about it is
specific to that crate.
The escape rules of with_program apply
unchanged: references into the arena cannot leave the closure (the
bound is higher-ranked for the same reason), while an
hermes_ast::context::NodeRc can — and dropping this ParsedJS
while one is alive panics inside Context::drop.
§Panics
Panics if another GCLock is active on this thread — in particular
if this is called from inside with_program or
from inside another transform_program.
Sourcepub fn to_estree_json(&mut self, pretty: bool) -> String
pub fn to_estree_json(&mut self, pretty: bool) -> String
Dump the AST as ESTree JSON: empty fields hidden, no loc/range,
and "raw" source text on numeric literals (the only node the dumper
emits it for). That is hermesc -dump-ast plus
-include-raw-ast-prop.
pretty selects indented output. For other dumper settings use
to_estree_json_with.
Takes &mut self although it only reads the tree, for the reason
with_program documents: dumping locks the
arena, and taking the lock needs exclusive access to the Context.
§Panics
Takes the arena lock, so it panics if another GCLock is live on
this thread — in particular when called from inside
with_program.
Examples found in repository?
21fn main() {
22 let path = std::env::args().nth(1);
23 let (name, source) = match &path {
24 Some(p) => (
25 p.as_str(),
26 std::fs::read_to_string(p).unwrap_or_else(|e| {
27 eprintln!("cannot read '{p}': {e}");
28 std::process::exit(1);
29 }),
30 ),
31 None => (
32 "<builtin>",
33 "function greet(name) { return 'Hello, ' + name; }".to_string(),
34 ),
35 };
36
37 // Plain ECMAScript. A file extension says nothing about the dialect, so
38 // like `hermesc` this example assumes none; set the flags explicitly for
39 // the others, e.g.:
40 // ParseFlags { parse_flow: true, ..Default::default() } // Flow
41 // ParseFlags { parse_ts: true, ..Default::default() } // TypeScript
42 // ParseFlags { parse_jsx: true, ..Default::default() } // JSX
43 let flags = ParseFlags::default();
44
45 match parse_named(&source, name, flags) {
46 Ok(mut parsed) => print!("{}", parsed.to_estree_json(true)),
47 Err(e) => {
48 // `Display` is the one-line summary; `messages()` is the full
49 // LLVM-style rendering, which is what a CLI wants. Each string is
50 // already newline-terminated, so this is `eprint!`, not
51 // `eprintln!`.
52 for m in e.messages() {
53 eprint!("{m}");
54 }
55 std::process::exit(1);
56 }
57 }
58}Sourcepub fn to_estree_json_with(
&mut self,
pretty: bool,
mode: ESTreeDumpMode,
loc_mode: LocationDumpMode,
raw_prop: ESTreeRawProp,
) -> String
pub fn to_estree_json_with( &mut self, pretty: bool, mode: ESTreeDumpMode, loc_mode: LocationDumpMode, raw_prop: ESTreeRawProp, ) -> String
Dump the AST as ESTree JSON with full control over the dumper, which
is hermes_ast::dump::dump_estree_json_with_sm — see it for what each
argument does.
&mut self for the same reason as
to_estree_json: it takes the arena lock.
§Panics
Takes the arena lock, so it panics if another GCLock is live on
this thread — in particular when called from inside
with_program.
Sourcepub fn diagnostics(&self) -> &[ResolvedDiagnostic]
pub fn diagnostics(&self) -> &[ResolvedDiagnostic]
The diagnostics recorded while parsing.
An Ok parse reported no errors, so these are warnings and notes.
Render one with hermes_support::render::render_diagnostic.
Sourcepub fn source_manager(&self) -> &SourceErrorManager
pub fn source_manager(&self) -> &SourceErrorManager
The source manager that owns the parsed buffer, for coordinate lookups
(find_coords) and for driving the ast dumper by hand.