summaryrefslogtreecommitdiffstats
path: root/third_party/rust/jsparagus-emitter/src/lib.rs
blob: e4b5d49bc1fe5e2358d490bc74d18b05a16cc308 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
mod array_emitter;
mod ast_emitter;
mod block_emitter;
mod compilation_info;
mod control_structures;
mod dis;
mod emitter;
mod emitter_scope;
mod expression_emitter;
mod function_declaration_emitter;
mod object_emitter;
mod reference_op_emitter;
mod script_emitter;

extern crate jsparagus_ast as ast;
extern crate jsparagus_scope as scope;
extern crate jsparagus_stencil as stencil;

pub use crate::emitter::{EmitError, EmitOptions};
pub use dis::dis;

use crate::compilation_info::CompilationInfo;

use ast::source_atom_set::SourceAtomSet;
use ast::source_slice_list::SourceSliceList;
use scope::{ScopeBuildError, ScopePassResult};
use stencil::result::EmitResult;

pub fn emit<'alloc>(
    ast: &'alloc ast::types::Program<'alloc>,
    options: &EmitOptions,
    atoms: SourceAtomSet<'alloc>,
    slices: SourceSliceList<'alloc>,
) -> Result<EmitResult<'alloc>, EmitError> {
    let ScopePassResult {
        scope_data_map,
        function_declarations,
        function_stencil_indices,
        function_declaration_properties,
        scripts,
        error,
    } = scope::generate_scope_data(ast);

    // Error case for scope analysis will be removed once all syntax is
    // supported. Use field instead of Result type here for simplicity.
    match error {
        Some(ScopeBuildError::NotImplemented(s)) => {
            return Err(EmitError::NotImplemented(s));
        }
        None => {}
    }

    let compilation_info = CompilationInfo::new(
        atoms,
        slices,
        scope_data_map,
        function_declarations,
        function_stencil_indices,
        function_declaration_properties,
        scripts,
    );
    ast_emitter::emit_program(ast, options, compilation_info)
}

#[cfg(test)]
mod tests {
    extern crate jsparagus_parser as parser;

    use super::{emit, EmitOptions};
    use crate::dis::*;
    use ast::source_atom_set::SourceAtomSet;
    use ast::source_slice_list::SourceSliceList;
    use bumpalo::Bump;
    use parser::{parse_script, ParseOptions};
    use std::cell::RefCell;
    use std::convert::TryInto;
    use std::rc::Rc;
    use stencil::opcode::*;
    use stencil::script::SourceExtent;

    fn bytecode(source: &str) -> Vec<u8> {
        let alloc = &Bump::new();
        let parse_options = ParseOptions::new();
        let atoms = Rc::new(RefCell::new(SourceAtomSet::new()));
        let slices = Rc::new(RefCell::new(SourceSliceList::new()));
        let source_len = source.len();
        let parse_result =
            parse_script(alloc, source, &parse_options, atoms.clone(), slices.clone())
                .expect("Failed to parse");
        // println!("{:?}", parse_result);

        let extent = SourceExtent::top_level_script(source_len.try_into().unwrap(), 1, 0);
        let emit_options = EmitOptions::new(extent);

        let result = emit(
            alloc.alloc(ast::types::Program::Script(parse_result.unbox())),
            &emit_options,
            atoms.replace(SourceAtomSet::new_uninitialized()),
            slices.replace(SourceSliceList::new()),
        )
        .expect("Should work!");

        let script_data_index: usize = result.scripts[0]
            .immutable_script_data
            .expect("Top level script should have ImmutableScriptData")
            .into();
        let script_data = &result.script_data_list[script_data_index];
        let bytecode = &script_data.bytecode;

        println!("{}", dis(&bytecode));
        bytecode.to_vec()
    }

    #[test]
    fn it_works() {
        assert_eq!(
            bytecode("2 + 2"),
            vec![
                Opcode::Int8 as u8,
                2,
                Opcode::Int8 as u8,
                2,
                Opcode::Add as u8,
                Opcode::SetRval as u8,
                Opcode::RetRval as u8,
            ]
        )
    }

    #[test]
    fn dis_call() {
        assert_eq!(
            bytecode("dis()"),
            vec![
                Opcode::GetGName as u8,
                1,
                0,
                0,
                0,
                Opcode::Undefined as u8,
                Opcode::Call as u8,
                0,
                0,
                Opcode::SetRval as u8,
                Opcode::RetRval as u8,
            ]
        )
    }

    #[test]
    fn literals() {
        assert_eq!(
            bytecode("true"),
            vec![
                Opcode::True as u8,
                Opcode::SetRval as u8,
                Opcode::RetRval as u8,
            ]
        );
        assert_eq!(
            bytecode("false"),
            vec![
                Opcode::False as u8,
                Opcode::SetRval as u8,
                Opcode::RetRval as u8,
            ]
        );
        //assert_eq!(
        //    bytecode("'hello world'"),
        //    vec![
        //        Opcode::String as u8, 0, 0, 0, 0,
        //        Opcode::SetRval as u8,
        //        Opcode::RetRval as u8,
        //    ]
        //);
    }
}