mirror of
https://github.com/leporello-js/leporello-js
synced 2026-01-13 13:04:30 -08:00
default import and export
This commit is contained in:
@@ -41,34 +41,12 @@ export const map_destructuring_identifiers = (node, mapper) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const collect_imports = module => {
|
export const collect_imports = module => {
|
||||||
const import_statements =
|
return uniq(
|
||||||
module.stmts
|
module.stmts
|
||||||
.filter(n => n.type == 'import')
|
.filter(n => n.type == 'import')
|
||||||
.filter(n => !n.is_external)
|
.filter(n => !n.is_external)
|
||||||
|
.map(n => n.full_import_path)
|
||||||
const imported_modules = uniq(
|
|
||||||
import_statements.map(n => n.full_import_path)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const imports =
|
|
||||||
import_statements
|
|
||||||
.map(n =>
|
|
||||||
n.imports.map(i =>
|
|
||||||
({name: i.value, module: n.full_import_path})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.flat()
|
|
||||||
|
|
||||||
const by_module = Object.fromEntries(imported_modules.map(m =>
|
|
||||||
[
|
|
||||||
m,
|
|
||||||
imports
|
|
||||||
.filter(i => i.module == m)
|
|
||||||
.map(i => i.name)
|
|
||||||
]
|
|
||||||
))
|
|
||||||
|
|
||||||
return by_module
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const collect_external_imports = modules =>
|
export const collect_external_imports = modules =>
|
||||||
|
|||||||
17
src/cmd.js
17
src/cmd.js
@@ -1,12 +1,11 @@
|
|||||||
import {map_object, filter_object, collect_nodes_with_parents, uniq}
|
import {map_object, map_find, filter_object, collect_nodes_with_parents, uniq}
|
||||||
from './utils.js'
|
from './utils.js'
|
||||||
import {
|
import {
|
||||||
is_eq, is_child, ancestry, ancestry_inc, map_tree,
|
is_eq, is_child, ancestry, ancestry_inc, map_tree,
|
||||||
find_leaf, find_fn_by_location, find_node, find_error_origin_node,
|
find_leaf, find_fn_by_location, find_node, find_error_origin_node,
|
||||||
collect_external_imports
|
collect_external_imports, collect_destructuring_identifiers
|
||||||
} from './ast_utils.js'
|
} from './ast_utils.js'
|
||||||
import {load_modules} from './parse_js.js'
|
import {load_modules} from './parse_js.js'
|
||||||
import {find_export} from './find_definitions.js'
|
|
||||||
import {eval_modules} from './eval.js'
|
import {eval_modules} from './eval.js'
|
||||||
import {
|
import {
|
||||||
root_calltree_node, root_calltree_module, make_calltree,
|
root_calltree_node, root_calltree_module, make_calltree,
|
||||||
@@ -555,7 +554,17 @@ const goto_definition = (state, index) => {
|
|||||||
} else {
|
} else {
|
||||||
let loc
|
let loc
|
||||||
if(d.module != null) {
|
if(d.module != null) {
|
||||||
const exp = find_export(node.value, state.parse_result.modules[d.module])
|
const exp = map_find(state.parse_result.modules[d.module].stmts, n => {
|
||||||
|
if(n.type != 'export') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if(n.is_default && d.is_default) {
|
||||||
|
return n.children[0]
|
||||||
|
} else if(!n.is_default && !d.is_default) {
|
||||||
|
const ids = collect_destructuring_identifiers(n.binding.name_node)
|
||||||
|
return ids.find(i => i.value == node.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
loc = {module: d.module, index: exp.index}
|
loc = {module: d.module, index: exp.index}
|
||||||
} else {
|
} else {
|
||||||
loc = {module: state.current_module, index: d.index}
|
loc = {module: state.current_module, index: d.index}
|
||||||
|
|||||||
41
src/eval.js
41
src/eval.js
@@ -261,18 +261,25 @@ ${JSON.stringify(errormessage)}, true)`
|
|||||||
} else if(node.type == 'destructuring_pair') {
|
} else if(node.type == 'destructuring_pair') {
|
||||||
return do_codegen(node.key) + ' : ' + do_codegen(node.value);
|
return do_codegen(node.key) + ' : ' + do_codegen(node.value);
|
||||||
} else if(node.type == 'import') {
|
} else if(node.type == 'import') {
|
||||||
const names = node.imports.map(n => n.value)
|
let names, def
|
||||||
if(names.length == 0) {
|
if(node.default_import != null) {
|
||||||
return ''
|
def = `default: ${node.default_import},`
|
||||||
|
names = node.children.slice(1).map(n => n.value)
|
||||||
} else {
|
} else {
|
||||||
return `const {${names.join(',')}} = __cxt.modules['${node.full_import_path}'];`;
|
def = ''
|
||||||
|
names = node.children.map(n => n.value)
|
||||||
}
|
}
|
||||||
|
return `const {${def} ${names.join(',')}} = __cxt.modules['${node.full_import_path}'];`;
|
||||||
} else if(node.type == 'export') {
|
} else if(node.type == 'export') {
|
||||||
const identifiers = collect_destructuring_identifiers(node.binding.name_node)
|
if(node.is_default) {
|
||||||
.map(i => i.value)
|
return `__cxt.modules['${node_cxt.module}'].default = ${do_codegen(node.children[0])};`
|
||||||
return do_codegen(node.binding)
|
} else {
|
||||||
+
|
const identifiers = collect_destructuring_identifiers(node.binding.name_node)
|
||||||
`Object.assign(__cxt.modules['${node_cxt.module}'], {${identifiers.join(',')}});`
|
.map(i => i.value)
|
||||||
|
return do_codegen(node.binding)
|
||||||
|
+
|
||||||
|
`Object.assign(__cxt.modules['${node_cxt.module}'], {${identifiers.join(',')}});`
|
||||||
|
}
|
||||||
} else if(node.type == 'function_decl') {
|
} else if(node.type == 'function_decl') {
|
||||||
const expr = node.children[0]
|
const expr = node.children[0]
|
||||||
return `const ${expr.name} = ${codegen_function_expr(expr, node_cxt)};`
|
return `const ${expr.name} = ${codegen_function_expr(expr, node_cxt)};`
|
||||||
@@ -991,12 +998,20 @@ const eval_statement = (s, scope, calls, context) => {
|
|||||||
node: {...s, children: [node], result: {ok: node.result.ok}}
|
node: {...s, children: [node], result: {ok: node.result.ok}}
|
||||||
}
|
}
|
||||||
} else if(s.type == 'import') {
|
} else if(s.type == 'import') {
|
||||||
const children = s.imports.map(i => (
|
const module = context.modules[s.full_import_path]
|
||||||
{...i,
|
const children = s.children.map((imp, i) => (
|
||||||
result: {ok: true, value: context.modules[s.full_import_path][i.value]}
|
{...imp,
|
||||||
|
result: {
|
||||||
|
ok: true,
|
||||||
|
value: imp.definition.is_default
|
||||||
|
? module['default']
|
||||||
|
: module[imp.value]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
const imported_scope = Object.fromEntries(children.map(i => [i.value, i.result.value]))
|
const imported_scope = Object.fromEntries(
|
||||||
|
children.map(imp => [imp.value, imp.result.value])
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
scope: {...scope, ...imported_scope},
|
scope: {...scope, ...imported_scope},
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const map_find_definitions = (nodes, mapper) => {
|
|||||||
const scope_from_node = n => {
|
const scope_from_node = n => {
|
||||||
if(n.type == 'import') {
|
if(n.type == 'import') {
|
||||||
return Object.fromEntries(
|
return Object.fromEntries(
|
||||||
n.imports.map(i => [i.value, i])
|
n.children.map(i => [i.value, i])
|
||||||
)
|
)
|
||||||
} else if(n.type == 'export'){
|
} else if(n.type == 'export'){
|
||||||
return scope_from_node(n.binding)
|
return scope_from_node(n.binding)
|
||||||
@@ -137,7 +137,13 @@ export const find_definitions = (ast, scope = {}, closure_scope = {}, module_nam
|
|||||||
let children, full_import_path
|
let children, full_import_path
|
||||||
if(ast.type == 'import') {
|
if(ast.type == 'import') {
|
||||||
full_import_path = concat_path(module_name, ast.module)
|
full_import_path = concat_path(module_name, ast.module)
|
||||||
children = ast.children.map(c => ({...c, definition: {module: full_import_path}}))
|
children = ast.children.map((c, i) => ({
|
||||||
|
...c,
|
||||||
|
definition: {
|
||||||
|
module: full_import_path,
|
||||||
|
is_default: i == 0 && ast.default_import != null,
|
||||||
|
}
|
||||||
|
}))
|
||||||
} else if(ast.type == 'const') {
|
} else if(ast.type == 'const') {
|
||||||
children = [add_trivial_definition(ast.name_node), ...ast.children.slice(1)]
|
children = [add_trivial_definition(ast.name_node), ...ast.children.slice(1)]
|
||||||
} else if(ast.type == 'let') {
|
} else if(ast.type == 'let') {
|
||||||
@@ -162,16 +168,6 @@ export const find_definitions = (ast, scope = {}, closure_scope = {}, module_nam
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const find_export = (name, module) => {
|
|
||||||
return map_find(module.stmts, n => {
|
|
||||||
if(n.type != 'export') {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
const ids = collect_destructuring_identifiers(n.binding.name_node)
|
|
||||||
return ids.find(i => i.value == name)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const BASE = 'dummy://dummy/'
|
const BASE = 'dummy://dummy/'
|
||||||
const concat_path = (base, i) => {
|
const concat_path = (base, i) => {
|
||||||
const result = new URL(i, BASE + base).toString()
|
const result = new URL(i, BASE + base).toString()
|
||||||
@@ -184,11 +180,9 @@ const concat_path = (base, i) => {
|
|||||||
|
|
||||||
export const topsort_modules = (modules) => {
|
export const topsort_modules = (modules) => {
|
||||||
const sort_module_deps = (module) => {
|
const sort_module_deps = (module) => {
|
||||||
return Object.keys(collect_imports(modules[module]))
|
return collect_imports(modules[module])
|
||||||
.reduce(
|
.map(m => sort_module_deps(m))
|
||||||
(result, m) => result.concat(sort_module_deps(m)),
|
.flat()
|
||||||
[]
|
|
||||||
)
|
|
||||||
.concat(module)
|
.concat(module)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,6 +229,8 @@ export const check_imports = modules => {
|
|||||||
.reduce(
|
.reduce(
|
||||||
(imports, n) => [
|
(imports, n) => [
|
||||||
...imports,
|
...imports,
|
||||||
|
// TODO imports
|
||||||
|
// TODO use flatmap
|
||||||
...(n.imports.map(i => ({name: i.value, from: n.module})))
|
...(n.imports.map(i => ({name: i.value, from: n.module})))
|
||||||
],
|
],
|
||||||
[]
|
[]
|
||||||
@@ -245,7 +241,8 @@ export const check_imports = modules => {
|
|||||||
.reduce((all, current) => [...all, ...current], [])
|
.reduce((all, current) => [...all, ...current], [])
|
||||||
|
|
||||||
return {imports, exports}
|
return {imports, exports}
|
||||||
//TODO check for each import, there is export
|
// TODO check for each import, there is export. For default import there is
|
||||||
|
// default export
|
||||||
})
|
})
|
||||||
// Topological sort
|
// Topological sort
|
||||||
// For each module
|
// For each module
|
||||||
|
|||||||
@@ -1226,20 +1226,34 @@ const import_statement =
|
|||||||
optional(by_type('pragma_external')),
|
optional(by_type('pragma_external')),
|
||||||
seq([
|
seq([
|
||||||
literal('import'),
|
literal('import'),
|
||||||
|
// TODO import can have both named import and default import,
|
||||||
|
// like 'import foo, {bar} from "module"'
|
||||||
optional(
|
optional(
|
||||||
seq([
|
seq_select(0, [
|
||||||
list(
|
either(
|
||||||
['{', '}'],
|
list(
|
||||||
|
['{', '}'],
|
||||||
|
identifier,
|
||||||
|
),
|
||||||
identifier,
|
identifier,
|
||||||
),
|
),
|
||||||
literal('from'),
|
literal('from'),
|
||||||
])
|
]),
|
||||||
),
|
),
|
||||||
string_literal
|
string_literal
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
({value: [pragma_external, imp]}) => {
|
({value: [pragma_external, imp]}) => {
|
||||||
const {value: [_import, identifiers, module], ...node} = imp
|
const {value: [_import, imports, module], ...node} = imp
|
||||||
|
let default_import, children
|
||||||
|
if(imports == null) {
|
||||||
|
children = []
|
||||||
|
} else if(imports.value.type == 'identifier') {
|
||||||
|
default_import = imports.value.value
|
||||||
|
children = [imports.value]
|
||||||
|
} else {
|
||||||
|
children = imports.value.value
|
||||||
|
}
|
||||||
// remove quotes
|
// remove quotes
|
||||||
const module_string = module.value.slice(1, module.value.length - 1)
|
const module_string = module.value.slice(1, module.value.length - 1)
|
||||||
// if url starts with protocol, then it is always external
|
// if url starts with protocol, then it is always external
|
||||||
@@ -1252,25 +1266,44 @@ const import_statement =
|
|||||||
// TODO refactor hanlding of string literals. Drop quotes from value and
|
// TODO refactor hanlding of string literals. Drop quotes from value and
|
||||||
// fix codegen for string_literal
|
// fix codegen for string_literal
|
||||||
module: module_string,
|
module: module_string,
|
||||||
children: identifiers == null ? [] : identifiers.value[0].value,
|
default_import,
|
||||||
|
children,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const export_statement =
|
const export_statement =
|
||||||
if_ok(
|
either(
|
||||||
seq_select(1, [
|
if_ok(
|
||||||
literal('export'),
|
seq_select(1, [
|
||||||
// TODO export let statement, export default, etc. Or not allow
|
literal('export'),
|
||||||
// let statement because export let cannot be compiled properly?
|
// TODO export let statement, export default, etc. Or not allow
|
||||||
const_statement,
|
// let statement because export let cannot be compiled properly?
|
||||||
]),
|
const_statement,
|
||||||
({value, ...node}) => ({
|
]),
|
||||||
...node,
|
({value, ...node}) => ({
|
||||||
not_evaluatable: true,
|
...node,
|
||||||
type: 'export',
|
not_evaluatable: true,
|
||||||
children: [value],
|
type: 'export',
|
||||||
})
|
is_default: false,
|
||||||
|
children: [value],
|
||||||
|
})
|
||||||
|
),
|
||||||
|
if_ok(
|
||||||
|
seq_select(2, [
|
||||||
|
literal('export'),
|
||||||
|
literal('default'),
|
||||||
|
expr,
|
||||||
|
]),
|
||||||
|
({value, ...node}) => ({
|
||||||
|
...node,
|
||||||
|
not_evaluatable: true,
|
||||||
|
type: 'export',
|
||||||
|
is_default: true,
|
||||||
|
children: [value],
|
||||||
|
})
|
||||||
|
),
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -1479,7 +1512,6 @@ const update_children_not_rec = (node, children = node.children) => {
|
|||||||
}
|
}
|
||||||
} else if(node.type == 'import') {
|
} else if(node.type == 'import') {
|
||||||
return {...node,
|
return {...node,
|
||||||
imports: children,
|
|
||||||
is_statement: true,
|
is_statement: true,
|
||||||
}
|
}
|
||||||
} else if(node.type == 'export') {
|
} else if(node.type == 'export') {
|
||||||
@@ -1685,7 +1717,7 @@ const do_load_modules = (module_names, loader, already_loaded) => {
|
|||||||
|
|
||||||
const deps = uniq(
|
const deps = uniq(
|
||||||
Object.values(modules)
|
Object.values(modules)
|
||||||
.map(m => Object.keys(collect_imports(m)))
|
.map(m => collect_imports(m))
|
||||||
.flat()
|
.flat()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
31
test/test.js
31
test/test.js
@@ -1145,6 +1145,37 @@ export const tests = [
|
|||||||
assert_equal(state.parse_result.ok, true)
|
assert_equal(state.parse_result.ok, true)
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
test('modules default export', () => {
|
||||||
|
const modules = {
|
||||||
|
'' : "import foo from 'foo'; foo",
|
||||||
|
'foo': `export default 1`
|
||||||
|
}
|
||||||
|
assert_code_evals_to(modules , 1)
|
||||||
|
|
||||||
|
const i = test_initial_state(modules)
|
||||||
|
const s = COMMANDS.goto_definition(i, modules[''].indexOf('foo')).state
|
||||||
|
assert_equal(current_cursor_position(s), modules['foo'].indexOf('1'))
|
||||||
|
assert_equal(s.current_module, 'foo')
|
||||||
|
}),
|
||||||
|
|
||||||
|
test('modules default import', () => {
|
||||||
|
const code = `
|
||||||
|
// external
|
||||||
|
import foo from 'foo.js'
|
||||||
|
foo
|
||||||
|
`
|
||||||
|
const initial = test_initial_state(code)
|
||||||
|
|
||||||
|
const next = COMMANDS.external_imports_loaded(initial, initial, {
|
||||||
|
'foo.js': {
|
||||||
|
ok: true,
|
||||||
|
module: {
|
||||||
|
'default': 'foo_value'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
assert_equal(active_frame(next).children.at(-1).result.value, 'foo_value')
|
||||||
|
}),
|
||||||
|
|
||||||
// Static analysis
|
// Static analysis
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user