#!/usr/bin/env python """ Various formatting classes for Ada code """ import sys import re from collections import namedtuple, defaultdict TD = namedtuple('TD', ['ada', 'is_enum', 'is_gobject', 'is_list', 'property']) def Enum(ada, property=None): base = ada[ada.rfind(".") + 1:] or ada if property is None: return TD(ada, True, False, False, "Gtk.Enums.Property_%s" % base) else: return TD(ada, True, False, False, property) def GObject(ada): return TD(ada, False, True, False, "Glib.Properties.Property_Object") def Proxy(ada, property=None): if property is None: return TD(ada, False, False, False, "Glib.Properties.Property_Boxed") else: return TD(ada, False, False, False, property) def List(ada): return TD(ada, False, False, True, "Glib.Properties.Property_Object") class AdaNaming(object): def __init__(self): self.cname_to_adaname = { # Maps c methods to Ada subprograms. # All methods that are generated automatically will be added # as they are processed. "gtk_widget_get_direction": "Gtk.Widget.Get_Direction", "gtk_widget_add_events": "Gtk.Widget.Add_Event", "gtk_widget_set_size_request": "Gtk.Widget.Set_Size_Request", "gtk_window_set_default_icon": "Gtk.Window.Set_Default_Icon", "gtk_widget_set_has_window": "Gtk.Widget.Set_Has_Window", "gtk_show_uri": "gtk_show_uri()", "gtk_widget_show": "Gtk.Widget.Show", "gtk_icon_factory_add_default": "Gtk.Icon_Factory.Add_Default", "gtk_icon_factory_add": "Gtk.Icon_Factory.Add", "gdk_pixbuf_new_from_data": "Gdk.Pixbuf.Gdk_New_From_Data", "gdk_pixbuf_new_from_file": "Gdk.Pixbuf.Gdk_New_From_File", "gdk_pixbuf_new_from_xpm_data": "Gdk.Pixbuf.Gdk_New_From_Xpm_Data", "gdk_pixbuf_animation_new_from_file": "Gdk.Pixbuf.Gdk_New_From_File", "gdk_pixbuf_new": "Gdk.Pixbuf.Gdk_New", "gdk_pixbuf_new_subpixbuf": "Gdk.Pixbuf.Gdk_New_Subpixbuf", "gtk_accel_map_add_entry": "Gtk.Accel_Map.Add_Entry", "gtk_accel_map_change_entry": "Gtk.Accel_Map.Change_Entry", # ??? Doesn't exist "gtk_activatable_get_action": "Gtk.Activatable.Get_Action", # Will be bound later "gtk_action_group_add_action_with_accel": "Gtk.Action_Group.Add_Action_With_Accel", "gtk_tool_item_set_expand": "Gtk.Tool_Item.Set_Expand", "gtk_builder_add_from_file": "Gtk.Builder.Add_From_File", "gtk_builder_add_from_string": "Gtk.Builder.Add_From_String", } self.girname_to_ctype = { # Maps GIR's "name" to a "c:type". This isn't needed for the # classes themselves, since this is automatically read from the # GIR file. # Mostly used for properties. The values must correspond to # entries in self.type_exceptions. "GdkPixbuf.Pixbuf": "GdkPixbuf", "Pango.EllipsizeMode": "PangoEllipsizeMode", "Pango.WrapMode": "PangoWrapMode", "Pango.AttrList": "PangoAttrList", "Gio.Icon": "GIcon*", "IconSet": "GtkIconSet*", "Gdk.Pixmap": "GdkPixmap*", "Gdk.Image": "GdkImage*", "GdkPixbuf.PixbufAnimation": "GdkPixbufAnimation*", "Gdk.Bitmap": "GdkBitmap*", "GObject.Object": "GObject*", "GObject.Closure": "GClosure*", } self.exceptions = { # Naming exceptions. In particular maps Ada keywords. "Entry": "GEntry", "Type": "The_Type", "Range": "GRange", "Delay": "The_Delay", "Select": "Gtk_Select", "End": "The_End", "Return": "Do_Return", "Function": "Func", "Digits": "Number_Of_Digits", "Reverse": "Gtk_Reverse", } self.type_exceptions = { # Maps C types to type descriptions. # All standard widgets will be added automatically. Only special # namings are needed here "gboolean": Enum("Boolean", "Glib.Properties.Property_Boolean"), "gdouble": Proxy("Gdouble", "Glib.Properties.Property_Double"), "gint": Proxy("Gint", "Glib.Properties.Property_Int"), "guint": Proxy("Guint", "Glib.Properties.Property_Uint"), "gfloat": Proxy("Gfloat", "Glib.Properties.Property_Float"), "PangoAttrList": Proxy("Pango.Attributes.Pango_Attr_List", ""), "PangoEllipsizeMode":Enum("Pango.Layout.Pango_Ellipsize_Mode", ""), "PangoWrapMode": Enum("Pango.Layout.Pango_Wrap_Mode", ""), "PangoLayout": GObject("Pango.Layout.Pango_Layout"), "GdkEvent*": Proxy("Gdk.Event.Gdk_Event", ""), "GObject*": GObject("Glib.Object.GObject"), "GClosure*": Proxy("System.Address", ""), "GValue": Proxy("Glib.Values.GValue", ""), # Specific to this binding generator (referenced from binding.xml) "WidgetSList": List("Gtk.Widget.Widget_SList.GSList"), "WidgetList": List("Gtk.Widget.Widget_List.GList"), "ObjectList": List("Glib.Object.Object_Simple_List.GList"), "ObjectSList": List("Glib.Object.Object_List.GSList"), "StringList": List("Gtk.Enums.String_List.Glist"), "MessagesList": List("Gtk.Status_Bar.Messages_List.GSlist"), "gpointer": Proxy("System.Address", ""), "GDestroyNotify": Proxy("Glib.G_Destroy_Notify_Address"), "GIcon*": Proxy("Glib.G_Icon.G_Icon"), "GtkPositionType": Enum("Gtk.Enums.Gtk_Position_Type"), "GtkReliefStyle": Enum("Gtk.Enums.Gtk_Relief_Style"), "GtkShadowType": Enum("Gtk.Enums.Gtk_Shadow_Type"), "GtkArrowType": Enum("Gtk.Enums.Gtk_Arrow_Type"), "GtkPackType": Enum("Gtk.Enums.Gtk_Pack_Type"), "GtkJustification": Enum("Gtk.Enums.Gtk_Justification"), "GtkScrollType": Enum("Gtk.Enums.Gtk_Scroll_Type"), "GtkSelectionMode": Enum("Gtk.Enums.Gtk_Selection_Mode"), "GtkSensitivityType": Enum("Gtk.Enums.Gtk_Sensitivity_Type"), "GtkUpdateType": Enum("Gtk.Enums.Gtk_Update_Type"), "GtkButtonBoxStyle": Enum("Gtk.Enums.Gtk_Button_Box_Style"), "GtkCurveType": Enum("Gtk.Enums.Gtk_Curve_Type"), "GtkMetricType": Enum("Gtk.Enums.Gtk_Metric_Type", "Gtk.Enums.Property_Metric_Type"), "GtkAttachOptions": Enum("Gtk.Enums.Gtk_Attach_Options"), "GtkOrientation": Enum("Gtk.Enums.Gtk_Orientation"), # interfaces "GtkCellEditable": GObject("Gtk.Cell_Editable.Gtk_Cell_Editable"), "GtkCellLayout": GObject("Gtk.Cell_Layout.Gtk_Cell_Layout"), "GtkFileChooser": GObject("Gtk.File_Chooser.Gtk_File_Chooser"), "GtkRecentChooser": GObject("Gtk.Recent_Chooser.Gtk_Recent_Chooser"), "GtkTreeSortable": GObject("Gtk.Tree_Sortable.Gtk_Tree_Sortable"), "GtkAboutDialog": GObject("Gtk.About_Dialog.Gtk_About_Dialog"), "GtkAccelGroup": GObject("Gtk.Accel_Group.Gtk_Accel_Group"), "GtkAspectFrame": GObject("Gtk.Aspect_Frame.Gtk_Aspect_Frame"), "GtkButtonBox": GObject("Gtk.Button_Box.Gtk_Button_Box"), "GtkCellRenderer": GObject("Gtk.Cell_Renderer.Gtk_Cell_Renderer"), "GtkCheckButton": GObject("Gtk.Check_Button.Gtk_Check_Button"), "GtkComboBox": GObject("Gtk.Combo_Box.Gtk_Combo_Box"), "GtkDrawingArea": GObject("Gtk.Drawing_Area.Gtk_Drawing_Area"), "GtkEntry": GObject("Gtk.GEntry.Gtk_Entry"), "GtkEntryCompletion": GObject("Gtk.Entry_Completion.Gtk_Entry_Completion"), "GtkEventBox": GObject("Gtk.Event_Box.Gtk_Event_Box"), "GtkFileFilter": GObject("Gtk.File_Filter.Gtk_File_Filter"), "GtkHButtonBox": GObject("Gtk.Hbutton_Box.Gtk_Hbutton_Box"), "GtkMenuItem": GObject("Gtk.Menu_Item.Gtk_Menu_Item"), "GtkRadioAction": GObject("Gtk.Radio_Action.Gtk_Radio_Action"), "GtkRadioButton": GObject("Gtk.Radio_Button.Gtk_Radio_Button"), "GtkRange": GObject("Gtk.GRange.Gtk_Range"), "GtkScaleButton": GObject("Gtk.Scale_Button.Gtk_Scale_Button"), "GtkSizeGroup": GObject("Gtk.Size_Group.Gtk_Size_Group"), "GtkSeparatorMenuItem": GObject("Gtk.Separator_Menu_Item.Gtk_Separator_Menu_Item"), "GtkSeparatorToolItem": GObject("Gtk.Separator_Tool_Item.Gtk_Separator_Tool_Item"), "GtkStatusbar": GObject("Gtk.Status_Bar.Gtk_Status_Bar"), "GtkToggleAction": GObject("Gtk.Toggle_Action.Gtk_Toggle_Action"), "GtkToggleButton": GObject("Gtk.Toggle_Button.Gtk_Toggle_Button"), "GtkToolItem": GObject("Gtk.Tool_Item.Gtk_Tool_Item"), "GtkTreeIter*": Proxy("Gtk.Tree_Model.Gtk_Tree_Iter"), "GtkTreeModel": GObject("Gtk.Tree_Model.Gtk_Tree_Model"), "GtkTreeViewRowSeparatorFunc": Proxy("Gtk.Tree_View.Gtk_Tree_View_Row_Separator_Func"), "GtkVButtonBox": GObject("Gtk.Vbutton_Box.Gtk_Vbutton_Box"), "GtkVolumeButton": GObject("Gtk.Volume_Button.Gtk_Volume_Button"), "GtkBorder": Proxy("Gtk.Style.Gtk_Border"), "GtkIconSet*": Proxy("Gtk.Icon_Factory.Gtk_Icon_Set"), "GdkWindow": Proxy("Gdk.Window.Gdk_Window"), "GdkPixmap*": Proxy("Gdk.Pixmap.Gdk_Pixmap"), "GdkBitmap*": Proxy("Gdk.Bitmap.Gdk_Bitmap"), "GdkImage*": Proxy("Gdk.Image.Gdk_Image"), "GdkPixbuf": GObject("Gdk.Pixbuf.Gdk_Pixbuf"), "GdkPixbufAnimation*": Proxy("Gdk.Pixbuf.Gdk_Pixbuf_Animation"), "GdkRectangle*": Proxy("Gdk.Rectangle.Gdk_Rectangle"), "GdkModifierType": Proxy("Gdk.Types.Gdk_Modifier_Type"), "GdkKeyType": Proxy("Gdk.Types.Gdk_Key_Type"), } def add_type_exception(self, cname, type, override=False): """Declares a new type exception, unless there already existed one for that cname. """ assert(isinstance(type, TD)) if override or cname not in self.type_exceptions: self.type_exceptions[cname] = type def add_cmethod(self, cname, adaname): """Register the mapping from c method's name to Ada subprogram. This is used to replace C function names in the documentation with their Ada equivalent""" self.cname_to_adaname[cname] = adaname def add_girname(self, girname, ctype): """Maps a GIR's "name" attribute to its matching C type. This is used to resolve such names in the documentation and in properties types. """ self.girname_to_ctype[girname] = ctype def ctype_from_girname(self, girname): """Return the C type corresponding to a GIR name""" return self.girname_to_ctype.get(girname, "Gtk%s" % girname) def adamethod_name(self, cname): """Return the ada name corresponding to the C method's name""" try: return self.cname_to_adaname[cname] except KeyError: if cname.startswith("gtk_"): print "Function quoted in doc has no Ada binding: %s" % cname self.cname_to_adaname[cname] = cname # Display warning once only return cname def case(self, name): """Return the proper casing to use for 'name', taking keywords into account. This is for packages. """ name = name.replace("-", "_").title() if name.endswith("_"): name = name[:-1] return self.exceptions.get(name, name) def full_type_from_girname(self, girname): """Return the type description from a GIR name""" return self.type_exceptions.get( girname, # First try GIR name as is in the table (gint, ...) self.type_exceptions.get( self.ctype_from_girname(girname), # Else the C type Proxy(girname))) # Else return the GIR name itself def full_type(self, cname): """Return the type description from a C type name""" return self.type_exceptions.get(cname, Proxy(cname)) naming = AdaNaming() def max_length(iter): """Return the length of the longuest element in iter""" longuest = 0 for f in iter: longuest = max(longuest, len(f)) return longuest def fill_text(text, prefix, length, firstLineLength=0): """Split TEXT on several lines (with a given max length and a prefix). """ line = "" result = [] maxLen = firstLineLength or length - len(prefix) text=text.replace("\n\n", "\n
") for w in text.split(): # for each word (this loses whitespaces) if w.startswith("
"): result.append(line) maxLen = length - len(prefix) line = w[4:] elif len(line) + len(w) + 1 > maxLen: result.append(line) maxLen = length - len(prefix) line = w elif w: line += " " + w if line != "": result.append(line) return ("\n" + prefix).join(result) def cleanup_doc(doc): """Replaces C features in the doc with appropriate Ada equivalents""" doc = doc.replace("%NULL", "null") doc = doc.replace("%TRUE", "True") doc = doc.replace("%FALSE", "False") doc = doc.replace("", "") doc = doc.replace("", "") doc = doc.replace("", "\nNote: ") doc = doc.replace("", "") subp = re.compile("([\S_]+)\(\)") doc = subp.sub(lambda x: naming.adamethod_name(x.group(1)), doc) types = re.compile("#([\w_]+)") doc = types.sub(lambda x: naming.full_type(x.group(1))[0], doc) params = re.compile("@([\w_]+)") doc = params.sub(lambda x: x.group(1).title(), doc) return doc def box(name, indent=" "): return indent + "-" * (len(name) + 6) + "\n" \ + indent + "-- " + name + " --\n" \ + indent + "-" * (len(name) + 6) def indent_code(code, indent=3): """Return code properly indented and split on several lines. These are heuristics only, not perfect. """ body = code.strip() if not body: return "" # Add newlines where needed, but preserve existing blank lines body = re.sub(";(?!\s*\n)", ";\n", body) body = re.sub("(? old_parent: indent += (parent_count - old_parent) * 3 elif not old_parent: result += " " * indent if parent_count > old_parent: indent += (parent_count - old_parent) * 3 else: if parent_count > old_parent: indent += (parent_count - old_parent) * 3 result += " " * indent if old_parent > parent_count: indent -= (old_parent - parent_count) * 3 result += l + eol_comment + "\n" if(l.endswith("then") and not l.endswith("and then")) \ or l.endswith("loop") \ or(l.endswith("else") and not l.endswith("or else"))\ or l.endswith("begin") \ or l.endswith("record") \ or l.endswith("is") \ or l.endswith("declare"): indent += 3 return result # The necessary setup to use a variable in a subprogram call. The returned # values map to the following Ada code: # declare # $(tmpvars) # A list of LocalVar # begin # $(precall) # Call ($(call), ...) # #(postcall) # end; # and are used in case temporary variables are needed. If not, only 'call' # will have a non-null value VariableCall = namedtuple('VariableCall', ['call', 'precall', 'postcall', 'tmpvars']) class CType(object): """Describes the types in the various cases where they can be used. A type applies either to an Ada subprogram written in Ada, or to an Ada subprogram implemented via a pragma Import. The latter case is abbreviated to a "c" subprogram below. For returned values, various pieces of information are needed: (adatype, ctype, converter, tmpvars=[]) They are used as: function ... (...) return adatype is function ... (...) return ctype; pragma Import (C, ..., "..."); Tmp : ctype; tmpvars; begin ...; -- pre-call code Tmp := ... (...); ...; -- post-call code return end; In the example above, "Tmp" is only created if there is some post-call code, otherwise we avoid the temporary variable. The variable "Tmp" is automatically added, and does not need to be specified manually in tmpvars. if converted contains the string "%(tmp)s", then we always use a temporary variable of type adatype. This is used for instance when the variable is initialized through a procedure call rather than a function call. function ... (...) return adatype is function ... (...) return ctype; pragma Import (C, ..., "...") Tmp_Result : adatype; tmpvars; begin ... -- pre-call code convert % {"var":..., "tmp":"Tmp_Result"}; -- proc call ... -- post-call code return Tmp_Result; end; The variable "Tmp_Result" is automatically added, and does not need to be specified manually in tmpvars. Converter will contain a single %s which will be replaced by the name of the temporary variable that holds the result of the call to the function. """ def _as_enumeration(self, td, cname, pkg): """Self is an enumeration, setup the conversion hooks `td' is an instance of TD""" name = td.ada if pkg and "." in name: pkg.add_with(name[0:name.rfind(".")]) self.param = name self.cparam = cname self.convert = "%s'Pos (%%(var)s)" % name self.postconvert = "%s'Val (%%(var)s)" % name self.returns = (self.param, self.cparam, self.postconvert, []) self.property = td.property def _as_gobject(self, td, pkg=None, userecord=True): """Self is a descendant of GObject, setup the conversion hooks `td' is an instance of TD. If `userecord' is True, the type is represented as an access to the record type, rather than use the access type itself. """ full = td.ada if pkg and "." in full: pkg.add_with(full[0:full.rfind(".")]) if userecord: self.param = "access %s_Record'Class" % full else: self.param = full self.cparam = "System.Address" self.convert = "Get_Object_Or_Null (GObject (%(var)s))" self.is_ptr = False self.property = td.property if full == "Glib.Object.GObject": convert = "Get_User_Data (%(var)s, Stub)" else: convert = "%s (Get_User_Data (%%(var)s, Stub))" % full self.returns = ( full, self.cparam, convert, [Local_Var( "Stub", AdaType("%s_Record" % full, pkg=pkg, in_spec=False))]) def _as_list(self, td, pkg): adatype = td.ada adapkg = adatype[:adatype.rfind(".")] # A list comes from an instantiation (pkg.instance.glist), so we need # to skip backward two "." if pkg: p = td.ada.rfind(".") if p != -1: p = td.ada[:p].rfind(".") if p != -1: pkg.add_with(td.ada[:p]) self.param = adatype self.cparam = "System.Address" self.convert = "%s.Get_Object (%%(var)s)" % adapkg self.is_ptr = False self.property = td.property self.returns = ( # Use %(tmp)s so forces the use of temporary var. adatype, self.cparam, "%s.Set_Object (%%(tmp)s, %%(var)s)" % adapkg, []) def __init__(self, name, cname, pkg, isArray=False, allow_access=True, empty_maps_to_null=False): """A type a described in a .gir file 'pkg' is an instance of Package, to which extra with clauses will be added if needed. 'isArray' should be true for an array of the simple type 'name'. 'allow_access' should be True if the parameter can be represented as 'access Type', rather than an explicit type, in the case of GObject descendants. If `empty_maps_to_null' is True, then an empty string maps to a NULL pointer in C, rather than an empty C string. For a GObject, this means the parameter should not be passed with mode="access", since the user is allowed to pass null. """ self.is_ptr = cname \ and cname[-1] == '*' \ and name != "utf8" # not a "char*" self.param = None # type as parameter self.returns = None # type as return type self.cparam = None # type for Ada subprograms binding to C self.convert = "%(var)s" # Convert from Ada parameter to C parameter # If it used %(tmp)s, we assume the converter sets # the value of the temporary variable itself. # Otherwise, it is used as " Tmp := " self.postconvert = "%(var)s" # Convert from C to Ada value # %(var)s is the value to convert self.cleanup = None # If set, a tmp variable is created to hold the # result of convert during the call, and is then # free by calling this cleanup. Use "%s" as the # name of the variable. self.isArray = isArray if cname == "gchar**" or name == "array_of_utf8": self.param = "GNAT.Strings.String_List" self.cparam = "Interfaces.C.Strings.chars_ptr_array" self.convert = "From_String_List (%(var)s)" self.cleanup = "GtkAda.Types.Free (%s)" self.returns = ( "GNAT.Strings.String_List", "chars_ptr_array_access", "To_String_List (%(var)s.all)", []) self.property = "" # Can't map those if pkg: pkg.add_with("GNAT.Strings") pkg.add_with("Gtkada.Types", specs=False) pkg.add_with("Interfaces.C.Strings", specs=False) pkg.add_with("Gtkada.Bindings", specs=False) elif name == "utf8": self.param = "UTF8_String" self.cparam = "Interfaces.C.Strings.chars_ptr" if empty_maps_to_null: self.convert = 'if %(var)s = "" then %(tmp)s := Interfaces.C.Strings.Null_Ptr; else %(tmp)s := New_String (%(var)s); end if;' else: self.convert = "New_String (%(var)s)" self.cleanup = "Free (%s);" if pkg: pkg.add_with("Interfaces.C.Strings", specs=False) self.returns = ( "UTF8_String", "Interfaces.C.Strings.chars_ptr", "Interfaces.C.Strings.Value (%(var)s)", []) self.property = "Glib.Properties.Property_String" else: basename = None if cname: # Check whether the C type, including trailing "*", maps # directly to an Ada type. full = naming.full_type(cname) self.is_ptr = False if full.ada[-1] == "*": # No, try without the trailing "*" full = naming.full_type(cname[0:-1]) if full.ada[-1] != "*": self.is_ptr = True # Yes, so we had a pointer else: basename = cname[0:-1] # Remove all "*" if basename[-1] == "*": basename = basename[0:-1] full = naming.full_type(basename) else: full = naming.full_type_from_girname(name) if full.is_enum: self._as_enumeration(full, "Integer", pkg) elif full.is_gobject: self._as_gobject( full, pkg, userecord=not empty_maps_to_null and cname and not cname.endswith("**") and allow_access) elif full.is_list: self._as_list(full, pkg) else: ada = full.ada if pkg and "." in ada: pkg.add_with(ada[0:ada.rfind(".")]) self.param = ada self.property = full.property if self.cparam is None: self.cparam = self.param if self.returns is None: self.returns = (self.param, self.cparam, "%(var)s", []) def as_property(self): """The type to use for the property""" return self.property def as_return(self, lang="ada"): """See CType documentation for a description of the returned tuple""" return self.returns def as_ada_param(self): """Converts self to a description for an Ada parameter to a subprogram. """ return self.param def as_c_param(self): """Returns the C type (as a parameter to a subprogram that imports a C function) """ return self.cparam def as_call(self, name, wrapper="%s", lang="ada", mode="in"): """'name' represents a parameter of type 'self'. 'wrapper' is used in the call itself, and %s is replaced by the name of the variable (or the temporary variable). Returns an instance of VariableCall. """ if lang == "ada": return VariableCall( call=wrapper % name, precall='', postcall='', tmpvars=[]) elif self.postconvert != "%(var)s" and mode != "in": # An "out" parameter for an enumeration requires a temporary # variable: Internal(Enum'Pos (Param)) is invalid tmp = "Tmp_%s" % name return VariableCall( call=wrapper % tmp, precall="", postcall='%s := %s;' % (name, self.postconvert % {"var":tmp}), tmpvars=[Local_Var(name=tmp, type=self.cparam)]) elif "%(tmp)" in self.convert: # The conversion sets the temporary variable itself tmp = "Tmp_%s" % name return VariableCall( call=wrapper % tmp, precall=self.convert % {"var":name, "tmp":tmp}, postcall=self.cleanup % tmp, tmpvars=[Local_Var(name=tmp, type=self.cparam)]) elif self.cleanup: tmp = "Tmp_%s" % name conv = self.convert % {"var":name} # Initialize the temporary variable with a default value, in case # it is an unconstrained type (a chars_ptr_array for instance) return VariableCall( call=wrapper % tmp, precall='', postcall=self.cleanup % tmp, tmpvars=[Local_Var( name=tmp, type=AdaType(self.cparam), default=conv)]) else: conv = self.convert % {"var":name} return VariableCall( call=wrapper % conv, precall='', postcall='', tmpvars=[]) def direct_cmap(self): """Whether the parameter can be passed as is to C""" return self.convert == "%(var)s" class AdaType(CType): """Similar to a CType, but created directly from Ada types""" def __init__(self, adatype, pkg=None, in_spec=True, ctype="", convert="%(var)s"): """The 'adatype' type is represented as 'ctype' for subprograms that import C functions. The parameters of that type are converted from Ada to C by using 'convert'. 'convert' must use '%s' once to indicate where the name of the parameter should go """ self.param = adatype self.cparam = ctype or adatype self.returns = (self.param, self.cparam, "%(var)s", []) self.convert = convert self.postconvert = "%(var)s" self.cleanup = None self.is_ptr = adatype.startswith("access ") if False and (adatype.lower().endswith(".glist") \ or adatype.lower().endswith(".gslist")): self._as_list(GObject(adatype), pkg=pkg) elif pkg and "." in adatype: pkg.add_with(adatype[0:adatype.rfind(".")], specs=in_spec) class Local_Var(object): __slots__ = ["name", "type", "default", "aliased"] def __init__(self, name, type, default="", aliased=False): if isinstance(type, CType): self.type = type else: self.type = AdaType(type) self.name = name self.default = default self.aliased = aliased def _type(self, lang): if isinstance(self.type, CType): if lang == "ada": return self.type.as_ada_param() elif lang == "c": return self.type.as_c_param() return self.type def spec(self, length=0, lang="ada"): """Format the declaration for the variable or parameter. 'length' is the minimum length that the name should occupy (for proper alignment when there are several variables. """ t = self._type(lang) aliased = "" if self.aliased: aliased = "aliased " if self.default: return "%-*s : %s%s := %s" % ( length, self.name, aliased, t, self.default) else: return "%-*s : %s%s" % (length, self.name, aliased, t) def as_call(self, lang="ada", mode="in"): """Pass 'self' as a parameter to an Ada subprogram call, implemented in the given language. Returns an instance of VariableCall """ wrapper = "%s" n = self.name if mode == "access_c": mode = "access" wrapper="%s'Access" if isinstance(self.type, CType): return self.type.as_call(n, lang=lang, mode=mode, wrapper=wrapper) else: return VariableCall(call=n, precall='', postcall='', tmpvars=[]) class Parameter(Local_Var): __slots__ = ["name", "type", "default", "aliased", "mode", "doc"] def __init__(self, name, type, default="", doc="", mode="in"): """A mode "access_c" indicates an "access" parameter for which calls will use a 'Access. """ super(Parameter, self).__init__(name, type, default) self.mode = mode self.doc = doc def _type(self, lang): t = super(Parameter, self)._type(lang) mode = self.mode if self.mode == "access_c": mode = "access" if mode != "in": return "%s %s" % (mode, t) return t def as_call(self, lang="ada"): return super(Parameter, self).as_call(lang, mode=self.mode) def direct_cmap(self): """Whether the parameter can be passed as is to C""" return self.type.direct_cmap() max_profile_length = 79 - len(" is") class Subprogram(object): """An Ada subprogram that we are generating""" def __init__(self, name, code="", plist=[], local_vars=[], returns=None, doc=[], showdoc=True): """Create a new subprogram. 'plist' is a list of Parameter. 'local_vars' is a list of Local_Var. 'doc' is a string or a list of paragraphs. 'code' can be the empty string, in which case no body is output. The code will be automatically pretty-printed, and the appropriate pragma Unreferenced are also added automatically. """ assert(returns is None or isinstance(returns, CType)) self.name = naming.case(name) self.plist = plist self.returns = returns self.local = local_vars self.showdoc = showdoc self._import = None self._nested = [] # nested subprograms self._deprecated = (False, "") # True if deprecated self._manual_body = None # Written by user explicitly self.lang = "ada" # Language for the types of parameters if code and code[-1] != ";": self.code = code + ";" else: self.code = code if isinstance(doc, list): self.doc = doc else: self.doc = [doc] def import_c(self, cname): """Declares that 'self' is implemented as a pragma Import. This returns 'self' so that it can be chained: s = Subprogram(...).import_c('...') """ self._import = 'pragma Import (C, %s, "%s");' % (self.name, cname) return self def set_param_lang(self, lang): """Set the language to use when printing the types of parameters. If "c", prints the C type corresponding to the "ada" types. """ self.lang = lang def mark_deprecated(self, msg): """Mark the subprogram as deprecated""" self._deprecated = (True, msg) def add_nested(self, *args): """Add some nested subprograms""" for subp in args: self._nested.append(subp) return self def set_body(self, body): self._manual_body = body def _profile(self, indent=" ", lang="ada", maxlen=max_profile_length): """Compute the profile for the subprogram""" returns = self.returns and self.returns.as_return(lang) if returns: prefix = indent + "function %s" % self.name if self.lang == "c": suffix = " return %s" % returns[1] else: suffix = " return %s" % returns[0] else: prefix = indent + "procedure %s" % self.name suffix = "" if self.plist: # First test: all parameters on same line plist = [p.spec(lang=lang) for p in self.plist] p = " (" + "; ".join(plist) + ")" # If too long, split on several lines if len(p) + len(prefix) + len(suffix) > maxlen: max = max_length([p.name for p in self.plist]) plist = [p.spec(max, lang=lang) for p in self.plist] p = "\n " + indent + "(" \ + (";\n " + indent).join(plist) + ")" else: p = "" # Should the "return" go on a separate line ? if p and len(p.splitlines()[-1]) + len(suffix) > maxlen: return prefix + p + "\n " + indent + suffix else: return prefix + p + suffix def spec(self, indent=" ", show_doc=True, maxlen=max_profile_length): """Return the spec of the subprogram""" if self.showdoc and show_doc: doc = [cleanup_doc(d) for d in self.doc] if self._deprecated[0]: doc += [cleanup_doc(self._deprecated[1])] doc += [cleanup_doc(p.doc) for p in self.plist] else: doc = [] result = self._profile(indent, lang=self.lang, maxlen=maxlen) + ";" if self._import: result += "\n" + indent + self._import if self._deprecated[0]: result += "\n" + indent + "pragma Obsolescent (%s);" % self.name for d in doc: if d: if d.startswith("%PRE%"): d = d[5:] lines = ["\n" + indent + "-- " + l for l in d.split("\n")] result += "".join(lines) else: result += "\n" + indent + "-- " result += fill_text(d, indent + "-- ", 79) return result def _find_unreferenced(self, local_vars="", indent=" "): """List the pragma Unreferenced statements that are needed for this subprogram. """ unreferenced = [] for p in self.plist: if not re.search( r'\b%s\b' % p.name, self.code + local_vars, re.IGNORECASE): unreferenced.append(p.name) if unreferenced: return indent + " pragma Unreferenced (%s);\n" % ( ", ".join(unreferenced)) else: return "" def _format_local_vars(self, indent=" "): """The list of local variable declarations""" if self.local: max = max_length([p.name for p in self.local]) result = [v.spec(max) for v in self.local] return indent + " " + (";\n " + indent).join(result) + ";\n" else: return "" def body(self, indent=" "): if self._manual_body: return self._manual_body if not self.code: return "" result = box(self.name) + "\n\n" profile = self._profile(lang=self.lang) result += profile if profile.find("\n") != -1: result += "\n is\n" else: result += " is\n" local = self._format_local_vars(indent=indent) result += self._find_unreferenced(local_vars=local, indent=indent) for s in self._nested: result += s.spec(indent=indent + " ") + "\n" result += s.body(indent=indent + " ") result += local result += indent + "begin\n" result += indent_code(self.code, indent=6) return result + indent + "end %s;\n" % self.name def call(self, in_pkg="", extra_postcall=""): """A call to 'self'. The parameters that are passed to self are assumed to have the same name as in self's declaration. When 'self' is implemented as a pragma Import, proper conversions are done. 'in_pkg' is used to fully qualify the name of the subprogram, to avoid ambiguities. This is optional. This can be either a string or an instance of Package. Returned value is a tuple: ("code", "variable_for_return", tmpvars=[]) where "code" is the code to execute for the call, including creation of temporary variables, and "variable_for_return" is either None, or the code to get the result of the subprogram. So a call is: declare tmp_vars; begin code; extra_postcall; return variable_for_return; -- Omitted for procedures end; """ if self._import: lang = "c" else: lang = "ada" tmpvars = [] precall = "" params = [] postcall = extra_postcall for arg in self.plist: c = arg.as_call(lang) # An instance of VariableCall params.append(c.call) tmpvars.extend(c.tmpvars) precall += c.precall postcall += c.postcall if params: call = "%s (%s)" % (self.name, ", ".join(params)) else: call = self.name if in_pkg: if isinstance(in_pkg, Package): call = "%s.%s" % (in_pkg.name, call) else: call = "%s.%s" % (in_pkg, call) returns = self.returns and self.returns.as_return(lang) if returns is not None: if lang == "c": tmpvars.extend(returns[3]) if "%(tmp)s" in returns[2]: # Result of Internal is used to create a temp. variable, # which is then returned. This variable has the same type # as the Ada type (not necessarily same as Internal) tmpvars.append(Local_Var("Tmp_Return", returns[0])) call = returns[2] % {"var":call, "tmp":"Tmp_Return"} return ("%s%s;%s" % (precall, call, postcall), "Tmp_Return", tmpvars) elif postcall: tmpvars.append(Local_Var("Tmp_Return", returns[1])) call = "Tmp_Return := %s" % call return ("%s%s;%s" % (precall, call, postcall), returns[2] % {"var":"Tmp_Return"}, tmpvars) else: # No need for a temporary variable return (precall, returns[2] % {"var":call}, tmpvars) else: if postcall: # We need to use a temporary variable, since there are # cleanups to perform. This will not work if the function # returns an unconstrained array though. tmpvars.append(Local_Var("Tmp_Return", returns[0])) call = "Tmp_Return := %s" % call return ("%s%s;%s" % (precall, call, postcall), "Tmp_Return", tmpvars) else: # No need for a temporary variable return ("%s" % precall, call, tmpvars) else: # A procedure return ("%s%s;%s" % (precall, call, postcall), None, tmpvars) class Section(object): """A group of types and subprograms in an Ada package. There is a single section with a given name in the package """ group_getters_and_setters = True # If true, a getter will be displayed with its corresponding setter. # Only one doc will be displayed for the two, and no separation line # will be output. def __init__(self, name): self.name = name self.comment = "" self.subprograms = [] # All subprograms self.spec_code = "" # hard-coded code self.body_code = "" # hard-coded code def add_comment(self, comment, fill=True): """If 'fill' is true, the comment is automatically split on several lines if needed. Otherwise, the comment is assumed to be already formatted properly, minus the leading -- """ if comment == "": self.comment += " --\n" elif fill: self.comment += " -- " + fill_text( cleanup_doc (comment), " -- ", 79) + "\n" else: self.comment += "\n".join( " -- %s" % cleanup_doc(p) for p in comment.splitlines()) + "\n" def add(self, *args): """Add one or more objects to the section (subprogram, code,...)""" for a in args: if isinstance(a, Subprogram): self.subprograms.append(a) elif isinstance(a, str): self.spec_code += a + "\n" def add_code(self, code, specs=True): if specs: self.spec_code += code + "\n" else: self.body_code += code + "\n" def _group_subprograms(self): """Returns a list of list of subprograms. In each nested list, the subprograms are grouped and a single documentation is output for the whole group. At the same time, this preserves the order of groups, so they appear in the order in which the first subprogram in the group appeared. """ if Section.group_getters_and_setters: result = [] tmp = dict() # group_name => [subprograms] gtk_new_index = 0; for s in self.subprograms: name = s.name.replace("Get_", "") \ .replace("Query_", "") \ .replace("Gtk_New", "") \ .replace("Initialize", "") \ .replace("Set_From_", "") \ .replace("Set_", "") if s.name == "Gtk_New": # Always create a new group for Gtk_New, since they all # have different parameters. But we still want to group # Gtk_New and Initialize. t = tmp["Gtk_New%d" % gtk_new_index] = [s] result.append(t) elif s.name == "Initialize": tmp["Gtk_New%d" % gtk_new_index].append(s) gtk_new_index += 1 elif name in tmp: tmp[name].append(s) # Also modified in result else: tmp[name] = [s] result.append(tmp[name]) return result else: return [[s] for s in self.subprograms] def spec(self): """Return the spec of the section""" result = [] if self.subprograms or self.spec_code: result.append("") # A separator with previous section if self.name: result.append(box(self.name)) if self.comment: result.append(self.comment) else: result.append("") if self.spec_code: result.append(indent_code(self.spec_code)) for group in self._group_subprograms(): for s in group: result.append(s.spec(show_doc=s == group[-1])) if s == group[-1]: result.append("") return "\n".join(result) def body(self): result = [] if self.body_code: result.append(indent_code(self.body_code)) self.subprograms.sort(lambda x, y: cmp(x.name, y.name)) for s in self.subprograms: b = s.body() if b: result.append(b) return "\n".join(result) class Package(object): copyright_header = "" # Can be overridden by applications to change the copyright header def __init__(self, name, doc=[]): """'doc' is a list of strings, where each string is a paragraph""" self.name = name self.doc = doc self.sections = [] # [Section] self.spec_withs = dict() # "pkg" -> use:Boolean self.body_withs = dict() # "pkg" -> use:Boolean self.private = [] # Private section def section(self, name): """Return an existing section (or create a new one) with the given name. """ for s in self.sections: if s.name == name: return s s = Section(name) self.sections.append(s) return s def add_with(self, pkg, specs=True, do_use=True): """Add a with+use clause for pkg, where pkg can also be a list. Automatic casing is performed. If specs is True, the withs are added to the specs of the package, otherwise to the body """ if pkg == "System": return # Not needed, already done in gtk.ads if type(pkg) == str: pkg = [pkg] for p in pkg: if p.lower() == self.name.lower(): continue # No dependence on self if specs: self.spec_withs[p] = do_use or self.spec_withs.get(p, False) self.body_withs.pop(p, None) # Remove same with in body elif p not in self.spec_withs: self.body_withs[p] = do_use or self.body_withs.get(p, False) def add_private(self, code): self.private.append(code) def _output_withs(self, withs): if withs: result = [] m = max_length(withs) for w in sorted(withs.keys()): if withs[w]: result.append( "with %-*s use %s;" % (m + 1, w + ";", w)) else: result.append( "with %-*s" % (m + 1, w + ";")) return "\n".join(result) + "\n\n" return "" def spec(self, out): """Returns the spec of the package, in the file `out`""" if Package.copyright_header: out.write(Package.copyright_header + "\n") if self.doc: for d in self.doc: if d.startswith("%PRE%"): d = d[5:] lines = ["\n-- " + l for l in d.split("\n")] out.write("".join(lines)) elif d: out.write("-- " + fill_text(d, "-- ", 79)) else: out.write("--") out.write("\n") out.write('\npragma Warnings (Off, "*is already use-visible*");\n') out.write(self._output_withs(self.spec_withs)) out.write("package %s is" % self.name) for s in self.sections: out.write(s.spec()) if self.private: out.write("\nprivate\n") out.write("\n".join(self.private)) out.write("\nend %s;" % self.name) def body(self, out): """Returns the body of the package""" body = "" for s in self.sections: b = s.body() if b: body += "\n" body += b if not body: return if Package.copyright_header: out.write(Package.copyright_header + "\n") out.write("pragma Style_Checks (Off);\n") out.write('pragma Warnings (Off, "*is already use-visible*");\n') out.write(self._output_withs(self.body_withs)) out.write("package body %s is\n" % self.name) out.write(body) out.write("\nend %s;" % self.name)