mirror of
https://github.com/leporello-js/leporello-js
synced 2026-01-13 21:14:28 -08:00
improve static analysis
This commit is contained in:
@@ -71,7 +71,6 @@ const add_trivial_definition = node => {
|
|||||||
* will be assigned by the time the closures would be called
|
* will be assigned by the time the closures would be called
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// TODO in same pass find already declared
|
|
||||||
export const find_definitions = (ast, globals, scope = {}, closure_scope = {}, module_name) => {
|
export const find_definitions = (ast, globals, scope = {}, closure_scope = {}, module_name) => {
|
||||||
|
|
||||||
|
|
||||||
@@ -246,63 +245,100 @@ 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. For default import there is
|
|
||||||
// default export
|
|
||||||
})
|
})
|
||||||
// Topological sort
|
|
||||||
// For each module
|
|
||||||
// Depth-traverse deps and detect cycles
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
TODO: relax, only disallow code that leads to broken target code
|
|
||||||
|
|
||||||
code analysis:
|
code analysis:
|
||||||
- function must have one and only one return statement in every branch
|
|
||||||
- return must be the last statement in block
|
|
||||||
|
|
||||||
- name is declared once and only once (including function args). Name can be imported once
|
- name is declared once and only once (including function args). Name can be imported once
|
||||||
- let must be assigned once and only once (in each branch)
|
- every assignment can only be to identifier is earlier declared by let
|
||||||
- every assignment can only be to if identifier is earlier declared by let
|
- cannot import names that are not exported from modules.If there is default import from module, there should be default export
|
||||||
- assignment can only be inside if statement (after let) (relax it?)
|
|
||||||
- cannot import names that are not exported from modules
|
|
||||||
- module can be imported either as external or regular
|
- module can be imported either as external or regular
|
||||||
- cannot return from modules (even inside toplevel if statements)
|
- cannot return from modules toplevel
|
||||||
- await only in async fns
|
- await only in async fns
|
||||||
|
- import only from toplevel
|
||||||
*/
|
*/
|
||||||
export const analyze = (node, is_toplevel = true) => {
|
export const analyze = (node, is_toplevel = true) => {
|
||||||
// TODO remove
|
return [
|
||||||
return []
|
...analyze_await(node, true),
|
||||||
|
...named_declared_once(node),
|
||||||
/*
|
]
|
||||||
// TODO sort by location?
|
}
|
||||||
if(node.type == 'do') {
|
|
||||||
let illegal_returns
|
const collect_problems = (node, context, collector) => {
|
||||||
if(is_toplevel) {
|
const {context: next_context, problems: node_problems} = collector(node, context)
|
||||||
illegal_returns = node.stmts.filter(s => s.type == 'return')
|
if(node.children == null) {
|
||||||
} else {
|
return node_problems
|
||||||
const last = node.stmts[node.stmts.length - 1];
|
}
|
||||||
illegal_returns = node.stmts.filter(s => s.type == 'return' && s != last);
|
return node.children.reduce(
|
||||||
|
(problems, c) => {
|
||||||
returns.map(node => ({
|
const ps = collect_problems(c, next_context, collector)
|
||||||
node,
|
if(ps == null) {
|
||||||
message: 'illegal return statement',
|
return problems
|
||||||
}));
|
}
|
||||||
|
if(problems == null) {
|
||||||
const last_return = last.type == 'return'
|
return ps
|
||||||
? null
|
}
|
||||||
: {node: last, message: 'block must end with return statement'}
|
return problems.concat(ps)
|
||||||
|
},
|
||||||
|
node_problems
|
||||||
// TODO recur to childs
|
)
|
||||||
}
|
}
|
||||||
} else if(node.children != null){
|
|
||||||
return node.children
|
const analyze_await = (node, is_async_context = true) => {
|
||||||
.map(n => analyze(n, node.type == 'function_expr' ? false : is_toplevel))
|
const result = collect_problems(node, is_async_context, (node, is_async_context) => {
|
||||||
.reduce((ps, p) => ps.concat(p), [])
|
if(node.type == 'function_expr') {
|
||||||
} else {
|
return {problems: null, context: node.is_async}
|
||||||
// TODO
|
}
|
||||||
1
|
if(node.type == 'unary' && node.operator == 'await' && !is_async_context) {
|
||||||
}
|
const _await = node.children[0]
|
||||||
*/
|
const problem = {
|
||||||
|
index: _await.index,
|
||||||
|
length: _await.length,
|
||||||
|
message: 'await is only valid in async functions and the top level bodies of modules',
|
||||||
|
}
|
||||||
|
return {problems: [problem], context: is_async_context}
|
||||||
|
}
|
||||||
|
return {problems: null, context: is_async_context}
|
||||||
|
})
|
||||||
|
|
||||||
|
return result ?? []
|
||||||
|
}
|
||||||
|
|
||||||
|
const named_declared_once = node => {
|
||||||
|
return collect_problems(node, null, (node, cxt) => {
|
||||||
|
if(node.type == 'do') {
|
||||||
|
const names = node
|
||||||
|
.children
|
||||||
|
.map(c => {
|
||||||
|
if(c.type == 'function_decl') {
|
||||||
|
const function_expr = c.children[0]
|
||||||
|
return {
|
||||||
|
value: function_expr.name,
|
||||||
|
index: function_expr.index,
|
||||||
|
length: function_expr.name.length
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const scope = scope_from_node(c)
|
||||||
|
return scope == null
|
||||||
|
? null
|
||||||
|
: Object.values(scope)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.flat()
|
||||||
|
.filter(n => n != null)
|
||||||
|
const duplicates = names.filter((n, i) =>
|
||||||
|
names.find((name, j) => name.value == n.value && j < i) != null
|
||||||
|
)
|
||||||
|
const problems = duplicates.map(d => ({
|
||||||
|
index: d.index,
|
||||||
|
length: d.length,
|
||||||
|
message: `Identifier '${d.value}' has already been declared`,
|
||||||
|
}))
|
||||||
|
return {context: null, problems}
|
||||||
|
} else {
|
||||||
|
return {context: null, problems: null}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
106
test/test.js
106
test/test.js
@@ -1244,7 +1244,100 @@ export const tests = [
|
|||||||
1
|
1
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
test('await only inside async fns', () => {
|
||||||
|
const parse_result = do_parse('function x() { await 1 }')
|
||||||
|
assert_equal(parse_result.ok, false)
|
||||||
|
}),
|
||||||
|
|
||||||
|
test('identifier has already been declared', () => {
|
||||||
|
const code = `
|
||||||
|
const x = 1
|
||||||
|
const x = 2
|
||||||
|
`
|
||||||
|
const i = test_initial_state(code)
|
||||||
|
assert_equal(i.parse_result.ok, false)
|
||||||
|
assert_equal(
|
||||||
|
i.parse_result.problems,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
index: code.indexOf('x = 2'),
|
||||||
|
length: 1,
|
||||||
|
message: "Identifier 'x' has already been declared",
|
||||||
|
module: '',
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
|
||||||
|
test('identifier has already been declared fn decl', () => {
|
||||||
|
const code = `
|
||||||
|
const x = 1
|
||||||
|
function x() {
|
||||||
|
}
|
||||||
|
`
|
||||||
|
const i = test_initial_state(code)
|
||||||
|
assert_equal(i.parse_result.ok, false)
|
||||||
|
assert_equal(
|
||||||
|
i.parse_result.problems,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
index: code.indexOf('function x()'),
|
||||||
|
length: 1,
|
||||||
|
message: "Identifier 'x' has already been declared",
|
||||||
|
module: '',
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
|
||||||
|
test('identifier has already been declared export', () => {
|
||||||
|
const code = `
|
||||||
|
export const x = 1
|
||||||
|
function x() {
|
||||||
|
}
|
||||||
|
`
|
||||||
|
const i = test_initial_state(code)
|
||||||
|
assert_equal(i.parse_result.ok, false)
|
||||||
|
assert_equal(
|
||||||
|
i.parse_result.problems,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
index: code.indexOf('function x()'),
|
||||||
|
length: 1,
|
||||||
|
message: "Identifier 'x' has already been declared",
|
||||||
|
module: '',
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
|
||||||
|
test('identifier has already been declared import', () => {
|
||||||
|
const code = {
|
||||||
|
'': `
|
||||||
|
import {x} from 'x.js'
|
||||||
|
function x() {
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
'x.js': `
|
||||||
|
export const x = 1
|
||||||
|
`
|
||||||
|
}
|
||||||
|
const i = test_initial_state(code)
|
||||||
|
assert_equal(i.parse_result.ok, false)
|
||||||
|
assert_equal(
|
||||||
|
i.parse_result.problems,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
index: code[''].indexOf('function x()'),
|
||||||
|
length: 1,
|
||||||
|
message: "Identifier 'x' has already been declared",
|
||||||
|
module: '',
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
|
||||||
test('function decl', () => {
|
test('function decl', () => {
|
||||||
const code = `
|
const code = `
|
||||||
function fib(n) {
|
function fib(n) {
|
||||||
@@ -1265,17 +1358,6 @@ export const tests = [
|
|||||||
assert_equal(s2.active_calltree_node.value, 5)
|
assert_equal(s2.active_calltree_node.value, 5)
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/*
|
|
||||||
test('await only in async', () => {
|
|
||||||
const code = `
|
|
||||||
() => {
|
|
||||||
await 1
|
|
||||||
}
|
|
||||||
`
|
|
||||||
console.log(do_parse(code).problems[0])
|
|
||||||
//return assert_equal(do_parse(code).problems[0].message, 'undeclared identifier: x')
|
|
||||||
}),
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
TODO use before assignment
|
TODO use before assignment
|
||||||
|
|||||||
Reference in New Issue
Block a user