/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: VerilogParser.java
 * Written by Jonathan Gainsley, Sun Microsystems.
 *
 * Copyright (c) 2004, Oracle and/or its affiliates. All rights reserved.
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.sun.electric.tool.simulation.test;

import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.StreamTokenizer;
import java.io.IOException;

/**
 * A brain-dead, extremely limited verilog parser.  In fact, it really
 * doesn't do anything except return a module name with it's port defintions.
 *
 * <P>Again, this is not meant to be a fully featured parser.  It's error handling
 * is also not very useful.  It is only meant to extract module names and their ports.
 */
public class VerilogParser {

    /**
     * A Verilog Module. Contains a list of Ports. Does not
     * contain a list of sub-modules, always statements, or anything else!
     */
    public static final class Module {
        public final String name;
        private final List inports;
        private final List outports;
        private final List inoutports;

        public Module(String name) {
            this.name = name;
            inports = new ArrayList();
            outports = new ArrayList();
            inoutports = new ArrayList();
        }

        void addPort(Port port) {
            if (port.type == Port.INPUT) inports.add(port);
            else if (port.type == Port.OUTPUT) outports.add(port);
            else if (port.type == Port.INOUT) inoutports.add(port);
            else { System.out.println("Unknown port type ("+port.type+") for port "+port.name); }
        }
        List getInports() { return inports; }
        List getOutports() { return outports; }
        List getInoutports() { return inoutports; }
        List getPorts() {
            List ports = new ArrayList();
            ports.addAll(inports);
            ports.addAll(outports);
            ports.addAll(inoutports);
            return ports;
        }

        StringBuffer print() {
            StringBuffer buf = new StringBuffer();
            buf.append("module "+name);
            buf.append(printPorts(null));
            return buf;
        }

        StringBuffer printPorts(VerilogModel.AllAliasedNames aliased) {
            StringBuffer buf = new StringBuffer();
            int lineno = 0;
            buf.append("(");
            for (Iterator it = getPorts().iterator(); it.hasNext(); ) {
                Port p = (Port)it.next();
                String alias = p.name;
                if (aliased != null) alias = aliased.getAliasFor(alias);
                buf.append("."+p.name+"("+alias+")");
                if (it.hasNext()) buf.append(", ");
                if ((int)(buf.length() / 60) > lineno) {
                    buf.append("\n\t");
                    lineno++;
                }
            }
            buf.append(");\n");
            return buf;
        }
    }

    /**
     * A Verilog port.  May be bussed.
     */
    public static final class Port {
        public final String name;
        public final int type;
        public final int start;
        public final int end;
        public final boolean containsSpecialChars;
        public static final int INPUT = 0;
        public static final int OUTPUT = 1;
        public static final int INOUT = 2;

        private static final Pattern bussed = Pattern.compile("(\\w+)\\[(\\d+):(\\d+)\\]");
        private static final Pattern bussedSig = Pattern.compile("(\\w+)\\[(\\d+)\\]");

        public Port(String name, int type, int start, int end, boolean containsSpecialChars) {
            this.name = name;
            this.type = type;
            this.start = start;
            this.end = end;
            this.containsSpecialChars = containsSpecialChars;
            //System.out.println("Created port "+name+", "+type+", "+start+", "+end);
        }

        public Port(String name, int type) {
            this(name, type, 1, 1, false);
        }

        public boolean contains(String name) {
            if (name.equals(this.name)) return true;
            Matcher m = bussedSig.matcher(name);
            if (start != end && m.matches()) {
                String n = m.group(1);
                int idx = Integer.parseInt(m.group(2));
                if (n.equals(this.name) && (idx >= start && idx <= end))
                    return true;
            }
            return false;
        }
    }

    private List modules;
    private BufferedReader fin;

    public VerilogParser() {
        modules = new ArrayList();
        fin = null;
    }

    public List getModules() { return modules; }


    /**
     * Parse a verilog file. Very limited functionality at this time.
     * Only builds list of modules and their ports
     * @param verilogFile
     * @return list of modules parsed
     */
    public boolean parse(String verilogFile) {
        // try to open the file
        try {
            FileReader reader = new FileReader(verilogFile);
            fin = new BufferedReader(reader);
        } catch (java.io.IOException e) {
            System.out.println("Failed to open file for read: "+verilogFile+": "+e.getMessage());
        }
        if (fin == null) return false;

        StreamTokenizer st = new StreamTokenizer(fin);
        st.slashSlashComments(true);
        st.slashStarComments(true);
        st.wordChars('_', '_');

        try {
            int token = st.nextToken();
            while (token != StreamTokenizer.TT_EOF) {
                switch(token) {
                    case StreamTokenizer.TT_WORD: {
                        // Parse Modules
                        if (st.sval.equals("module")) {
                            if (!parseModule(st)) return false;
                        } else if (st.sval.equals("primitive")) {
                            if (!parsePrimitive(st)) return false;
                        } else {
                            error(st, "expected module/primitive definition");
                        }
                        break;
                    }
                    case '`': {
                        int t = st.nextToken();
                        if (st.sval.equals("include")) {
                            st.nextToken();     // grab file name
                        } else if (st.sval.equals("define")) {
                            st.nextToken();
                            st.nextToken();
                        } else {
                            error(st, "expected valid defintion after `");
                        }
                        break;
                    }
                    default: {
                        error(st, "expected word");
                    }
                }
                token = st.nextToken();
            }
        } catch (java.io.IOException e) {
            System.out.println(e.getMessage());
            return false;
        }
        return true;
    }

    private boolean parseModule(StreamTokenizer st) {
        try {
            int token = st.nextToken();
            if (token != StreamTokenizer.TT_WORD) {
                error(st, "expected module name");
                return false;
            }
            Module module = new Module(st.sval);

            expect(st, '(');
            while (token != ')') {
                // gobble ports in module definition
                token = st.nextToken();
            }
            expect(st, ';');
            // parse ports
            boolean processPorts = true;
            while (processPorts) {
                token = st.nextToken();
                switch(token) {
                    case StreamTokenizer.TT_WORD: {
                        if (st.sval.equalsIgnoreCase("input")) {
                            parsePorts(st, Port.INPUT, module);
                        } else if (st.sval.equalsIgnoreCase("output")) {
                            parsePorts(st, Port.OUTPUT, module);
                        } else if (st.sval.equalsIgnoreCase("inout")) {
                            parsePorts(st, Port.INOUT, module);
                        } else {
                            processPorts = false;
                            st.pushBack();
                        }
                        break;
                    }
                }
            }
            // eat rest of module
            while(true) {
                token = st.nextToken();
                if (token == StreamTokenizer.TT_EOF) break;
                if (token == StreamTokenizer.TT_WORD && st.sval.equals("endmodule")) break;
            }
            modules.add(module);

        } catch (java.io.IOException e) {
            System.out.println(e.getMessage());
            return false;
        }
        return true;
    }

    private boolean parsePrimitive(StreamTokenizer st) {
        try {
            while (true) {
                int token = st.nextToken();
                if (token == StreamTokenizer.TT_EOF) break;
                if (token == StreamTokenizer.TT_WORD && st.sval.equals("endprimitive")) break;
            }
        } catch (IOException e) {
            System.out.println(e.getMessage());
            return false;
        }
        return true;
    }

    private void parsePorts(StreamTokenizer st, int type, Module module) {
        try {
            int token;
            while ( (token = st.nextToken()) != ';') {
                if (token == ',') continue;
                String name = st.sval;
                int start = 1, end = 1;
                boolean quoted = false;
                if (token == '[') {
                    // parse bus
                    st.nextToken();
                    start = (int)st.nval;
                    expect(st, ':');
                    st.nextToken();
                    end = (int)st.nval;
                    expect(st, ']');
                    token = st.nextToken();
                    name = st.sval;
                }
                if (token == '\\') {
                    // parse special string delimited by \ at start and single whitespace at end
                    StringBuffer buf = new StringBuffer("\\");
                    st.ordinaryChar(' ');
                    while ( (token = st.nextToken()) != ' ') {
                        if (token == StreamTokenizer.TT_WORD) buf.append(st.sval);
                        else if (token == StreamTokenizer.TT_NUMBER) buf.append((int)st.nval);
                        else buf.append((char)token);
                    }
                    st.whitespaceChars(' ', ' ');
                    buf.append(" ");
                    name = buf.toString();
                    quoted = true;
                }
                module.addPort(new Port(name, type, start, end, quoted));
            }

        } catch (java.io.IOException e) {
            System.out.println(e.getMessage());
        }
    }

    private boolean expect(StreamTokenizer st, int nextToken) {
        try {
            int token = st.nextToken();
            if (token != nextToken) {
                error(st, "expected "+(char)nextToken);
                return false;
            }
        } catch (java.io.IOException e) {
            System.out.println(e.getMessage());
            return false;
        }
        return true;
    }

    private void error(StreamTokenizer st, String msg) {
        System.out.print("Parse error on token ");
        switch(st.ttype) {
            case StreamTokenizer.TT_WORD: System.out.print("\""+st.sval+"\"");
            case StreamTokenizer.TT_NUMBER: System.out.print("\""+st.nval+"\"");
            case StreamTokenizer.TT_EOF: System.out.print("EOF");
            case StreamTokenizer.TT_EOL: System.out.print("EOL");
            default: System.out.print("\""+(char)st.ttype+"\"");
        }
        System.out.println(", line "+st.lineno()+": "+msg);
    }



    /** unit test */
    public static void main(String args[]) {
        VerilogParser vp = new VerilogParser();
        if (!vp.parse(VerilogModel.getExampleVerilogChipFile())) {
            System.out.println("Parsing failed.");
            return;
        }
        System.out.println("Parsing succeeded: modules are:");
        for (Iterator it = vp.getModules().iterator(); it.hasNext(); ) {
            Module m = (Module)it.next();
            System.out.println(m.print());
        }
    }
}
