mirror of
https://gitlab.gnome.org/jwestman/blueprint-compiler.git
synced 2025-05-04 15:59:08 -04:00
reorganization: Finish moving parsing and AST
This commit is contained in:
parent
8f9de81e24
commit
ee5f32622f
27 changed files with 737 additions and 719 deletions
|
@ -1,6 +1,8 @@
|
|||
""" Contains all the syntax beyond basic objects, properties, signal, and
|
||||
templates. """
|
||||
|
||||
from .gobject_object import Object
|
||||
from .gobject_property import Property
|
||||
from .gobject_signal import Signal
|
||||
from .gtk_a11y import A11y
|
||||
from .gtk_combo_box_text import Items
|
||||
|
@ -10,12 +12,36 @@ from .gtk_menu import menu
|
|||
from .gtk_size_group import Widgets
|
||||
from .gtk_string_list import Strings
|
||||
from .gtk_styles import Styles
|
||||
from .gtkbuilder_child import Child
|
||||
from .gtkbuilder_template import Template
|
||||
from .ui import UI
|
||||
from .values import IdentValue, TranslatedStringValue, FlagsValue, LiteralValue
|
||||
|
||||
from .common import *
|
||||
|
||||
OBJECT_HOOKS.children = [menu]
|
||||
OBJECT_HOOKS.children = [
|
||||
menu,
|
||||
Object,
|
||||
]
|
||||
|
||||
OBJECT_CONTENT_HOOKS.children = [
|
||||
Signal, A11y, Styles, Layout, mime_types, patterns, suffixes, Widgets, Items,
|
||||
Signal,
|
||||
Property,
|
||||
A11y,
|
||||
Styles,
|
||||
Layout,
|
||||
mime_types,
|
||||
patterns,
|
||||
suffixes,
|
||||
Widgets,
|
||||
Items,
|
||||
Strings,
|
||||
Child,
|
||||
]
|
||||
|
||||
VALUE_HOOKS.children = [
|
||||
TranslatedStringValue,
|
||||
FlagsValue,
|
||||
IdentValue,
|
||||
LiteralValue,
|
||||
]
|
||||
|
|
45
blueprintcompiler/language/attributes.py
Normal file
45
blueprintcompiler/language/attributes.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
# attributes.py
|
||||
#
|
||||
# Copyright 2022 James Westman <james@jwestman.net>
|
||||
#
|
||||
# This file is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This file 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
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
|
||||
from .values import Value, TranslatedStringValue
|
||||
from .common import *
|
||||
|
||||
|
||||
class BaseAttribute(AstNode):
|
||||
""" A helper class for attribute syntax of the form `name: literal_value;`"""
|
||||
|
||||
tag_name: str = ""
|
||||
attr_name: str = "name"
|
||||
|
||||
def emit_xml(self, xml: XmlEmitter):
|
||||
value = self.children[Value][0]
|
||||
attrs = { self.attr_name: self.tokens["name"] }
|
||||
|
||||
if isinstance(value, TranslatedStringValue):
|
||||
attrs = { **attrs, **value.attrs }
|
||||
|
||||
xml.start_tag(self.tag_name, **attrs)
|
||||
value.emit_xml(xml)
|
||||
xml.end_tag()
|
||||
|
||||
|
||||
class BaseTypedAttribute(BaseAttribute):
|
||||
""" A BaseAttribute whose parent has a value_type property that can assist
|
||||
in validation. """
|
|
@ -18,11 +18,11 @@
|
|||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
|
||||
from ..ast import BaseTypedAttribute, Value, Template
|
||||
from .. import gir
|
||||
from ..ast_utils import AstNode, validate, docs
|
||||
from ..completions_utils import *
|
||||
from ..gir import StringType, BoolType, IntType, FloatType, GirType
|
||||
from ..lsp_utils import Completion, CompletionItemKind
|
||||
from ..lsp_utils import Completion, CompletionItemKind, SemanticToken, SemanticTokenType
|
||||
from ..parse_tree import *
|
||||
from ..parser_utils import *
|
||||
from ..xml_emitter import XmlEmitter
|
||||
|
@ -30,3 +30,4 @@ from ..xml_emitter import XmlEmitter
|
|||
|
||||
OBJECT_HOOKS = AnyOf()
|
||||
OBJECT_CONTENT_HOOKS = AnyOf()
|
||||
VALUE_HOOKS = AnyOf()
|
||||
|
|
97
blueprintcompiler/language/gobject_object.py
Normal file
97
blueprintcompiler/language/gobject_object.py
Normal file
|
@ -0,0 +1,97 @@
|
|||
# gobject_object.py
|
||||
#
|
||||
# Copyright 2022 James Westman <james@jwestman.net>
|
||||
#
|
||||
# This file is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This file 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
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
|
||||
from .common import *
|
||||
|
||||
|
||||
class ObjectContent(AstNode):
|
||||
grammar = ["{", Until(OBJECT_CONTENT_HOOKS, "}")]
|
||||
|
||||
@property
|
||||
def gir_class(self):
|
||||
return self.parent.gir_class
|
||||
|
||||
# @validate()
|
||||
# def only_one_style_class(self):
|
||||
# if len(self.children[Style]) > 1:
|
||||
# raise CompileError(
|
||||
# f"Only one style directive allowed per object, but this object contains {len(self.children[Style])}",
|
||||
# start=self.children[Style][1].group.start,
|
||||
# )
|
||||
|
||||
def emit_xml(self, xml: XmlEmitter):
|
||||
for x in self.children:
|
||||
x.emit_xml(xml)
|
||||
|
||||
class Object(AstNode):
|
||||
grammar = Sequence(
|
||||
class_name,
|
||||
Optional(UseIdent("id")),
|
||||
ObjectContent,
|
||||
)
|
||||
|
||||
@validate("namespace")
|
||||
def gir_ns_exists(self):
|
||||
if not self.tokens["ignore_gir"]:
|
||||
self.root.gir.validate_ns(self.tokens["namespace"])
|
||||
|
||||
@validate("class_name")
|
||||
def gir_class_exists(self):
|
||||
if self.tokens["class_name"] and not self.tokens["ignore_gir"] and self.gir_ns is not None:
|
||||
self.root.gir.validate_class(self.tokens["class_name"], self.tokens["namespace"])
|
||||
|
||||
@property
|
||||
def gir_ns(self):
|
||||
if not self.tokens["ignore_gir"]:
|
||||
return self.root.gir.namespaces.get(self.tokens["namespace"] or "Gtk")
|
||||
|
||||
@property
|
||||
def gir_class(self):
|
||||
if self.tokens["class_name"] and not self.tokens["ignore_gir"]:
|
||||
return self.root.gir.get_class(self.tokens["class_name"], self.tokens["namespace"])
|
||||
|
||||
|
||||
@docs("namespace")
|
||||
def namespace_docs(self):
|
||||
if ns := self.root.gir.namespaces.get(self.tokens["namespace"]):
|
||||
return ns.doc
|
||||
|
||||
|
||||
@docs("class_name")
|
||||
def class_docs(self):
|
||||
if self.gir_class:
|
||||
return self.gir_class.doc
|
||||
|
||||
|
||||
def emit_xml(self, xml: XmlEmitter):
|
||||
xml.start_tag("object", **{
|
||||
"class": self.gir_class.glib_type_name if self.gir_class else self.tokens["class_name"],
|
||||
"id": self.tokens["id"],
|
||||
})
|
||||
for child in self.children:
|
||||
child.emit_xml(xml)
|
||||
xml.end_tag()
|
||||
|
||||
|
||||
def validate_parent_type(node, ns: str, name: str, err_msg: str):
|
||||
parent = node.root.gir.get_type(name, ns)
|
||||
container_type = node.parent_by_type(Object).gir_class
|
||||
if container_type and not container_type.assignable_to(parent):
|
||||
raise CompileError(f"{container_type.full_name} is not a {parent.full_name}, so it doesn't have {err_msg}")
|
139
blueprintcompiler/language/gobject_property.py
Normal file
139
blueprintcompiler/language/gobject_property.py
Normal file
|
@ -0,0 +1,139 @@
|
|||
# gobject_property.py
|
||||
#
|
||||
# Copyright 2022 James Westman <james@jwestman.net>
|
||||
#
|
||||
# This file is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This file 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
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
|
||||
from .gobject_object import Object
|
||||
from .gtkbuilder_template import Template
|
||||
from .values import Value, TranslatedStringValue
|
||||
from .common import *
|
||||
|
||||
|
||||
class Property(AstNode):
|
||||
grammar = AnyOf(
|
||||
Statement(
|
||||
UseIdent("name"),
|
||||
":",
|
||||
"bind",
|
||||
UseIdent("bind_source").expected("the ID of a source object to bind from"),
|
||||
".",
|
||||
UseIdent("bind_property").expected("a property name to bind from"),
|
||||
ZeroOrMore(AnyOf(
|
||||
["sync-create", UseLiteral("sync_create", True)],
|
||||
["inverted", UseLiteral("inverted", True)],
|
||||
["bidirectional", UseLiteral("bidirectional", True)],
|
||||
)),
|
||||
),
|
||||
Statement(
|
||||
UseIdent("name"),
|
||||
":",
|
||||
AnyOf(
|
||||
OBJECT_HOOKS,
|
||||
VALUE_HOOKS,
|
||||
).expected("a value"),
|
||||
),
|
||||
)
|
||||
|
||||
@property
|
||||
def gir_class(self):
|
||||
return self.parent.parent.gir_class
|
||||
|
||||
|
||||
@property
|
||||
def gir_property(self):
|
||||
if self.gir_class is not None:
|
||||
return self.gir_class.properties.get(self.tokens["name"])
|
||||
|
||||
|
||||
@property
|
||||
def value_type(self):
|
||||
if self.gir_property is not None:
|
||||
return self.gir_property.type
|
||||
|
||||
|
||||
@validate("name")
|
||||
def property_exists(self):
|
||||
if self.gir_class is None:
|
||||
# Objects that we have no gir data on should not be validated
|
||||
# This happens for classes defined by the app itself
|
||||
return
|
||||
|
||||
if isinstance(self.parent.parent, Template):
|
||||
# If the property is part of a template, it might be defined by
|
||||
# the application and thus not in gir
|
||||
return
|
||||
|
||||
if self.gir_property is None:
|
||||
raise CompileError(
|
||||
f"Class {self.gir_class.full_name} does not contain a property called {self.tokens['name']}",
|
||||
did_you_mean=(self.tokens["name"], self.gir_class.properties.keys())
|
||||
)
|
||||
|
||||
|
||||
@validate()
|
||||
def obj_property_type(self):
|
||||
if len(self.children[Object]) == 0:
|
||||
return
|
||||
|
||||
object = self.children[Object][0]
|
||||
type = self.value_type
|
||||
if object and type and object.gir_class and not object.gir_class.assignable_to(type):
|
||||
raise CompileError(
|
||||
f"Cannot assign {object.gir_class.full_name} to {type.full_name}"
|
||||
)
|
||||
|
||||
|
||||
@docs("name")
|
||||
def property_docs(self):
|
||||
if self.gir_property is not None:
|
||||
return self.gir_property.doc
|
||||
|
||||
|
||||
def emit_xml(self, xml: XmlEmitter):
|
||||
values = self.children[Value]
|
||||
value = values[0] if len(values) == 1 else None
|
||||
|
||||
bind_flags = []
|
||||
if self.tokens["sync_create"]:
|
||||
bind_flags.append("sync-create")
|
||||
if self.tokens["inverted"]:
|
||||
bind_flags.append("invert-boolean")
|
||||
if self.tokens["bidirectional"]:
|
||||
bind_flags.append("bidirectional")
|
||||
bind_flags_str = "|".join(bind_flags) or None
|
||||
|
||||
props = {
|
||||
"name": self.tokens["name"],
|
||||
"bind-source": self.tokens["bind_source"],
|
||||
"bind-property": self.tokens["bind_property"],
|
||||
"bind-flags": bind_flags_str,
|
||||
}
|
||||
|
||||
if isinstance(value, TranslatedStringValue):
|
||||
props = { **props, **value.attrs }
|
||||
|
||||
if len(self.children[Object]) == 1:
|
||||
xml.start_tag("property", **props)
|
||||
self.children[Object][0].emit_xml(xml)
|
||||
xml.end_tag()
|
||||
elif value is None:
|
||||
xml.put_self_closing("property", **props)
|
||||
else:
|
||||
xml.start_tag("property", **props)
|
||||
value.emit_xml(xml)
|
||||
xml.end_tag()
|
|
@ -18,6 +18,7 @@
|
|||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
|
||||
from .gtkbuilder_template import Template
|
||||
from .common import *
|
||||
|
||||
|
||||
|
|
|
@ -17,14 +17,10 @@
|
|||
#
|
||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
from ..ast import BaseTypedAttribute, Value
|
||||
from ..ast_utils import AstNode, validate, docs
|
||||
from ..completions_utils import *
|
||||
from ..gir import StringType, BoolType, IntType, FloatType, GirType
|
||||
from ..lsp_utils import Completion, CompletionItemKind
|
||||
from ..parse_tree import *
|
||||
from ..parser_utils import *
|
||||
from ..xml_emitter import XmlEmitter
|
||||
from .gobject_object import ObjectContent, validate_parent_type
|
||||
from .attributes import BaseTypedAttribute
|
||||
from .values import Value
|
||||
from .common import *
|
||||
|
||||
|
||||
def get_property_types(gir):
|
||||
|
@ -109,7 +105,7 @@ class A11yProperty(BaseTypedAttribute):
|
|||
grammar = Statement(
|
||||
UseIdent("name"),
|
||||
":",
|
||||
value.expected("a value"),
|
||||
VALUE_HOOKS.expected("a value"),
|
||||
)
|
||||
|
||||
@property
|
||||
|
@ -153,7 +149,7 @@ class A11y(AstNode):
|
|||
|
||||
@validate("accessibility")
|
||||
def container_is_widget(self):
|
||||
self.validate_parent_type("Gtk", "Widget", "accessibility properties")
|
||||
validate_parent_type(self, "Gtk", "Widget", "accessibility properties")
|
||||
|
||||
|
||||
def emit_xml(self, xml: XmlEmitter):
|
||||
|
@ -164,7 +160,7 @@ class A11y(AstNode):
|
|||
|
||||
|
||||
@completer(
|
||||
applies_in=[ast.ObjectContent],
|
||||
applies_in=[ObjectContent],
|
||||
matches=new_statement_patterns,
|
||||
)
|
||||
def a11y_completer(ast_node, match_variables):
|
||||
|
|
|
@ -18,14 +18,9 @@
|
|||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
|
||||
from ..ast import BaseTypedAttribute
|
||||
from ..ast_utils import AstNode, validate
|
||||
from ..completions_utils import *
|
||||
from ..gir import StringType
|
||||
from ..lsp_utils import Completion, CompletionItemKind
|
||||
from ..parse_tree import *
|
||||
from ..parser_utils import *
|
||||
from ..xml_emitter import XmlEmitter
|
||||
from .attributes import BaseTypedAttribute
|
||||
from .gobject_object import ObjectContent, validate_parent_type
|
||||
from .common import *
|
||||
|
||||
|
||||
class Item(BaseTypedAttribute):
|
||||
|
@ -44,7 +39,7 @@ item = Group(
|
|||
UseIdent("name"),
|
||||
":",
|
||||
]),
|
||||
value,
|
||||
VALUE_HOOKS,
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -59,7 +54,7 @@ class Items(AstNode):
|
|||
|
||||
@validate("items")
|
||||
def container_is_combo_box_text(self):
|
||||
self.validate_parent_type("Gtk", "ComboBoxText", "combo box items")
|
||||
validate_parent_type(self, "Gtk", "ComboBoxText", "combo box items")
|
||||
|
||||
|
||||
def emit_xml(self, xml: XmlEmitter):
|
||||
|
@ -70,7 +65,7 @@ class Items(AstNode):
|
|||
|
||||
|
||||
@completer(
|
||||
applies_in=[ast.ObjectContent],
|
||||
applies_in=[ObjectContent],
|
||||
applies_in_subclass=("Gtk", "ComboBoxText"),
|
||||
matches=new_statement_patterns,
|
||||
)
|
||||
|
|
|
@ -18,19 +18,14 @@
|
|||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
|
||||
from .. import ast
|
||||
from ..ast_utils import AstNode, validate
|
||||
from ..completions_utils import *
|
||||
from ..lsp_utils import Completion, CompletionItemKind
|
||||
from ..parse_tree import *
|
||||
from ..parser_utils import *
|
||||
from ..xml_emitter import XmlEmitter
|
||||
from .gobject_object import ObjectContent, validate_parent_type
|
||||
from .common import *
|
||||
|
||||
|
||||
class Filters(AstNode):
|
||||
@validate()
|
||||
def container_is_file_filter(self):
|
||||
self.validate_parent_type("Gtk", "FileFilter", "file filter properties")
|
||||
validate_parent_type(self, "Gtk", "FileFilter", "file filter properties")
|
||||
|
||||
def emit_xml(self, xml: XmlEmitter):
|
||||
xml.start_tag(self.tokens["tag_name"])
|
||||
|
@ -74,7 +69,7 @@ suffixes = create_node("suffixes", "suffix")
|
|||
|
||||
|
||||
@completer(
|
||||
applies_in=[ast.ObjectContent],
|
||||
applies_in=[ObjectContent],
|
||||
applies_in_subclass=("Gtk", "FileFilter"),
|
||||
matches=new_statement_patterns,
|
||||
)
|
||||
|
|
|
@ -18,13 +18,9 @@
|
|||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
|
||||
from ..ast import BaseAttribute
|
||||
from ..ast_utils import AstNode, validate
|
||||
from ..completions_utils import *
|
||||
from ..lsp_utils import Completion, CompletionItemKind
|
||||
from ..parse_tree import *
|
||||
from ..parser_utils import *
|
||||
from ..xml_emitter import XmlEmitter
|
||||
from .attributes import BaseAttribute
|
||||
from .gobject_object import ObjectContent, validate_parent_type
|
||||
from .common import *
|
||||
|
||||
|
||||
class LayoutProperty(BaseAttribute):
|
||||
|
@ -41,7 +37,7 @@ layout_prop = Group(
|
|||
Statement(
|
||||
UseIdent("name"),
|
||||
":",
|
||||
value.expected("a value"),
|
||||
VALUE_HOOKS.expected("a value"),
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -55,7 +51,7 @@ class Layout(AstNode):
|
|||
|
||||
@validate("layout")
|
||||
def container_is_widget(self):
|
||||
self.validate_parent_type("Gtk", "Widget", "layout properties")
|
||||
validate_parent_type(self, "Gtk", "Widget", "layout properties")
|
||||
|
||||
|
||||
def emit_xml(self, xml: XmlEmitter):
|
||||
|
@ -66,7 +62,7 @@ class Layout(AstNode):
|
|||
|
||||
|
||||
@completer(
|
||||
applies_in=[ast.ObjectContent],
|
||||
applies_in=[ObjectContent],
|
||||
applies_in_subclass=("Gtk", "Widget"),
|
||||
matches=new_statement_patterns,
|
||||
)
|
||||
|
|
|
@ -18,13 +18,10 @@
|
|||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
|
||||
from ..ast import BaseAttribute
|
||||
from ..ast_utils import AstNode
|
||||
from ..completions_utils import *
|
||||
from ..lsp_utils import Completion, CompletionItemKind
|
||||
from ..parse_tree import *
|
||||
from ..parser_utils import *
|
||||
from ..xml_emitter import XmlEmitter
|
||||
from .attributes import BaseAttribute
|
||||
from .gobject_object import ObjectContent
|
||||
from .ui import UI
|
||||
from .common import *
|
||||
|
||||
|
||||
class Menu(AstNode):
|
||||
|
@ -74,7 +71,7 @@ menu_attribute = Group(
|
|||
[
|
||||
UseIdent("name"),
|
||||
":",
|
||||
value.expected("a value"),
|
||||
VALUE_HOOKS.expected("a value"),
|
||||
Match(";").expected(),
|
||||
]
|
||||
)
|
||||
|
@ -98,20 +95,20 @@ menu_item_shorthand = Group(
|
|||
"(",
|
||||
Group(
|
||||
MenuAttribute,
|
||||
[UseLiteral("name", "label"), value],
|
||||
[UseLiteral("name", "label"), VALUE_HOOKS],
|
||||
),
|
||||
Optional([
|
||||
",",
|
||||
Optional([
|
||||
Group(
|
||||
MenuAttribute,
|
||||
[UseLiteral("name", "action"), value],
|
||||
[UseLiteral("name", "action"), VALUE_HOOKS],
|
||||
),
|
||||
Optional([
|
||||
",",
|
||||
Group(
|
||||
MenuAttribute,
|
||||
[UseLiteral("name", "icon"), value],
|
||||
[UseLiteral("name", "icon"), VALUE_HOOKS],
|
||||
),
|
||||
])
|
||||
])
|
||||
|
@ -143,7 +140,7 @@ menu = Group(
|
|||
|
||||
|
||||
@completer(
|
||||
applies_in=[ast.UI],
|
||||
applies_in=[UI],
|
||||
matches=new_statement_patterns,
|
||||
)
|
||||
def menu_completer(ast_node, match_variables):
|
||||
|
|
|
@ -18,13 +18,8 @@
|
|||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
|
||||
from .. import ast
|
||||
from ..ast_utils import AstNode, validate
|
||||
from ..completions_utils import *
|
||||
from ..lsp_utils import Completion, CompletionItemKind
|
||||
from ..parse_tree import *
|
||||
from ..parser_utils import *
|
||||
from ..xml_emitter import XmlEmitter
|
||||
from .gobject_object import ObjectContent, validate_parent_type
|
||||
from .common import *
|
||||
|
||||
|
||||
class Widget(AstNode):
|
||||
|
@ -58,7 +53,7 @@ class Widgets(AstNode):
|
|||
|
||||
@validate("widgets")
|
||||
def container_is_size_group(self):
|
||||
self.validate_parent_type("Gtk", "SizeGroup", "size group properties")
|
||||
validate_parent_type(self, "Gtk", "SizeGroup", "size group properties")
|
||||
|
||||
def emit_xml(self, xml: XmlEmitter):
|
||||
xml.start_tag("widgets")
|
||||
|
@ -68,7 +63,7 @@ class Widgets(AstNode):
|
|||
|
||||
|
||||
@completer(
|
||||
applies_in=[ast.ObjectContent],
|
||||
applies_in=[ObjectContent],
|
||||
applies_in_subclass=("Gtk", "SizeGroup"),
|
||||
matches=new_statement_patterns,
|
||||
)
|
||||
|
|
|
@ -18,18 +18,14 @@
|
|||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
|
||||
from ..ast import BaseTypedAttribute, Value, TranslatedStringValue
|
||||
from ..ast_utils import AstNode, validate
|
||||
from ..completions_utils import *
|
||||
from ..gir import StringType
|
||||
from ..lsp_utils import Completion, CompletionItemKind
|
||||
from ..parse_tree import *
|
||||
from ..parser_utils import *
|
||||
from ..xml_emitter import XmlEmitter
|
||||
from .attributes import BaseTypedAttribute
|
||||
from .gobject_object import ObjectContent, validate_parent_type
|
||||
from .values import Value, TranslatedStringValue
|
||||
from .common import *
|
||||
|
||||
|
||||
class Item(AstNode):
|
||||
grammar = value
|
||||
grammar = VALUE_HOOKS
|
||||
|
||||
@property
|
||||
def value_type(self):
|
||||
|
@ -53,7 +49,7 @@ class Strings(AstNode):
|
|||
|
||||
@validate("items")
|
||||
def container_is_string_list(self):
|
||||
self.validate_parent_type("Gtk", "StringList", "StringList items")
|
||||
validate_parent_type(self, "Gtk", "StringList", "StringList items")
|
||||
|
||||
|
||||
def emit_xml(self, xml: XmlEmitter):
|
||||
|
@ -64,7 +60,7 @@ class Strings(AstNode):
|
|||
|
||||
|
||||
@completer(
|
||||
applies_in=[ast.ObjectContent],
|
||||
applies_in=[ObjectContent],
|
||||
applies_in_subclass=("Gtk", "StringList"),
|
||||
matches=new_statement_patterns,
|
||||
)
|
||||
|
|
|
@ -18,13 +18,8 @@
|
|||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
|
||||
from .. import ast
|
||||
from ..ast_utils import AstNode, validate
|
||||
from ..completions_utils import *
|
||||
from ..lsp_utils import Completion, CompletionItemKind
|
||||
from ..parse_tree import *
|
||||
from ..parser_utils import *
|
||||
from ..xml_emitter import XmlEmitter
|
||||
from .gobject_object import ObjectContent, validate_parent_type
|
||||
from .common import *
|
||||
|
||||
|
||||
class StyleClass(AstNode):
|
||||
|
@ -44,7 +39,7 @@ class Styles(AstNode):
|
|||
|
||||
@validate("styles")
|
||||
def container_is_widget(self):
|
||||
self.validate_parent_type("Gtk", "Widget", "style classes")
|
||||
validate_parent_type(self, "Gtk", "Widget", "style classes")
|
||||
|
||||
def emit_xml(self, xml: XmlEmitter):
|
||||
xml.start_tag("style")
|
||||
|
@ -54,7 +49,7 @@ class Styles(AstNode):
|
|||
|
||||
|
||||
@completer(
|
||||
applies_in=[ast.ObjectContent],
|
||||
applies_in=[ObjectContent],
|
||||
applies_in_subclass=("Gtk", "Widget"),
|
||||
matches=new_statement_patterns,
|
||||
)
|
||||
|
|
45
blueprintcompiler/language/gtkbuilder_child.py
Normal file
45
blueprintcompiler/language/gtkbuilder_child.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
# gtkbuilder_child.py
|
||||
#
|
||||
# Copyright 2022 James Westman <james@jwestman.net>
|
||||
#
|
||||
# This file is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This file 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
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
|
||||
from .gobject_object import Object
|
||||
from .common import *
|
||||
|
||||
|
||||
class Child(AstNode):
|
||||
grammar = [
|
||||
Optional([
|
||||
"[",
|
||||
Optional(["internal-child", UseLiteral("internal_child", True)]),
|
||||
UseIdent("child_type").expected("a child type"),
|
||||
"]",
|
||||
]),
|
||||
Object,
|
||||
]
|
||||
|
||||
def emit_xml(self, xml: XmlEmitter):
|
||||
child_type = internal_child = None
|
||||
if self.tokens["internal_child"]:
|
||||
internal_child = self.tokens["child_type"]
|
||||
else:
|
||||
child_type = self.tokens["child_type"]
|
||||
xml.start_tag("child", type=child_type, internal_child=internal_child)
|
||||
for child in self.children:
|
||||
child.emit_xml(xml)
|
||||
xml.end_tag()
|
46
blueprintcompiler/language/gtkbuilder_template.py
Normal file
46
blueprintcompiler/language/gtkbuilder_template.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
# gtkbuilder_template.py
|
||||
#
|
||||
# Copyright 2022 James Westman <james@jwestman.net>
|
||||
#
|
||||
# This file is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This file 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
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
|
||||
from .gobject_object import Object, ObjectContent
|
||||
from .common import *
|
||||
|
||||
|
||||
class Template(Object):
|
||||
grammar = [
|
||||
"template",
|
||||
UseIdent("name").expected("template class name"),
|
||||
Optional([
|
||||
Match(":"),
|
||||
class_name.expected("parent class"),
|
||||
]),
|
||||
ObjectContent,
|
||||
]
|
||||
|
||||
def emit_xml(self, xml: XmlEmitter):
|
||||
if self.gir_class:
|
||||
parent = self.gir_class.glib_type_name
|
||||
elif self.tokens["class_name"]:
|
||||
parent = self.tokens["class_name"]
|
||||
else:
|
||||
parent = None
|
||||
xml.start_tag("template", **{"class": self.tokens["name"]}, parent=parent)
|
||||
for child in self.children:
|
||||
child.emit_xml(xml)
|
||||
xml.end_tag()
|
|
@ -18,6 +18,7 @@
|
|||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
|
||||
from .. import gir
|
||||
from .common import *
|
||||
|
||||
|
||||
|
|
103
blueprintcompiler/language/ui.py
Normal file
103
blueprintcompiler/language/ui.py
Normal file
|
@ -0,0 +1,103 @@
|
|||
# ui.py
|
||||
#
|
||||
# Copyright 2022 James Westman <james@jwestman.net>
|
||||
#
|
||||
# This file is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This file 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
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
|
||||
from .. import gir
|
||||
from .imports import GtkDirective, Import
|
||||
from .gtkbuilder_template import Template
|
||||
from .common import *
|
||||
|
||||
|
||||
class UI(AstNode):
|
||||
""" The AST node for the entire file """
|
||||
|
||||
grammar = [
|
||||
GtkDirective,
|
||||
ZeroOrMore(Import),
|
||||
Until(AnyOf(
|
||||
Template,
|
||||
OBJECT_HOOKS,
|
||||
), Eof()),
|
||||
]
|
||||
|
||||
@property
|
||||
def gir(self):
|
||||
gir_ctx = gir.GirContext()
|
||||
self._gir_errors = []
|
||||
|
||||
try:
|
||||
gir_ctx.add_namespace(self.children[GtkDirective][0].gir_namespace)
|
||||
except CompileError as e:
|
||||
e.start = self.children[GtkDirective][0].group.start
|
||||
e.end = self.children[GtkDirective][0].group.end
|
||||
self._gir_errors.append(e)
|
||||
|
||||
for i in self.children[Import]:
|
||||
try:
|
||||
if i.gir_namespace is not None:
|
||||
gir_ctx.add_namespace(i.gir_namespace)
|
||||
except CompileError as e:
|
||||
e.start = i.group.tokens["namespace"].start
|
||||
e.end = i.group.tokens["version"].end
|
||||
self._gir_errors.append(e)
|
||||
|
||||
return gir_ctx
|
||||
|
||||
|
||||
@property
|
||||
def objects_by_id(self):
|
||||
return { obj.tokens["id"]: obj for obj in self.iterate_children_recursive() if obj.tokens["id"] is not None }
|
||||
|
||||
|
||||
@validate()
|
||||
def gir_errors(self):
|
||||
# make sure gir is loaded
|
||||
self.gir
|
||||
if len(self._gir_errors):
|
||||
raise MultipleErrors(self._gir_errors)
|
||||
|
||||
|
||||
@validate()
|
||||
def at_most_one_template(self):
|
||||
if len(self.children[Template]) > 1:
|
||||
for template in self.children[Template][1:]:
|
||||
raise CompileError(
|
||||
f"Only one template may be defined per file, but this file contains {len(self.children[Template])}",
|
||||
template.group.tokens["name"].start, template.group.tokens["name"].end,
|
||||
)
|
||||
|
||||
|
||||
@validate()
|
||||
def unique_ids(self):
|
||||
passed = {}
|
||||
for obj in self.iterate_children_recursive():
|
||||
if obj.tokens["id"] is None:
|
||||
continue
|
||||
|
||||
if obj.tokens["id"] in passed:
|
||||
token = obj.group.tokens["id"]
|
||||
raise CompileError(f"Duplicate object ID '{obj.tokens['id']}'", token.start, token.end)
|
||||
passed[obj.tokens["id"]] = obj
|
||||
|
||||
|
||||
def emit_xml(self, xml: XmlEmitter):
|
||||
xml.start_tag("interface")
|
||||
for x in self.children:
|
||||
x.emit_xml(xml)
|
||||
xml.end_tag()
|
175
blueprintcompiler/language/values.py
Normal file
175
blueprintcompiler/language/values.py
Normal file
|
@ -0,0 +1,175 @@
|
|||
# values.py
|
||||
#
|
||||
# Copyright 2022 James Westman <james@jwestman.net>
|
||||
#
|
||||
# This file is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This file 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
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
|
||||
from .common import *
|
||||
|
||||
|
||||
class Value(AstNode):
|
||||
pass
|
||||
|
||||
|
||||
class TranslatedStringValue(Value):
|
||||
grammar = AnyOf(
|
||||
[
|
||||
"_",
|
||||
"(",
|
||||
UseQuoted("value").expected("a quoted string"),
|
||||
Match(")").expected(),
|
||||
],
|
||||
[
|
||||
"C_",
|
||||
"(",
|
||||
UseQuoted("context").expected("a quoted string"),
|
||||
",",
|
||||
UseQuoted("value").expected("a quoted string"),
|
||||
Optional(","),
|
||||
Match(")").expected(),
|
||||
],
|
||||
)
|
||||
|
||||
@property
|
||||
def attrs(self):
|
||||
attrs = { "translatable": "true" }
|
||||
if "context" in self.tokens:
|
||||
attrs["context"] = self.tokens["context"]
|
||||
return attrs
|
||||
|
||||
def emit_xml(self, xml: XmlEmitter):
|
||||
xml.put_text(self.tokens["value"])
|
||||
|
||||
|
||||
class LiteralValue(Value):
|
||||
grammar = AnyOf(
|
||||
UseNumber("value"),
|
||||
UseQuoted("value"),
|
||||
)
|
||||
|
||||
def emit_xml(self, xml: XmlEmitter):
|
||||
xml.put_text(self.tokens["value"])
|
||||
|
||||
@validate()
|
||||
def validate_for_type(self):
|
||||
type = self.parent.value_type
|
||||
if isinstance(type, gir.IntType):
|
||||
try:
|
||||
int(self.tokens["value"])
|
||||
except:
|
||||
raise CompileError(f"Cannot convert {self.group.tokens['value']} to integer")
|
||||
|
||||
elif isinstance(type, gir.UIntType):
|
||||
try:
|
||||
int(self.tokens["value"])
|
||||
if int(self.tokens["value"]) < 0:
|
||||
raise Exception()
|
||||
except:
|
||||
raise CompileError(f"Cannot convert {self.group.tokens['value']} to unsigned integer")
|
||||
|
||||
elif isinstance(type, gir.FloatType):
|
||||
try:
|
||||
float(self.tokens["value"])
|
||||
except:
|
||||
raise CompileError(f"Cannot convert {self.group.tokens['value']} to float")
|
||||
|
||||
elif isinstance(type, gir.StringType):
|
||||
pass
|
||||
|
||||
elif isinstance(type, gir.Class) or isinstance(type, gir.Interface):
|
||||
parseable_types = [
|
||||
"Gdk.Paintable",
|
||||
"Gdk.Texture",
|
||||
"Gdk.Pixbuf",
|
||||
"GLib.File",
|
||||
"Gtk.ShortcutTrigger",
|
||||
"Gtk.ShortcutAction",
|
||||
]
|
||||
if type.full_name not in parseable_types:
|
||||
raise CompileError(f"Cannot convert {self.group.tokens['value']} to {type.full_name}")
|
||||
|
||||
elif type is not None:
|
||||
raise CompileError(f"Cannot convert {self.group.tokens['value']} to {type.full_name}")
|
||||
|
||||
|
||||
class Flag(AstNode):
|
||||
grammar = UseIdent("value")
|
||||
|
||||
class FlagsValue(Value):
|
||||
grammar = [Flag, "|", Delimited(Flag, "|")]
|
||||
|
||||
def emit_xml(self, xml: XmlEmitter):
|
||||
xml.put_text("|".join([flag.tokens["value"] for flag in self.children[Flag]]))
|
||||
|
||||
|
||||
class IdentValue(Value):
|
||||
grammar = UseIdent("value")
|
||||
|
||||
def emit_xml(self, xml: XmlEmitter):
|
||||
if isinstance(self.parent.value_type, gir.Enumeration):
|
||||
xml.put_text(self.parent.value_type.members[self.tokens["value"]].nick)
|
||||
else:
|
||||
xml.put_text(self.tokens["value"])
|
||||
|
||||
@validate()
|
||||
def validate_for_type(self):
|
||||
type = self.parent.value_type
|
||||
|
||||
if isinstance(type, gir.Enumeration):
|
||||
if self.tokens["value"] not in type.members:
|
||||
raise CompileError(
|
||||
f"{self.tokens['value']} is not a member of {type.full_name}",
|
||||
did_you_mean=(self.tokens['value'], type.members.keys()),
|
||||
)
|
||||
|
||||
elif isinstance(type, gir.BoolType):
|
||||
if self.tokens["value"] not in ["true", "false"]:
|
||||
raise CompileError(
|
||||
f"Expected 'true' or 'false' for boolean value",
|
||||
did_you_mean=(self.tokens['value'], ["true", "false"]),
|
||||
)
|
||||
|
||||
elif type is not None:
|
||||
object = self.root.objects_by_id.get(self.tokens["value"])
|
||||
if object is None:
|
||||
raise CompileError(
|
||||
f"Could not find object with ID {self.tokens['value']}",
|
||||
did_you_mean=(self.tokens['value'], self.root.objects_by_id.keys()),
|
||||
)
|
||||
elif object.gir_class and not object.gir_class.assignable_to(type):
|
||||
raise CompileError(
|
||||
f"Cannot assign {object.gir_class.full_name} to {type.full_name}"
|
||||
)
|
||||
|
||||
|
||||
@docs()
|
||||
def docs(self):
|
||||
type = self.parent.value_type
|
||||
if isinstance(type, gir.Enumeration):
|
||||
if member := type.members.get(self.tokens["value"]):
|
||||
return member.doc
|
||||
else:
|
||||
return type.doc
|
||||
elif isinstance(type, gir.GirNode):
|
||||
return type.doc
|
||||
|
||||
|
||||
def get_semantic_tokens(self) -> T.Iterator[SemanticToken]:
|
||||
if isinstance(self.parent.value_type, gir.Enumeration):
|
||||
token = self.group.tokens["value"]
|
||||
yield SemanticToken(token.start, token.end, SemanticTokenType.EnumMember)
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue