<commit_before>package ch.ninecode.cim.CIMTool

import scala.collection.mutable

case class JavaScript (parser: ModelParser, pkg: Package)
{
    def parses (): Iterable[(String, String)] =
    {
        val ret = mutable.Set[(String, String)]()

        for (cls <- parser.classes.filter (_._2.pkg == pkg))
            ret.add (("cim:" + cls._2.name, "parse_" + cls._2.name.replace ("-", "_")))

        ret
    }

    def asText (): String =
    {
        val requires = mutable.Set[Package]()
        val provides = mutable.Set[String]()
        val enumerations = mutable.Set[String]()

        // order the classes based on their superclass to avoid "ReferenceError: can't access lexical declaration `blah' before initialization"
        val classes: List[(Int, Class)] = parser.classes.filter (_._2.pkg == pkg).toList
        case class Joe (name: String, superclass: String, superclass_package: String, objectID: Int, cls: Class)
        val bunch: List[Joe] = classes.map (
            cls ⇒
            {
                val (superclass, superclass_package) =
                    if (null != cls._2.sup)
                    {
                        val sup_pkg = cls._2.sup.pkg
                        val superclass_package = if (sup_pkg != pkg) { requires.add (sup_pkg); sup_pkg.name} else ""
                        val superclass = cls._2.sup.name
                        (superclass, superclass_package)
                    }
                    else
                        ("Element", "base")

                Joe (cls._2.name.replace ("-", "_"), superclass, superclass_package, cls._1, cls._2)
            })
        // define a Directed Acyclic Graph
        case class DAG (parent: Joe, var children: List[DAG])
        val graph = DAG (Joe ("Element", null, null, 0, null), List())
        def get (name: String)(dag: DAG): DAG =
        {
            if (dag.parent.name == name)
                dag
            else
                dag.children.find (get (name)(_) != null) match { case Some (d) ⇒ d case None ⇒ null }
        }
        def add (joes: List[Joe]): Unit =
        {
            if (joes.nonEmpty)
            {
                val head = joes.head
                val rest = joes.tail
                val name = if (head.superclass_package == "") head.superclass else "Element" // outside our package might as well be Element
                val dag = get (name) (graph)
                if (null != dag)
                {
                    dag.children = dag.children :+ DAG (head, List())
                    add (rest)
                }
                else
                    // add everyone else first
                    add (rest :+ head)
            }
        }
        // make the graph
        add (bunch)
        // read out the graph breadth first (or at least every superclass is ahead of it's subclasses)
        def read (dags: List[DAG]): List[Joe] =
        {
            dags.map (_.parent) ++: dags.flatMap (x ⇒ read (x.children))
        }

        val bunch2 = read (graph.children)
        val classes2 = bunch2.map (x ⇒ x.cls)

        def valid_attribute_name (name: String): String = name.replace (" ", "_").replace (".", "_").replace (",", "_")
        def valid_role_name (name: String): String = if (name == "") "unknown" else name.replace (" ", "_").replace (".", "_").replace (",", "_").replace ("""/""", """\/""")

        val p = new StringBuilder ()

        // do the enumerations
        for (cls <- classes2)
        {
            val s = new StringBuilder ()

            // get name
            val name = cls.name.replace ("-", "_")
            val attributes = parser.attributesFor (cls)

            if (cls.stereotype == "enumeration")
            {
                enumerations.add (name)

                // add javaDoc
                if (null != cls.note)
                    s.append (JavaDoc (cls.note, 8).asText)

                // https://stijndewitt.com/2014/01/26/enums-in-javascript/
                //            var SizeEnum = {
                //                SMALL: 1,
                //                MEDIUM: 2,
                //                LARGE: 3,
                //                properties: {
                //                    1: {name: "small", value: 1, code: "S"},
                //                    2: {name: "medium", value: 2, code: "M"},
                //                    3: {name: "large", value: 3, code: "L"}
                //                }
                //            };

                // output enumeration declaration
                s.append ("        var %s =\n        {\n".format (name))
                s.append (attributes.map (attribute ⇒ """            %s: "%s"""".format (valid_attribute_name (attribute.name), attribute.name)).mkString (",\n"))
                s.append ("\n        };\n        Object.freeze (%s);\n\n".format (name))
            }

            p.append (s)
        }

        // do the regular classes
        for (cls <- classes2)
        {
            val s = new StringBuilder ()

            // get name
            val name = cls.name.replace ("-", "_")
            val attributes = parser.attributesFor (cls)
            val roles = parser.rolesFor (cls)

            if (cls.stereotype != "enumeration")
            {
                provides.add (name)

                // add javaDoc
                if (null != cls.note)
                    s.append (JavaDoc (cls.note, 8).asText)

                // determine superclass and it's package (note package has a dot suffix)
                val (superclass_package, superclass) =
                    if (null != cls.sup)
                    {
                        val sup_pkg = cls.sup.pkg
                        val superclass_package = if (sup_pkg != pkg) { requires.add (sup_pkg); sup_pkg.name + "." } else ""
                        val superclass = cls.sup.name
                        (superclass_package, superclass)
                    }
                    else
                        ("base.", "Element")

                // output class declaration
                s.append ("        class %s extends %s%s".format (name, superclass_package, superclass))
                // output constructor and store function
                s.append ("""
                    |        {
                    |            constructor (template, cim_data)
                    |            {
                    |                super (template, cim_data);
                    |                var bucket = cim_data.%s;
                    |                if (null == bucket)
                    |                   cim_data.%s = bucket = {};
                    |                bucket[template.id] = template;
                    |            }
                    |
                    |            remove (obj, cim_data)
                    |            {
                    |               super.remove (obj, cim_data);
                    |               delete cim_data.%s[obj.id];
                    |            }
                    |""".stripMargin.format (name, name, name))


                // output parse function
                s.append ("""
                    |            parse (context, sub)
                    |            {
                    |                var obj;
                    |
                    |                obj = %s%s.prototype.parse.call (this, context, sub);
                    |                obj.cls = "%s";
                    |""".stripMargin.format (superclass_package, superclass, cls.name))
                for (attribute <- attributes)
                {
                    val n = attribute.name.replace ("""/""", """\/""")
                    val fn = attribute.typ match
                    {
                        case "Boolean" => "base.to_boolean"
                        case "DateTime" => "base.to_datetime"
                        case "Float" => "base.to_float"
                        case _ => "base.to_string"
                    }
                    if (enumerations.contains (attribute.typ))
                        s.append (
                            """                base.parse_attribute (/<cim:%s.%s\s+rdf:resource\s*?=\s*?("|')([\s\S]*?)\1\s*?\/>/g, obj, "%s", sub, context);
                              |""".stripMargin.format (cls.name, n, attribute.name))
                    else
                        s.append (
                            """                base.parse_element (/<cim:%s.%s>([\s\S]*?)<\/cim:%s.%s>/g, obj, "%s", %s, sub, context);
                            |""".stripMargin.format (cls.name, n, cls.name, n, attribute.name, fn))
                }
                for (role <- roles)
                {
                    val n = valid_role_name (role.name)
                    s.append (
                        """                base.%s (/<cim:%s.%s\s+rdf:resource\s*?=\s*?("|')([\s\S]*?)\1\s*?\/>/g, obj, "%s", sub, context);
                        |""".stripMargin.format (if (role.upper == 1) "parse_attribute" else "parse_attributes", cls.name, n, role.name))
                }
                // special handling for mRID in IdentifiedObject
                if (cls.name == "IdentifiedObject")
                    s.append ("""
                    |
                    |                if (null == obj.mRID)
                    |                    obj.mRID = obj.id;
                    |                if ((null != obj.mRID) && (obj.id != obj.mRID))
                    |                {
                    |                    if ("undefined" != typeof (console))
                    |                        console.log ("***Warning*** rdf:ID != mRID [" + obj.id + " != " + obj.mRID + "]");
                    |                    else
                    |                        print ("***Warning*** rdf:ID != mRID [" + obj.id + " != " + obj.mRID + "]");
                    |                    obj.id = obj.mRID;
                    |                }
                    |""".stripMargin)
                s.append (
                    """                var bucket = context.parsed.%s;
                    |                if (null == bucket)
                    |                   context.parsed.%s = bucket = {};
                    |                bucket[obj.id] = obj;
                    |
                    |                return (obj);
                    |            }
                    |
                    |""".stripMargin.format (name, name))

                // output the export function
                s.append (
                    """            export (obj, full)
                      |            {
                      |""".stripMargin.format (name))
                if (null != cls.sup)
                    s.append ("""                var fields = %s%s.prototype.export.call (this, obj, false);""".stripMargin.format (superclass_package, superclass))
                else
                    s.append ("""                var fields = [];""")
                s.append ("\n\n")
                for (attribute <- attributes)
                {
                    val n = attribute.name.replace ("""/""", """\/""")
                    val fn = attribute.typ match
                    {
                        case "Boolean" => "base.from_boolean"
                        case "DateTime" => "base.from_datetime"
                        case "Float" => "base.from_float"
                        case _ => "base.from_string"
                    }
                    if ((cls.name != "IdentifiedObject") || (n != "mRID"))
                        s.append (
                            """                base.export_element (obj, "%s", "%s", "%s",  %s, fields);
                            |""".stripMargin.format (cls.name, attribute.name, n, fn))
                }
                for (role <- roles)
                {
                    val n = valid_role_name (role.name)
                    s.append (<commit_msg>JavaScript export enumerations<commit_after>package ch.ninecode.cim.CIMTool

import scala.collection.mutable

case class JavaScript (parser: ModelParser, pkg: Package)
{
    def parses (): Iterable[(String, String)] =
    {
        val ret = mutable.Set[(String, String)]()

        for (cls <- parser.classes.filter (_._2.pkg == pkg))
            ret.add (("cim:" + cls._2.name, "parse_" + cls._2.name.replace ("-", "_")))

        ret
    }

    def asText (): String =
    {
        val requires = mutable.Set[Package]()
        val provides = mutable.Set[String]()
        val enumerations = mutable.Set[String]()

        // order the classes based on their superclass to avoid "ReferenceError: can't access lexical declaration `blah' before initialization"
        val classes: List[(Int, Class)] = parser.classes.filter (_._2.pkg == pkg).toList
        case class Joe (name: String, superclass: String, superclass_package: String, objectID: Int, cls: Class)
        val bunch: List[Joe] = classes.map (
            cls ⇒
            {
                val (superclass, superclass_package) =
                    if (null != cls._2.sup)
                    {
                        val sup_pkg = cls._2.sup.pkg
                        val superclass_package = if (sup_pkg != pkg) { requires.add (sup_pkg); sup_pkg.name} else ""
                        val superclass = cls._2.sup.name
                        (superclass, superclass_package)
                    }
                    else
                        ("Element", "base")

                Joe (cls._2.name.replace ("-", "_"), superclass, superclass_package, cls._1, cls._2)
            })
        // define a Directed Acyclic Graph
        case class DAG (parent: Joe, var children: List[DAG])
        val graph = DAG (Joe ("Element", null, null, 0, null), List())
        def get (name: String)(dag: DAG): DAG =
        {
            if (dag.parent.name == name)
                dag
            else
                dag.children.find (get (name)(_) != null) match { case Some (d) ⇒ d case None ⇒ null }
        }
        def add (joes: List[Joe]): Unit =
        {
            if (joes.nonEmpty)
            {
                val head = joes.head
                val rest = joes.tail
                val name = if (head.superclass_package == "") head.superclass else "Element" // outside our package might as well be Element
                val dag = get (name) (graph)
                if (null != dag)
                {
                    dag.children = dag.children :+ DAG (head, List())
                    add (rest)
                }
                else
                    // add everyone else first
                    add (rest :+ head)
            }
        }
        // make the graph
        add (bunch)
        // read out the graph breadth first (or at least every superclass is ahead of it's subclasses)
        def read (dags: List[DAG]): List[Joe] =
        {
            dags.map (_.parent) ++: dags.flatMap (x ⇒ read (x.children))
        }

        val bunch2 = read (graph.children)
        val classes2 = bunch2.map (x ⇒ x.cls)

        def valid_attribute_name (name: String): String = name.replace (" ", "_").replace (".", "_").replace (",", "_")
        def valid_role_name (name: String): String = if (name == "") "unknown" else name.replace (" ", "_").replace (".", "_").replace (",", "_").replace ("""/""", """\/""")

        val p = new StringBuilder ()

        // do the enumerations
        for (cls <- classes2)
        {
            val s = new StringBuilder ()

            // get name
            val name = cls.name.replace ("-", "_")
            val attributes = parser.attributesFor (cls)

            if (cls.stereotype == "enumeration")
            {
                enumerations.add (name)

                // add javaDoc
                if (null != cls.note)
                    s.append (JavaDoc (cls.note, 8).asText)

                // https://stijndewitt.com/2014/01/26/enums-in-javascript/
                //            var SizeEnum = {
                //                SMALL: 1,
                //                MEDIUM: 2,
                //                LARGE: 3,
                //                properties: {
                //                    1: {name: "small", value: 1, code: "S"},
                //                    2: {name: "medium", value: 2, code: "M"},
                //                    3: {name: "large", value: 3, code: "L"}
                //                }
                //            };

                // output enumeration declaration
                s.append ("        var %s =\n        {\n".format (name))
                s.append (attributes.map (attribute ⇒ """            %s: "%s"""".format (valid_attribute_name (attribute.name), attribute.name)).mkString (",\n"))
                s.append ("\n        };\n        Object.freeze (%s);\n\n".format (name))
            }

            p.append (s)
        }

        // do the regular classes
        for (cls <- classes2)
        {
            val s = new StringBuilder ()

            // get name
            val name = cls.name.replace ("-", "_")
            val attributes = parser.attributesFor (cls)
            val roles = parser.rolesFor (cls)

            if (cls.stereotype != "enumeration")
            {
                provides.add (name)

                // add javaDoc
                if (null != cls.note)
                    s.append (JavaDoc (cls.note, 8).asText)

                // determine superclass and it's package (note package has a dot suffix)
                val (superclass_package, superclass) =
                    if (null != cls.sup)
                    {
                        val sup_pkg = cls.sup.pkg
                        val superclass_package = if (sup_pkg != pkg) { requires.add (sup_pkg); sup_pkg.name + "." } else ""
                        val superclass = cls.sup.name
                        (superclass_package, superclass)
                    }
                    else
                        ("base.", "Element")

                // output class declaration
                s.append ("        class %s extends %s%s".format (name, superclass_package, superclass))
                // output constructor and store function
                s.append ("""
                    |        {
                    |            constructor (template, cim_data)
                    |            {
                    |                super (template, cim_data);
                    |                var bucket = cim_data.%s;
                    |                if (null == bucket)
                    |                   cim_data.%s = bucket = {};
                    |                bucket[template.id] = template;
                    |            }
                    |
                    |            remove (obj, cim_data)
                    |            {
                    |               super.remove (obj, cim_data);
                    |               delete cim_data.%s[obj.id];
                    |            }
                    |""".stripMargin.format (name, name, name))


                // output parse function
                s.append ("""
                    |            parse (context, sub)
                    |            {
                    |                var obj;
                    |
                    |                obj = %s%s.prototype.parse.call (this, context, sub);
                    |                obj.cls = "%s";
                    |""".stripMargin.format (superclass_package, superclass, cls.name))
                for (attribute <- attributes)
                {
                    val n = attribute.name.replace ("""/""", """\/""")
                    val fn = attribute.typ match
                    {
                        case "Boolean" => "base.to_boolean"
                        case "DateTime" => "base.to_datetime"
                        case "Float" => "base.to_float"
                        case _ => "base.to_string"
                    }
                    if (enumerations.contains (attribute.typ))
                        s.append (
                            """                base.parse_attribute (/<cim:%s.%s\s+rdf:resource\s*?=\s*?("|')([\s\S]*?)\1\s*?\/>/g, obj, "%s", sub, context);
                              |""".stripMargin.format (cls.name, n, attribute.name))
                    else
                        s.append (
                            """                base.parse_element (/<cim:%s.%s>([\s\S]*?)<\/cim:%s.%s>/g, obj, "%s", %s, sub, context);
                            |""".stripMargin.format (cls.name, n, cls.name, n, attribute.name, fn))
                }
                for (role <- roles)
                {
                    val n = valid_role_name (role.name)
                    s.append (
                        """                base.%s (/<cim:%s.%s\s+rdf:resource\s*?=\s*?("|')([\s\S]*?)\1\s*?\/>/g, obj, "%s", sub, context);
                        |""".stripMargin.format (if (role.upper == 1) "parse_attribute" else "parse_attributes", cls.name, n, role.name))
                }
                // special handling for mRID in IdentifiedObject
                if (cls.name == "IdentifiedObject")
                    s.append ("""
                    |
                    |                if (null == obj.mRID)
                    |                    obj.mRID = obj.id;
                    |                if ((null != obj.mRID) && (obj.id != obj.mRID))
                    |                {
                    |                    if ("undefined" != typeof (console))
                    |                        console.log ("***Warning*** rdf:ID != mRID [" + obj.id + " != " + obj.mRID + "]");
                    |                    else
                    |                        print ("***Warning*** rdf:ID != mRID [" + obj.id + " != " + obj.mRID + "]");
                    |                    obj.id = obj.mRID;
                    |                }
                    |""".stripMargin)
                s.append (
                    """                var bucket = context.parsed.%s;
                    |                if (null == bucket)
                    |                   context.parsed.%s = bucket = {};
                    |                bucket[obj.id] = obj;
                    |
                    |                return (obj);
                    |            }
                    |
                    |""".stripMargin.format (name, name))

                // output the export function
                s.append (
                    """            export (obj, full)
                      |            {
                      |""".stripMargin.format (name))
                if (null != cls.sup)
                    s.append ("""                var fields = %s%s.prototype.export.call (this, obj, false);""".stripMargin.format (superclass_package, superclass))
                else
                    s.append ("""                var fields = [];""")
                s.append ("\n\n")
                for (attribute <- attributes)
                {
                    val n = attribute.name.replace ("""/""", """\/""")
                    val fn = attribute.typ match
                    {
                        case "Boolean" => "base.from_boolean"
                        case "DateTime" => "base.from_datetime"
                        case "Float" => "base.from_float"
                        case _ => "base.from_string"
                    }
                    if ((cls.name != "IdentifiedObject") || (n != "mRID"))
                        if (enumerations.contains (attribute.typ))
                            s.append (
                                """                base.export_attribute (obj, "%s", "%s", "%s", fields);
                                |""".stripMargin.format (cls.name, attribute.name, n))
                        else
                            s.append (
                                """                base.export_element (obj, "%s", "%s", "%s",  %s, fields);
                                |""".stripMargin.format (cls.name, attribute.name, n, fn))
                }
                for (role <- roles)
                {
                    val n = valid_role_name (role.name)
                    s.append (
{'fraction_non_alphanumeric': 0.12926118882553225, 'fraction_numerical': 0.003117692907248636, 'mean_word_length': 0.9635924285138945, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 27, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 88, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '375578', 'n_tokens_mistral': 6527, 'n_tokens_neox': 6035, 'n_words': 1459}