SmaliParser.js

const _fs_ = require('fs');
const _es_ = require('event-stream');


var ut = require("./Utils.js"); 
var OPCODE = require("./Opcode.js");
var CONST = require("./CoreConst.js");
const CLASS = require("./CoreClass.js");
const Event = require("./Event.js");
const Logger = require("./Logger.js")();
const Accessor = require("./AccessFlags");

const SML_MAIN=0;
const SML_METH=1;
const SML_ANNO=2;
const SML_PSWITCH=3;

const ERR_PARSE=0;
const LOG_DBG = false;

var LEX = CONST.LEX;

// todo voir WABT, square attack, mesh

var LOG = {
    DEBUG: function(txt){
        if(LOG_DBG) console.log(txt); 
    }
};

// end of core class

var Checker = {
    isBasicType: function(c){
        return (CONST.TYPES[c] != undefined);
    },
    isObjectType: function(c){
        return c=="L";
    },
    makeFnHashcode: function(modif,cls,name,args,ret){
        let xargs = "";
        for(let i in args) xargs+="<"+args[i]._hashcode+">";
        return modif._name+"|"+cls.name+"|"+name+"|"+xargs+"|"+ret._hashcode;
    },
    makeFieldHashcode: function(modif,cls,name,type){
        //console.log(type);
        return modif._name+"|"+cls.name+"|"+name+"|"+type._hashcode;
    }
};


/** 
 * Represent the Smali parser 
 * @class
 */
class SmaliParser
{
    constructor(context=null){
        this.ctx = context;
        this.state = null; //  state of the parser
        this.subject = null; // parsed smali file
        
        this.obj = null;
        this.objReady = false;

        this.__tmp_meth = null;
        this.__tmp_block = null;

        this.__instr_ctr = null;
        this.__instr_line = null;

        this.currentLine = null;

        let self = this;
        this.__appendBlock_callback = {
            // disabled for perform reasons
            /*basicblock: function(meth,block){
                this.ctx.bus.send(new Event.Event({
                    type: "disass.bb.new" 
                }));
            },*/
            datablock: function(meth,block){
                self.ctx.bus.send(new Event.Event({
                    type: "disass.datablock.new",
                    data: block 
                }));
            }
        };
    }

    setContext(context){
        this.ctx = context;
    }

    isModifier(name){
        for(let i in LEX.MODIFIER) 
            if(LEX.MODIFIER[i]==name) 
                return true;
        return false;
    }

    modifier(src){
        if(src instanceof String) src=src.split(LEX.TOKEN.SPACE);
        let mod = new Accessor.AccessFlags() , next=true;

        //if(src.length<2) return ERR_PARSE;
        for(let i=0; i<src.length && next; i++){
            mod._match++;
            switch(ut.trim(src[i])){
                case LEX.MODIFIER.PRIVATE:
                    mod.private = true;
                    break;
                case LEX.MODIFIER.PROTECTED:
                    mod.protected = true;
                    break;
                case LEX.MODIFIER.PUBLIC:
                    mod.public = true;
                    break;
                case LEX.MODIFIER.STATIC:
                    mod.static = true;
                    break;
                case LEX.MODIFIER.VOLATILE:
                    mod.volatile = true;
                    break;
                case LEX.MODIFIER.ABSTRACT:
                    mod.abstract = true;
                    break;
                case LEX.MODIFIER.FINAL:
                    mod.final = true;
                    break;
                case LEX.MODIFIER.CONSTR:
                    mod.construct = true;
                    break;
                case LEX.MODIFIER.SYNTHETIC:
                    mod.synth = true;
                    break;
                case LEX.MODIFIER.ENUM:
                    mod.enum = true;
                    break;
                case LEX.MODIFIER.TRANSIENT:
                    mod.transient = true;
                    break;
                case LEX.MODIFIER.DECLSYNC:
                    mod.declsync = true;
                    break;
                case LEX.MODIFIER.BRIDGE:
                    mod.bridge = true;
                    break;
                case LEX.MODIFIER.VARARG:
                    mod.varargs = true;
                    break;
                case LEX.MODIFIER.NATIVE:
                    mod.native = true;
                    break;
                case LEX.MODIFIER.INTERFACE:
                    mod.interface = true;
                    break;
                case LEX.MODIFIER.ANNOTATION:
                    mod.annotation = true;
                    break;
                case LEX.MODIFIER.STRICTFP:
                    mod.strictfp = true;
                    break;
                case LEX.MODIFIER.SYNCHRONIZED:
                    mod.synchronized = true;
                    break;
                default:
                    next=false;
                    mod._match--;
                    break;
            }
        }

        if(LOG_DBG){
            LOG.DEBUG("[parser::modifier] "+mod.sprint());
        }
        return mod;
    }

    fqcn(src){
        if(src.length==0) return ERR_PARSE;
        let raw="";
        raw = (src instanceof Array)? src[0] : src;

        // remove additional chars : "L"  at begin and ";" at end. 
        // console.log("PARSER::FQCN > ",src);
        let s=raw.substr(1,raw.length-2);
        while(s.indexOf("/")>-1) s=s.replace("/",".");
        
        LOG.DEBUG("[parser::fqcn] "+s);
        return s; 
    }

    fspath(src){
        let s=src;
        s = s.substr(s.indexOf(LEX.TOKEN.DELIMITER)+1);
        s = s.substr(0,s.indexOf(LEX.TOKEN.DELIMITER));
        LOG.DEBUG("[parser::fspath] "+s);
        return s;
    }

    // char 
    basicTypes(c){
        return CONST.TYPES[c];
    }

    class(src){
        let fqcn=null,end=-1;
        LOG.DEBUG("---------------------------------------------\n[parser::class] Start ");

        //console.log("[?] Instruction parsed : "+OPCODE.CTR);
        if(src instanceof String)
            src=ut.trim(src).split(LEX.STRUCT.SPACE); 
        
        if(src[0]==LEX.STRUCT.CLASS)   src.shift();

        this.obj = new CLASS.Class();
        //console.log(src);
        // parse modifiers
        this.obj.modifiers = this.modifier(src);
        //console.log(src);

        // clean src with identified modifier
        for(let i=0; i<this.obj.modifiers._match; i++) src.shift();

        // console.log(src);
        // parse nam
        fqcn = this.fqcn(src);
        end = fqcn.lastIndexOf(".");
        this.obj.fqcn = this.obj.name = fqcn;
        this.obj.package = fqcn.substr(0,end); 
        this.obj.simpleName = fqcn.substr(end+1); 
        if(this.obj.name.indexOf(LEX.TOKEN.INNER_FQCN)>-1){
            this.obj.simpleName = this.obj.simpleName.substr(this.obj.simpleName.indexOf(LEX.TOKEN.INNER_FQCN)+1);
            this.obj.innerClass = true;
            this.obj.enclosingClass = this.obj.name.substr(0,this.obj.name.indexOf(LEX.TOKEN.INNER_FQCN));
        } 

        this.obj._hashcode = this.obj.hashCode();

        LOG.DEBUG("[parser::class] End\n---------------------------------------------");
        return this.obj;
    }


    type(src){
        let i=0,l=-1,types=[],s=src,fqn=null,isArray=false;

        while(i<src.length){
            if(src[i]==LEX.TOKEN.ARRAY){
                isArray=true;
                i++;
                continue;
            }

            if(src[i]==LEX.TOKEN.OBJREF){
                l=src.indexOf(";",i);
                fqn=this.fqcn(src.substr(i,l-i+1));
                //console.log(fqn);
                types.push(new CLASS.ObjectType(fqn, isArray));
                i=l+1;
                isArray=false;
                continue;

            }else if(Checker.isBasicType(src[i])){
                types.push(new CLASS.BasicType(src[i], isArray));
                i++;
                isArray = false;
                continue;
            }
            else{
                console.log("[!] Unknow type : "+src[i]+" (in "+src+")");
                break;
            }
        }

        return types;
    }


    /**
     * To parse a method header
     */
    methodHeader(src){
        let mod = this.modifier(src), raw=null, tmp=null, args=null, ret=null, sa=0, ea=0;
        let argTypes = null;
        // clean src with identified modifier
        for(let i=0; i<mod._match; i++) src.shift();

        if(src.length > 1){
            console.log(src,mod);
            console.log("[!] Method has more modifiers");
        }

        this.__tmp_meth.modifiers = mod;
        raw = ut.trim(src[src.length-1]);

        // risque d'UTF8 / autre dans le nom, quid des regexp;
        tmp = raw.substr(0,sa=raw.indexOf(LEX.TOKEN.METH_ARG_B));  
        this.__tmp_meth.name = tmp;
        
        args = raw.substr(sa+1,(ea=raw.indexOf(LEX.TOKEN.METH_ARG_E))-sa-1)
        argTypes = this.type(ut.trim(args));
        this.__tmp_meth.args = argTypes;
        this.__tmp_meth.argsNb = this.__tmp_meth.args.length;

        ret=raw.substr(ea+1);
        ret = this.type(ut.trim(ret))
        if(ret.length == 0)
            console.log("[!] this.method error : return type of '"+tmp+"("+args+")' cannot be parsed.")
            //exit(0);

        this.__tmp_meth.enclosingClass = this.obj;
        this.__tmp_meth.ret = ret[0];

        this.__tmp_meth._hashcode = this.__tmp_meth.hashCode();
    }

    instr(src, raw_src, src_line){
        let inst = null;//new Instruction();

        inst = OPCODE.parse(src,raw_src, src_line);

        if(inst != null){
            //console.log(inst);
        }
        //inst.operands = inst.opcode.parse(src);
        //console.log('"'+src[0]+'"',inst.opcode);
        // todo
        return inst;
    }

    field(src_arr, src_line){
        let f=new CLASS.Field(), type=null, tmp=null;

        // parse modifiers
        f.modifiers = this.modifier(src_arr);
        //console.log(f.modifiers);

        // clean src with identified modifier
        for(let i=0; i<f.modifiers._match; i++) src_arr.shift();
        // parse name and type
        tmp=src_arr[0].split(":");
        
        f.name=tmp[0];

        type=this.type(tmp[1]);
        if(type.length>0) f.type=type[0];

        //console.log(type.type[0]._hashcode);

        f.enclosingClass = this.obj;
        f._hashcode = f.hashCode();//Checker.makeFieldHashcode(f.modifiers,this.obj,f.name,f.type);
        
        f.signature();
        f.oline = src_line;

        LOG.DEBUG("[parser::field] Hashcode : "+f._hashcode);

        src_arr.shift();

        // parse value if available
        if(src_arr.length>0){
            // TODO : parse value
            f.value = src_arr.pop();
        }
        return f;
    }


    method(src, raw_src, src_line){
        if(this.state != SML_METH) return null;
        
        let sml=src, hdl=null, catchStmt=null, tmp=null;


        switch(ut.trim(sml[0])){
            case LEX.STRUCT.METHOD_BEG:
                LOG.DEBUG("---------------------------------------------\n[parser::method] Start ");    
                this.__tmp_meth = new CLASS.Method();
                sml.shift();

                this.methodHeader(sml,src_line);  
            
                this.__tmp_meth.__$in_annot = false;
                this.__tmp_block = new CLASS.BasicBlock();
                this.__instr_ctr = 0;
                break;
            case LEX.STRUCT.LOCALS:
                this.__tmp_meth.locals = parseInt(sml[1],10);
                break;
            case LEX.STRUCT.PARAMS:
                // this.__tmp_meth.params = parseInt(sml[1],10);
                this.__tmp_meth.params.push(
                    OPCODE.parseParam(sml[1],raw_src)
                );
                
                break;
            case LEX.STRUCT.REG:
                this.__tmp_meth.registers = parseInt(sml[1],10);
                break;
            case ".prologue":
                break;
            case LEX.STRUCT.LINE:
                
                // .line is just a metadata associated to an instruction
                /*if(this.__tmp_block != null && this.__tmp_block.stack.length > 0){

                    this.__tmp_meth.appendBlock(this.__tmp_block, this.__appendBlock_callback);
                    this.__tmp_block = new CLASS.BasicBlock();
                }*/
                //  && this.__tmp_block.line != null
                this.__tmp_block.line = parseInt(sml[1],10);
                this.currentLine = parseInt(sml[1],10);

                // source line number
                this.__tmp_block.srcln = parseInt(sml[1],10);
                
                break;
            case LEX.STRUCT.PSWITCH:
                
                if(sml[1] != undefined){
                    this.__tmp_block.setupPackedSwitchStatement(parseInt(sml[1],16));
                }

                break;
            case LEX.STRUCT.SSWITCH:
                
                this.__tmp_block.setupSparseSwitchStatement();
                
                break;
            case LEX.STRUCT.ARRAY:
                this.__tmp_block.setDataWidth(parseInt(sml[1],10));
                
                break;
            case LEX.STRUCT.END:

                if(sml[1]!=undefined && sml[1]==LEX.STRUCT.METHOD_NAME){
                    //hdl = this.__tmp_meth._hashcode;
                    this.state=SML_MAIN;   
                    this.__tmp_meth.appendBlock(this.__tmp_block, this.__appendBlock_callback);   
                    
                    this.obj.methods[ this.__tmp_meth.signature()] = this.__tmp_meth;
                    this.obj._methCount++;
                    LOG.DEBUG("[parser::method] End\n---------------------------------------------");
                }else if(sml[1]!=undefined && sml[1]==LEX.STRUCT.ANNOTATION_NAME){
                    this.__tmp_meth.__$in_annot = false;
                }/*
                else if(sml[1]!=undefined && sml[1]==LEX.STRUCT.PSWITCH_NAME){
                    // nothing to do
                    //console.log("End of packed switch");
                }xs
                else if(sml[1]!=undefined && sml[1]==LEX.STRUCT.ARRAY_NAME){

                }*/
                break;
            case LEX.STRUCT.ANNOT_BEG:
                // ignore
                this.__tmp_meth.__$in_annot = true;
                break;
            default: 
                if(this.__tmp_meth.__$in_annot){
                    // ignore
                    break;
                }

                if(sml[0].indexOf(':cond_')>-1){
                    if(this.__tmp_block instanceof CLASS.DataBlock || this.__tmp_block.stack.length>0){
                        this.__tmp_meth.appendBlock(this.__tmp_block, this.__appendBlock_callback);
                        this.__tmp_block = new CLASS.BasicBlock();
                    }

                    //this.__tmp_block.tag = sml[0];
                    this.__tmp_block.setAsConditionalBlock(sml[0].split('_')[1]);

                }else if(sml[0].indexOf(':goto_')>-1){
                    if(this.__tmp_block instanceof CLASS.DataBlock || this.__tmp_block.stack.length>0){
                        this.__tmp_meth.appendBlock(this.__tmp_block, this.__appendBlock_callback);
                        this.__tmp_block = new CLASS.BasicBlock();
                    }
                    //this.__tmp_block.tag = sml[0];
                    this.__tmp_block.setAsGotoBlock(sml[0].split('_')[1]);

                }
                else if(sml[0].indexOf(':try_start')>-1){
                    if(this.__tmp_block instanceof CLASS.DataBlock || this.__tmp_block.stack.length>0){
                        this.__tmp_meth.appendBlock(this.__tmp_block, this.__appendBlock_callback);
                        this.__tmp_block = new CLASS.BasicBlock();
                    }
                    //   this.__tmp_block.tag = sml[0];
                    this.__tmp_block.setAsTryBlock(sml[0]);
                    
                }
                else if(sml[0].indexOf(':try_end')>-1){
                    this.__tmp_block.setTryEndName(sml[0]);

                    if(this.__tmp_block != null){
                        this.__tmp_meth.appendBlock(this.__tmp_block, this.__appendBlock_callback);
                        this.__tmp_block = new CLASS.BasicBlock();
                    }
                }
                // >-1
                else if(sml[0].indexOf(LEX.LABEL.PSWITCH_DATA)==0 || sml[0].indexOf(LEX.LABEL.SSWITCH_DATA)==0){

                    if(this.__tmp_block != null){
                        this.__tmp_meth.appendBlock(this.__tmp_block, this.__appendBlock_callback);
                        this.__tmp_block = new CLASS.BasicBlock();
                    }
                    
                    this.__tmp_block.setAsSwitchStatement(sml[0]);
                }
                // >-1
                else if(sml[0].indexOf(LEX.LABEL.PSWITCH)==0){
    
                    if(this.__tmp_block.isSwitchStatement()){
                            this.__tmp_block.switch.appendCase(sml[0]);
                    }else{  
                        this.__tmp_block.setAsSwitchCase(sml[0]);
                    }   

                }
                // >-1 
                else if(sml[0].indexOf(LEX.LABEL.SSWITCH)==0){

                    if(this.__tmp_block != null
                        && (this.__tmp_block instanceof CLASS.DataBlock 
                            || this.__tmp_block.stack.length > 0)){
                        this.__tmp_meth.appendBlock(this.__tmp_block, this.__appendBlock_callback);
                        this.__tmp_block = new CLASS.BasicBlock();
                    }
                        
                    this.__tmp_block.setAsSwitchCase(sml[0]);
                }
                else if(sml.length > 2 && sml[2].indexOf(LEX.LABEL.SSWITCH)>-1 && sml[0].indexOf("p")==-1){
                    if(this.__tmp_block.isSwitchStatement()){
                     //   console.log(sml);
                        this.__tmp_block.switch.appendCase(sml[0],sml[2]);
                    } 

                }
                else if(sml[0].indexOf(LEX.LABEL.ARRAY)>-1){
                    // check if tmp block not empty (data or bb)
                    if(this.__tmp_block != null){
                        this.__tmp_meth.appendBlock(this.__tmp_block, this.__appendBlock_callback);
                    }
                    this.__tmp_block = new CLASS.DataBlock();
                    this.__tmp_block.name = sml[0];
                }
                else if(sml[0].indexOf(LEX.STRUCT.CATCH_ALL)>-1){
                    catchStmt = new CLASS.CatchStatement();
                    catchStmt.setTryStart(sml[1].substr(1,sml[1].length));
                    catchStmt.setTryEnd(sml[3].substr(0,sml[3].length-1));
                    catchStmt.setTarget(sml[4]);


                    tmp = this.__tmp_meth.getBasicBlocks();
                    tmp[tmp.length-1].addCatchStatement(catchStmt);

                    //this.__tmp_block.addCatchStatement(catchStmt);
                }
                else if(sml[0].indexOf(LEX.STRUCT.CATCH)>-1){
                    catchStmt = new CLASS.CatchStatement();
                    catchStmt.setException(this.type(sml[1])[0]);
                    catchStmt.setTryStart(sml[2].substr(1,sml[2].length));
                    catchStmt.setTryEnd(sml[4].substr(0,sml[4].length-1));
                    catchStmt.setTarget(sml[5]);

                    //console.log(sml);
                    tmp = this.__tmp_meth.getBasicBlocks();
                    tmp[tmp.length-1].addCatchStatement(catchStmt);
                    /*if(this.__tmp_block != null){
                        this.__tmp_meth.appendBlock(this.__tmp_block, this.__appendBlock_callback);
                        this.__tmp_block = new CLASS.BasicBlock();
                    }

                    this.__tmp_block.addCatchStatement(catchStmt);*/
                }
                else if(sml[0].indexOf(LEX.LABEL.CATCH)>-1){
                    if(this.__tmp_block != null 
                        && ( this.__tmp_block instanceof CLASS.DataBlock 
                            || this.__tmp_block.stack.length > 0 )){
                        this.__tmp_meth.appendBlock(this.__tmp_block, this.__appendBlock_callback);              
                        this.__tmp_block = new CLASS.BasicBlock();
                    }

                    this.__tmp_block.setCatchCond(raw_src);
                }
                else if(sml[0].indexOf(':catchall')>-1){

                    if(this.__tmp_block != null 
                        && ( this.__tmp_block instanceof CLASS.DataBlock 
                            || this.__tmp_block.stack.length > 0 )){
                        this.__tmp_meth.appendBlock(this.__tmp_block, this.__appendBlock_callback);              
                        this.__tmp_block = new CLASS.BasicBlock();
                    }
                    this.__tmp_block.setAsCatchBlock(sml[0]);

                }
                else if(this.__tmp_block instanceof CLASS.DataBlock){

                    hdl = CONST.RE.ARRAY_VALUE.exec(sml[0]);
                    if(hdl ==null) break; 
                    this.__tmp_block.pushData("0x"+hdl[2], (hdl[1] != undefined), (hdl[3]=='s'));
                }
                else{

                    if(this.__tmp_block instanceof CLASS.DataBlock) console.log("Error : DataBlock instead of BasicBlock",this.__tmp_meth);
                    if(this.__tmp_block == null) console.log("Error : tmpBlock is null",this.__tmp_meth);
                    hdl = this.instr(sml,raw_src,src_line);


                    if(hdl !== null){

                        this.__instr_ctr++;
                        hdl.offset = this.__instr_ctr;
                        hdl.oline = this.__instr_ctr;
                        if(this.currentLine != null){
                            hdl.iline = this.currentLine;
                            this.currentLine = null;
                        }
                        this.__tmp_block.stack.push(hdl);

                        if(hdl.opcode.type == CONST.INSTR_TYPE.IF || hdl.opcode.type == CONST.INSTR_TYPE.GOTO ) {

                            
                            if(this.__tmp_block instanceof CLASS.DataBlock || this.__tmp_block.stack.length>0){
                                this.__tmp_meth.appendBlock(this.__tmp_block, this.__appendBlock_callback);
                                this.__tmp_block = new CLASS.BasicBlock();
                            }

                        }
                    }


                    // add end of basic block on If / Goto
/*                    else if(this.isJumpInstruction(sml)){
                        if(this.__tmp_block instanceof CLASS.DataBlock || this.__tmp_block.stack.length>0){
                            this.__tmp_meth.appendBlock(this.__tmp_block, this.__appendBlock_callback);
                            this.__tmp_block = new CLASS.BasicBlock();
                        }

                        if(this.__tmp_block == null) console.log("Error : tmpBlock is null",this.__tmp_meth);
                        hdl = this.instr(sml,raw_src,src_line);

                        if(hdl !== null){
                            this.__instr_ctr++;
                            hdl.offset = this.__instr_ctr;
                            hdl.oline = this.__instr_ctr;
                            this.__tmp_block.stack.push(hdl);
                        }
                        //this.__tmp_block.tag = sml[0];
                        //this.__tmp_block.setAsGotoBlock(sml[0].split('_')[1]);
                    }*/
                    
                }



                break;
        }

        return true;
    }

    annotation(src){
        if(this.state != SML_ANNO) return null;

        let sml=[], hdl=null;

        sml=(src instanceof String)? src.split(LEX.TOKEN.SPACE) : src ;
        
        // search lexeme
        switch(ut.trim(sml[0])){
            case LEX.STRUCT.END:
                if(sml[1]!=undefined && sml[1]=="annotation"){
                    this.state=SML_MAIN;    
                }
                break;
        }
        //console.log("[!] this.annotation not implemented");
    }

    parse(src){
        let ls=src.split("\n"), ln=null, sml=null, obj=null;
    
        //console.log(ls);
        for(let l=0; l<ls.length; l++){
            ln=ut.trim(ls[l]);
            if(ln.length==0){
                continue;
            }
            sml=ln.split(LEX.TOKEN.SPACE);
            switch(sml[0]){
                case LEX.STRUCT.CLASS:
                    sml.shift();
                    this.class(sml);
                    break;
                case LEX.STRUCT.IMPLEMENTS:
                    sml.shift();
                    this.obj.implements.push(this.fqcn(sml[0]));
                    break;
                case LEX.STRUCT.SUPER:
                    sml.shift();
                    this.obj.extends = this.fqcn(sml[0]);
                    break;
                case LEX.STRUCT.SRC: 
                    this.obj.source = this.fspath(sml[1]);
                    break;
                case LEX.STRUCT.FIELD:
                    sml.shift();
                    obj=this.field(sml,l);
                    // use an internal name which combine visibility and field name
                    //this.obj.fields[obj._hashcode] = obj;
                    this.obj.fields[obj.signature()] = obj;
                    this.obj._fieldCount++;
                    
                    break;
                case LEX.STRUCT.METHOD_BEG:
                    this.state = SML_METH;
                    this.method(sml,ln,l);
                    break;
                case LEX.STRUCT.ANNOT_BEG:
                    if(this.state != SML_METH){
                        this.state = SML_ANNO;
                        this.annotation(sml);
                    }
                    break;
                default:
                    switch(this.state){
                        case SML_METH:
                            this.method(sml,ln,l);
                            break;
                        case SML_PSWITCH:
                            this.pswitch(sml,ln,l);
                            break;
                        case SML_ANNO:
                            this.annotation(sml);
                            break;
                    }
                    break;
            }
        }
        //console.log(this.obj);
        //this.obj.dump();
        return this.obj;
    }

    parseStream( pFilePath, pEncoding, pCallback){

        let _self = this, rs=null, stream=null;
        _self.obj = null;
        _self.objReady = false;

        stream =_fs_.createReadStream(pFilePath)
        .pipe(_es_.split())
        .pipe(_es_.mapSync(function(line){
    
                // pause the readstream
                stream.pause();
        
                
                // process line here and call s.resume() when rdy
                // function below was for logging memory usage
                console.log(line);
        
                // resume the readstream, possibly from a callback
                stream.resume();
            })
            .on('error', function(err){
                console.log('Error while reading file.', err);
            })
            .on('end', function(){
                console.log('Read entire file.')
            })
        );

        //stream = _fs_.createReadStream(pFilePath,{ encoding: 'utf8' });

        //stream = LineStream(stream);

        /*
        stream.on('resume', function(){
            console.log('resume');
        });*/

     
        
/*
        stream.on('pause', function(){
            console.log('paused');
        });

        stream.on('close', function(){
            console.log("close", _self.obj)
            pCallback(_self.obj);
        });

        stream.on('data', function(pLine){
            let ln=null, sml=null, obj=null;

            console.log(pLine);

            ln=ut.trim(pLine);
            if(ln.length==0){
                return;
            }

            sml=ln.split(LEX.TOKEN.SPACE);
            switch(sml[0]){
                case LEX.STRUCT.CLASS:
                    sml.shift();
                    _self.class(sml);
                    break;
                case LEX.STRUCT.IMPLEMENTS:
                    sml.shift();
                    _self.obj.implements.push(_self.fqcn(sml[0]));
                    break;
                case LEX.STRUCT.SUPER:
                    sml.shift();
                    _self.obj.extends = _self.fqcn(sml[0]);
                    break;
                case LEX.STRUCT.SRC: 
                    _self.obj.source = _self.fspath(sml[1]);
                    break;
                case LEX.STRUCT.FIELD:
                    sml.shift();
                    obj=_self.field(sml,l);
                    // use an internal name which combine visibility and field name
                    //this.obj.fields[obj._hashcode] = obj;
                    _self.obj.fields[obj.signature()] = obj;
                    _self.obj._fieldCount++;
                    
                    break;
                case LEX.STRUCT.METHOD_BEG:
                    _self.state = SML_METH;
                    _self.method(sml,ln,l);
                    break;
                case LEX.STRUCT.ANNOT_BEG:
                    if(_self.state != SML_METH){
                        _self.state = SML_ANNO;
                        _self.annotation(sml);
                    }
                    break;
                default:
                    switch(_self.state){
                        case SML_METH:
                            _self.method(sml,ln,l);
                            break;
                        case SML_PSWITCH:
                            _self.pswitch(sml,ln,l);
                            break;
                        case SML_ANNO:
                            _self.annotation(sml);
                            break;
                    }
                    break;
            }
        });*/
    }
}


module.exports = SmaliParser;