src/semantic.js
function walk(node, parent, ctx) {
    if (!node) {
        return
    }

    // walk each node if array
    if (util.isArray(node)) {
        return node.forEach(function(node){
            walk(node, parent, ctx)
        })
    }

    switch (node.type) {
        case T.IDENT :
            var name = node.content
            // do not allow ES keywords as identifier
            if (util.isESKeyword(name) && !util.isBuiltinFunction(name)) {
                ctx.error('`' + name + '` is a ECMAScript reserved keyword', node.loc.firstLine)
            }
        break
        case T.MODULE :
        case T.ARRAY  :
            node.content.forEach(function(node){
                if (isRIdent(node)) {
                    ctx.error('can not use `R` as identifier', node.loc.firstLine)
                }
            })
        case T.OBJECT :
            walk(node.content, node, ctx)
        break
        case T.PROPERTY :
            if (isRIdent(node.value)) {
                ctx.error('can not use `R` as identifier', node.value.loc.firstLine)
            }
            walk(node.value, node, ctx)
        break
        case T.SEXPR :
            var operator = node.operator
            var data     = node.content
            var lineno   = operator.loc.firstLine

            // just s-expressions and identifiers are callables
            if (operator.type !== T.IDENT && operator.type !== T.SEXPR) {
                ctx.error(unexpected(operator.type), lineno)
            }

            switch (operator.content) {
                case 'def':
                    // only in the module and function scope
                    if (parent.type !== T.MODULE && !isFnLiteral(parent)) {
                        ctx.error('unexpected `def`', lineno)
                    }
                    // must have one or two arguments
                    if (data.length == 0 || data.length > 2) {
                        ctx.error('`def` must contain one or two arguments', lineno)
                    } else {
                        var varName = data[0].content
                        // can only define a var once in the same scope
                        if (util.isDefVar(parent, varName)) {
                            ctx.error('redefining `' + varName + '`', lineno)
                        } else {
                            util.addDefVar(parent, varName)
                        }
                        // can not be used as the last s-expression inside a function scope
                        if (isFnLiteral(parent) && parent.content[parent.content.length - 1].operator === operator) {
                            ctx.error('`def` can not be returned or used as expression', lineno)
                        }
                    }
                    // can not be defined using qualified idents
                    if (isQualifiedIdent(data[0])) {
                        ctx.error('unespected qualified ident `' + varName + '`', lineno)
                    }
                break
                case 'import' :
                    if (parent.type !== T.MODULE) {
                        ctx.error('unexpected `import`', lineno)
                    }
                    if (data.length !== 2) {
                        ctx.error('`import` function must contain exactly two arguments', lineno)
                    } else {
                        var refer = data[1]
                        switch (refer.type) {
                            case T.IDENT :
                            break
                            case T.ARRAY :
                                // allowed array of identifiers
                                if (refer.content.length === 0) {
                                    ctx.error(unexpected('empty array'), node.loc.firstLine)
                                }
                                refer.content.forEach(function(node){
                                    if (node.type !== T.IDENT) {
                                        ctx.error(unexpected(node.type), node.loc.firstLine)
                                    }
                                })
                            break
                            default :
                                // allowed identifier
                                ctx.error(unexpected(refer.type), refer.loc.firstLine)
                        }
                    }
                break
                case 'export' :
                    var exported
                    function isDefined (varName, parent) {
                        var defined = false
                        parent.content.forEach(function(node) {
                            if (node.type === T.SEXPR
                                && node.operator.content === 'def'
                                && node.content[0].content === varName) {
                                defined = true
                            }
                        })
                        return defined
                    }

                    if (parent.type === T.MODULE) {
                        // data must be at least
                        if (data.length === 1) {
                            exported = data[0]
                            if (exported.type === T.IDENT) {
                                // it must be defined or imported
                                if (!isDefined(exported.content, parent)) {
                                    ctx.error('\'' + exported.content + '\' is not defined', exported.loc.firstLine)
                                }
                            } else if (exported.type == T.ARRAY) {
                                if (exported.content.length > 0) {
                                    exported.content.forEach(function(node) {
                                        if (!isDefined(node.content, parent)) {
                                            ctx.error('\'' + node.content + '\' is not defined', node.loc.firstLine)
                                        }
                                    })
                                } else {
                                    ctx.error('expected identifier', exported.loc.firstLine)
                                }
                            } else {
                                ctx.error('invalid exported element', lineno)
                            }
                        } else {
                            ctx.error('invalid exported element', lineno)
                        }
                    } else {
                        ctx.error('only can be exported in module scope', lineno)
                    }
                break
                case 'new' :
                    // must provide a class
                    if (data.length === 0) {
                        ctx.error('`new` function must contain at least one argument', lineno)
                    }
                break
                case 'fn' :
                    var args = data[0]
                    if (args && args.type === T.ARRAY) {
                        // arguments must be an list of identifiers
                        args.content.forEach(function(node){
                            if (node.type !== T.IDENT) {
                                ctx.error(unexpected(node.type), node.loc.firstLine)
                            }
                        })
                    } else {
                        // first arguments must be a list of arguments
                        ctx.error('no arguments provided to `fn` function', lineno)
                    }
                break
                case 'alter' :
                    // must have one or two args at lear
                    if (data.length === 0 || data.length > 2) {
                        ctx.error('`alter` function must contain one or two arguments', lineno)
                    }
                break
            }

            // can not use `R` identifier as operator
            if (isRIdent(operator)) {
                ctx.error('can not use `R` as identifier', operator.loc.firstLine)
            }

            data.forEach(function(node) {
                // can not use `R` identifier directly
                if (isRIdent(node)) {
                    ctx.error('can not use `R` as identifier', node.loc.firstLine)
                }
                // can not use builtin fn as data in s-expressions
                if (util.isBuiltinFunction(node.content)) {
                    ctx.error(unexpected(node.content), node.loc.firstLine)
                }
            })

            walk(operator, node, ctx)
            walk(data, node, ctx)
        break
    }
}
function walk(node, parent, ctx) {
    if (!node) {
        return
    }

    // walk each node if array
    if (util.isArray(node)) {
        return node.forEach(function(node){
            walk(node, parent, ctx)
        })
    }

    switch (node.type) {
        case T.IDENT :
            var name = node.content
            // do not allow ES keywords as identifier
            if (util.isESKeyword(name) && !util.isBuiltinFunction(name)) {
                ctx.error('`' + name + '` is a ECMAScript reserved keyword', node.loc.firstLine)
            }
        break
        case T.MODULE :
        case T.ARRAY  :
            node.content.forEach(function(node){
                if (isRIdent(node)) {
                    ctx.error('can not use `R` as identifier', node.loc.firstLine)
                }
            })
        case T.OBJECT :
            walk(node.content, node, ctx)
        break
        case T.PROPERTY :
            if (isRIdent(node.value)) {
                ctx.error('can not use `R` as identifier', node.value.loc.firstLine)
            }
            walk(node.value, node, ctx)
        break
        case T.SEXPR :
            var operator = node.operator
            var data     = node.content
            var lineno   = operator.loc.firstLine

            // just s-expressions and identifiers are callables
            if (operator.type !== T.IDENT && operator.type !== T.SEXPR) {
                ctx.error(unexpected(operator.type), lineno)
            }

            switch (operator.content) {
                case 'def':
                    // only in the module and function scope
                    if (parent.type !== T.MODULE && !isFnLiteral(parent)) {
                        ctx.error('unexpected `def`', lineno)
                    }
                    // must have one or two arguments
                    if (data.length == 0 || data.length > 2) {
                        ctx.error('`def` must contain one or two arguments', lineno)
                    } else {
                        var varName = data[0].content
                        // can only define a var once in the same scope
                        if (util.isDefVar(parent, varName)) {
                            ctx.error('redefining `' + varName + '`', lineno)
                        } else {
                            util.addDefVar(parent, varName)
                        }
                        // can not be used as the last s-expression inside a function scope
                        if (isFnLiteral(parent) && parent.content[parent.content.length - 1].operator === operator) {
                            ctx.error('`def` can not be returned or used as expression', lineno)
                        }
                    }
                    // can not be defined using qualified idents
                    if (isQualifiedIdent(data[0])) {
                        ctx.error('unespected qualified ident `' + varName + '`', lineno)
                    }
                break
                case 'import' :
                    if (parent.type !== T.MODULE) {
                        ctx.error('unexpected `import`', lineno)
                    }
                    if (data.length !== 2) {
                        ctx.error('`import` function must contain exactly two arguments', lineno)
                    } else {
                        var refer = data[1]
                        switch (refer.type) {
                            case T.IDENT :
                            break
                            case T.ARRAY :
                                // allowed array of identifiers
                                if (refer.content.length === 0) {
                                    ctx.error(unexpected('empty array'), node.loc.firstLine)
                                }
                                refer.content.forEach(function(node){
                                    if (node.type !== T.IDENT) {
                                        ctx.error(unexpected(node.type), node.loc.firstLine)
                                    }
                                })
                            break
                            default :
                                // allowed identifier
                                ctx.error(unexpected(refer.type), refer.loc.firstLine)
                        }
                    }
                break
                case 'export' :
                    var exported
                    function isDefined (varName, parent) {
                        var defined = false
                        parent.content.forEach(function(node) {
                            if (node.type === T.SEXPR
                                && node.operator.content === 'def'
                                && node.content[0].content === varName) {
                                defined = true
                            }
                        })
                        return defined
                    }

                    if (parent.type === T.MODULE) {
                        // data must be at least
                        if (data.length === 1) {
                            exported = data[0]
                            if (exported.type === T.IDENT) {
                                // it must be defined or imported
                                if (!isDefined(exported.content, parent)) {
                                    ctx.error('\'' + exported.content + '\' is not defined', exported.loc.firstLine)
                                }
                            } else if (exported.type == T.ARRAY) {
                                if (exported.content.length > 0) {
                                    exported.content.forEach(function(node) {
                                        if (!isDefined(node.content, parent)) {
                                            ctx.error('\'' + node.content + '\' is not defined', node.loc.firstLine)
                                        }
                                    })
                                } else {
                                    ctx.error('expected identifier', exported.loc.firstLine)
                                }
                            } else {
                                ctx.error('invalid exported element', lineno)
                            }
                        } else {
                            ctx.error('invalid exported element', lineno)
                        }
                    } else {
                        ctx.error('only can be exported in module scope', lineno)
                    }
                break
                case 'new' :
                    // must provide a class
                    if (data.length === 0) {
                        ctx.error('`new` function must contain at least one argument', lineno)
                    }
                break
                case 'fn' :
                    var args = data[0]
                    if (args && args.type === T.ARRAY) {
                        // arguments must be an list of identifiers
                        args.content.forEach(function(node){
                            if (node.type !== T.IDENT) {
                                ctx.error(unexpected(node.type), node.loc.firstLine)
                            }
                        })
                    } else {
                        // first arguments must be a list of arguments
                        ctx.error('no arguments provided to `fn` function', lineno)
                    }
                break
                case 'alter' :
                    // must have one or two args at lear
                    if (data.length === 0 || data.length > 2) {
                        ctx.error('`alter` function must contain one or two arguments', lineno)
                    }
                break
            }

            // can not use `R` identifier as operator
            if (isRIdent(operator)) {
                ctx.error('can not use `R` as identifier', operator.loc.firstLine)
            }

            data.forEach(function(node) {
                // can not use `R` identifier directly
                if (isRIdent(node)) {
                    ctx.error('can not use `R` as identifier', node.loc.firstLine)
                }
                // can not use builtin fn as data in s-expressions
                if (util.isBuiltinFunction(node.content)) {
                    ctx.error(unexpected(node.content), node.loc.firstLine)
                }
            })

            walk(operator, node, ctx)
            walk(data, node, ctx)
        break
    }
}
function walk ( node , parent , ctx ) { if ( ! node ) { return } // walk each node if array if ( util . isArray ( node ) ) { return node . forEach ( function ( node ) { walk ( node , parent , ctx ) } ) } switch ( node . type ) { case T . IDENT : var name = node . content // do not allow ES keywords as identifier if ( util . isESKeyword ( name ) && ! util . isBuiltinFunction ( name ) ) { ctx . error ( '`' + name + '` is a ECMAScript reserved keyword' , node . loc . firstLine ) } break case T . MODULE : case T . ARRAY : node . content . forEach ( function ( node ) { if ( isRIdent ( node ) ) { ctx . error ( 'can not use `R` as identifier' , node . loc . firstLine ) } } ) case T . OBJECT : walk ( node . content , node , ctx ) break case T . PROPERTY : if ( isRIdent ( node . value ) ) { ctx . error ( 'can not use `R` as identifier' , node . value . loc . firstLine ) } walk ( node . value , node , ctx ) break case T . SEXPR : var operator = node . operator var data = node . content var lineno = operator . loc . firstLine // just s-expressions and identifiers are callables if ( operator . type !== T . IDENT && operator . type !== T . SEXPR ) { ctx . error ( unexpected ( operator . type ) , lineno ) } switch ( operator . content ) { case 'def' : // only in the module and function scope if ( parent . type !== T . MODULE && ! isFnLiteral ( parent ) ) { ctx . error ( 'unexpected `def`' , lineno ) } // must have one or two arguments if ( data . length == 0 || data . length > 2 ) { ctx . error ( '`def` must contain one or two arguments' , lineno ) } else { var varName = data [ 0 ] . content // can only define a var once in the same scope if ( util . isDefVar ( parent , varName ) ) { ctx . error ( 'redefining `' + varName + '`' , lineno ) } else { util . addDefVar ( parent , varName ) } // can not be used as the last s-expression inside a function scope if ( isFnLiteral ( parent ) && parent . content [ parent . content . length - 1 ] . operator === operator ) { ctx . error ( '`def` can not be returned or used as expression' , lineno ) } } // can not be defined using qualified idents if ( isQualifiedIdent ( data [ 0 ] ) ) { ctx . error ( 'unespected qualified ident `' + varName + '`' , lineno ) } break case 'import' : if ( parent . type !== T . MODULE ) { ctx . error ( 'unexpected `import`' , lineno ) } if ( data . length !== 2 ) { ctx . error ( '`import` function must contain exactly two arguments' , lineno ) } else { var refer = data [ 1 ] switch ( refer . type ) { case T . IDENT : break case T . ARRAY : // allowed array of identifiers if ( refer . content . length === 0 ) { ctx . error ( unexpected ( 'empty array' ) , node . loc . firstLine ) } refer . content . forEach ( function ( node ) { if ( node . type !== T . IDENT ) { ctx . error ( unexpected ( node . type ) , node . loc . firstLine ) } } ) break default : // allowed identifier ctx . error ( unexpected ( refer . type ) , refer . loc . firstLine ) } } break case 'export' : var exported function isDefined ( varName , parent ) { var defined = false parent . content . forEach ( function ( node ) { if ( node . type === T . SEXPR && node . operator . content === 'def' && node . content [ 0 ] . content === varName ) { defined = true } } ) return defined } if ( parent . type === T . MODULE ) { // data must be at least if ( data . length === 1 ) { exported = data [ 0 ] if ( exported . type === T . IDENT ) { // it must be defined or imported if ( ! isDefined ( exported . content , parent ) ) { ctx . error ( '\'' + exported . content + '\' is not defined' , exported . loc . firstLine ) } } else if ( exported . type == T . ARRAY ) { if ( exported . content . length > 0 ) { exported . content . forEach ( function ( node ) { if ( ! isDefined ( node . content , parent ) ) { ctx . error ( '\'' + node . content + '\' is not defined' , node . loc . firstLine ) } } ) } else { ctx . error ( 'expected identifier' , exported . loc . firstLine ) } } else { ctx . error ( 'invalid exported element' , lineno ) } } else { ctx . error ( 'invalid exported element' , lineno ) } } else { ctx . error ( 'only can be exported in module scope' , lineno ) } break case 'new' : // must provide a class if ( data . length === 0 ) { ctx . error ( '`new` function must contain at least one argument' , lineno ) } break case 'fn' : var args = data [ 0 ] if ( args && args . type === T . ARRAY ) { // arguments must be an list of identifiers args . content . forEach ( function ( node ) { if ( node . type !== T . IDENT ) { ctx . error ( unexpected ( node . type ) , node . loc . firstLine ) } } ) } else { // first arguments must be a list of arguments ctx . error ( 'no arguments provided to `fn` function' , lineno ) } break case 'alter' : // must have one or two args at lear if ( data . length === 0 || data . length > 2 ) { ctx . error ( '`alter` function must contain one or two arguments' , lineno ) } break } // can not use `R` identifier as operator if ( isRIdent ( operator ) ) { ctx . error ( 'can not use `R` as identifier' , operator . loc . firstLine ) } data . forEach ( function ( node ) { // can not use `R` identifier directly if ( isRIdent ( node ) ) { ctx . error ( 'can not use `R` as identifier' , node . loc . firstLine ) } // can not use builtin fn as data in s-expressions if ( util . isBuiltinFunction ( node . content ) ) { ctx . error ( unexpected ( node . content ) , node . loc . firstLine ) } } ) walk ( operator , node , ctx ) walk ( data , node , ctx ) break } }
walk through nodes
walk through nodes