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,477 +0,0 @@
|
||||||
# ast.py
|
|
||||||
#
|
|
||||||
# Copyright 2021 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
|
|
||||||
|
|
||||||
import typing as T
|
|
||||||
|
|
||||||
from .ast_utils import *
|
|
||||||
from .errors import CompileError, CompilerBugError, MultipleErrors
|
|
||||||
from . import gir
|
|
||||||
from .lsp_utils import Completion, CompletionItemKind, SemanticToken, SemanticTokenType
|
|
||||||
from .parse_tree import *
|
|
||||||
from .tokenizer import Token
|
|
||||||
from .utils import lazy_prop
|
|
||||||
from .xml_emitter import XmlEmitter
|
|
||||||
|
|
||||||
|
|
||||||
class UI(AstNode):
|
|
||||||
""" The AST node for the entire file """
|
|
||||||
|
|
||||||
@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
|
|
||||||
|
|
||||||
|
|
||||||
@lazy_prop
|
|
||||||
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()
|
|
||||||
|
|
||||||
|
|
||||||
class GtkDirective(AstNode):
|
|
||||||
grammar = Statement(
|
|
||||||
Match("using").err("File must start with a \"using Gtk\" directive (e.g. `using Gtk 4.0;`)"),
|
|
||||||
Match("Gtk").err("File must start with a \"using Gtk\" directive (e.g. `using Gtk 4.0;`)"),
|
|
||||||
UseNumberText("version").expected("a version number for GTK"),
|
|
||||||
)
|
|
||||||
|
|
||||||
@validate("version")
|
|
||||||
def gtk_version(self):
|
|
||||||
if self.tokens["version"] not in ["4.0"]:
|
|
||||||
err = CompileError("Only GTK 4 is supported")
|
|
||||||
if self.tokens["version"].startswith("4"):
|
|
||||||
err.hint("Expected the GIR version, not an exact version number. Use `using Gtk 4.0;`.")
|
|
||||||
else:
|
|
||||||
err.hint("Expected `using Gtk 4.0;`")
|
|
||||||
raise err
|
|
||||||
|
|
||||||
|
|
||||||
@property
|
|
||||||
def gir_namespace(self):
|
|
||||||
return gir.get_namespace("Gtk", self.tokens["version"])
|
|
||||||
|
|
||||||
|
|
||||||
def emit_xml(self, xml: XmlEmitter):
|
|
||||||
xml.put_self_closing("requires", lib="gtk", version=self.tokens["version"])
|
|
||||||
|
|
||||||
|
|
||||||
class Import(AstNode):
|
|
||||||
grammar = Statement(
|
|
||||||
"using",
|
|
||||||
UseIdent("namespace").expected("a GIR namespace"),
|
|
||||||
UseNumberText("version").expected("a version number"),
|
|
||||||
)
|
|
||||||
|
|
||||||
@validate("namespace", "version")
|
|
||||||
def namespace_exists(self):
|
|
||||||
gir.get_namespace(self.tokens["namespace"], self.tokens["version"])
|
|
||||||
|
|
||||||
@property
|
|
||||||
def gir_namespace(self):
|
|
||||||
try:
|
|
||||||
return gir.get_namespace(self.tokens["namespace"], self.tokens["version"])
|
|
||||||
except CompileError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def emit_xml(self, xml):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Object(AstNode):
|
|
||||||
@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()
|
|
||||||
|
|
||||||
|
|
||||||
class Template(Object):
|
|
||||||
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()
|
|
||||||
|
|
||||||
|
|
||||||
class Child(AstNode):
|
|
||||||
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()
|
|
||||||
|
|
||||||
|
|
||||||
class ObjectContent(AstNode):
|
|
||||||
@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 Property(AstNode):
|
|
||||||
@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()
|
|
||||||
|
|
||||||
|
|
||||||
class Value(ast.AstNode):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class TranslatedStringValue(Value):
|
|
||||||
@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):
|
|
||||||
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):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class FlagsValue(Value):
|
|
||||||
def emit_xml(self, xml: XmlEmitter):
|
|
||||||
xml.put_text("|".join([flag.tokens["value"] for flag in self.children[Flag]]))
|
|
||||||
|
|
||||||
|
|
||||||
class IdentValue(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)
|
|
||||||
|
|
||||||
|
|
||||||
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. """
|
|
|
@ -20,7 +20,6 @@
|
||||||
import typing as T
|
import typing as T
|
||||||
from collections import ChainMap, defaultdict
|
from collections import ChainMap, defaultdict
|
||||||
|
|
||||||
from . import ast
|
|
||||||
from .errors import *
|
from .errors import *
|
||||||
from .lsp_utils import SemanticToken
|
from .lsp_utils import SemanticToken
|
||||||
from .utils import lazy_prop
|
from .utils import lazy_prop
|
||||||
|
@ -72,12 +71,6 @@ class AstNode:
|
||||||
else:
|
else:
|
||||||
return self.parent.parent_by_type(type)
|
return self.parent.parent_by_type(type)
|
||||||
|
|
||||||
def validate_parent_type(self, ns: str, name: str, err_msg: str):
|
|
||||||
parent = self.root.gir.get_type(name, ns)
|
|
||||||
container_type = self.parent_by_type(ast.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}")
|
|
||||||
|
|
||||||
@lazy_prop
|
@lazy_prop
|
||||||
def errors(self):
|
def errors(self):
|
||||||
return list(self._get_errors())
|
return list(self._get_errors())
|
||||||
|
|
|
@ -19,7 +19,6 @@
|
||||||
|
|
||||||
import typing as T
|
import typing as T
|
||||||
|
|
||||||
from . import ast
|
|
||||||
from . import gir
|
from . import gir
|
||||||
from .completions_utils import *
|
from .completions_utils import *
|
||||||
from .lsp_utils import Completion, CompletionItemKind
|
from .lsp_utils import Completion, CompletionItemKind
|
||||||
|
|
|
@ -20,7 +20,6 @@
|
||||||
|
|
||||||
import typing as T
|
import typing as T
|
||||||
|
|
||||||
from . import ast
|
|
||||||
from .tokenizer import Token, TokenType
|
from .tokenizer import Token, TokenType
|
||||||
from .lsp_utils import Completion
|
from .lsp_utils import Completion
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
""" Contains all the syntax beyond basic objects, properties, signal, and
|
""" Contains all the syntax beyond basic objects, properties, signal, and
|
||||||
templates. """
|
templates. """
|
||||||
|
|
||||||
|
from .gobject_object import Object
|
||||||
|
from .gobject_property import Property
|
||||||
from .gobject_signal import Signal
|
from .gobject_signal import Signal
|
||||||
from .gtk_a11y import A11y
|
from .gtk_a11y import A11y
|
||||||
from .gtk_combo_box_text import Items
|
from .gtk_combo_box_text import Items
|
||||||
|
@ -10,12 +12,36 @@ from .gtk_menu import menu
|
||||||
from .gtk_size_group import Widgets
|
from .gtk_size_group import Widgets
|
||||||
from .gtk_string_list import Strings
|
from .gtk_string_list import Strings
|
||||||
from .gtk_styles import Styles
|
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 *
|
from .common import *
|
||||||
|
|
||||||
OBJECT_HOOKS.children = [menu]
|
OBJECT_HOOKS.children = [
|
||||||
|
menu,
|
||||||
|
Object,
|
||||||
|
]
|
||||||
|
|
||||||
OBJECT_CONTENT_HOOKS.children = [
|
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,
|
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
|
# 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 ..ast_utils import AstNode, validate, docs
|
||||||
from ..completions_utils import *
|
from ..completions_utils import *
|
||||||
from ..gir import StringType, BoolType, IntType, FloatType, GirType
|
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 ..parse_tree import *
|
||||||
from ..parser_utils import *
|
from ..parser_utils import *
|
||||||
from ..xml_emitter import XmlEmitter
|
from ..xml_emitter import XmlEmitter
|
||||||
|
@ -30,3 +30,4 @@ from ..xml_emitter import XmlEmitter
|
||||||
|
|
||||||
OBJECT_HOOKS = AnyOf()
|
OBJECT_HOOKS = AnyOf()
|
||||||
OBJECT_CONTENT_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
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
|
|
||||||
|
from .gtkbuilder_template import Template
|
||||||
from .common import *
|
from .common import *
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -17,14 +17,10 @@
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
from ..ast import BaseTypedAttribute, Value
|
from .gobject_object import ObjectContent, validate_parent_type
|
||||||
from ..ast_utils import AstNode, validate, docs
|
from .attributes import BaseTypedAttribute
|
||||||
from ..completions_utils import *
|
from .values import Value
|
||||||
from ..gir import StringType, BoolType, IntType, FloatType, GirType
|
from .common import *
|
||||||
from ..lsp_utils import Completion, CompletionItemKind
|
|
||||||
from ..parse_tree import *
|
|
||||||
from ..parser_utils import *
|
|
||||||
from ..xml_emitter import XmlEmitter
|
|
||||||
|
|
||||||
|
|
||||||
def get_property_types(gir):
|
def get_property_types(gir):
|
||||||
|
@ -109,7 +105,7 @@ class A11yProperty(BaseTypedAttribute):
|
||||||
grammar = Statement(
|
grammar = Statement(
|
||||||
UseIdent("name"),
|
UseIdent("name"),
|
||||||
":",
|
":",
|
||||||
value.expected("a value"),
|
VALUE_HOOKS.expected("a value"),
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -153,7 +149,7 @@ class A11y(AstNode):
|
||||||
|
|
||||||
@validate("accessibility")
|
@validate("accessibility")
|
||||||
def container_is_widget(self):
|
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):
|
def emit_xml(self, xml: XmlEmitter):
|
||||||
|
@ -164,7 +160,7 @@ class A11y(AstNode):
|
||||||
|
|
||||||
|
|
||||||
@completer(
|
@completer(
|
||||||
applies_in=[ast.ObjectContent],
|
applies_in=[ObjectContent],
|
||||||
matches=new_statement_patterns,
|
matches=new_statement_patterns,
|
||||||
)
|
)
|
||||||
def a11y_completer(ast_node, match_variables):
|
def a11y_completer(ast_node, match_variables):
|
||||||
|
|
|
@ -18,14 +18,9 @@
|
||||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
|
|
||||||
from ..ast import BaseTypedAttribute
|
from .attributes import BaseTypedAttribute
|
||||||
from ..ast_utils import AstNode, validate
|
from .gobject_object import ObjectContent, validate_parent_type
|
||||||
from ..completions_utils import *
|
from .common import *
|
||||||
from ..gir import StringType
|
|
||||||
from ..lsp_utils import Completion, CompletionItemKind
|
|
||||||
from ..parse_tree import *
|
|
||||||
from ..parser_utils import *
|
|
||||||
from ..xml_emitter import XmlEmitter
|
|
||||||
|
|
||||||
|
|
||||||
class Item(BaseTypedAttribute):
|
class Item(BaseTypedAttribute):
|
||||||
|
@ -44,7 +39,7 @@ item = Group(
|
||||||
UseIdent("name"),
|
UseIdent("name"),
|
||||||
":",
|
":",
|
||||||
]),
|
]),
|
||||||
value,
|
VALUE_HOOKS,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -59,7 +54,7 @@ class Items(AstNode):
|
||||||
|
|
||||||
@validate("items")
|
@validate("items")
|
||||||
def container_is_combo_box_text(self):
|
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):
|
def emit_xml(self, xml: XmlEmitter):
|
||||||
|
@ -70,7 +65,7 @@ class Items(AstNode):
|
||||||
|
|
||||||
|
|
||||||
@completer(
|
@completer(
|
||||||
applies_in=[ast.ObjectContent],
|
applies_in=[ObjectContent],
|
||||||
applies_in_subclass=("Gtk", "ComboBoxText"),
|
applies_in_subclass=("Gtk", "ComboBoxText"),
|
||||||
matches=new_statement_patterns,
|
matches=new_statement_patterns,
|
||||||
)
|
)
|
||||||
|
|
|
@ -18,19 +18,14 @@
|
||||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
|
|
||||||
from .. import ast
|
from .gobject_object import ObjectContent, validate_parent_type
|
||||||
from ..ast_utils import AstNode, validate
|
from .common import *
|
||||||
from ..completions_utils import *
|
|
||||||
from ..lsp_utils import Completion, CompletionItemKind
|
|
||||||
from ..parse_tree import *
|
|
||||||
from ..parser_utils import *
|
|
||||||
from ..xml_emitter import XmlEmitter
|
|
||||||
|
|
||||||
|
|
||||||
class Filters(AstNode):
|
class Filters(AstNode):
|
||||||
@validate()
|
@validate()
|
||||||
def container_is_file_filter(self):
|
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):
|
def emit_xml(self, xml: XmlEmitter):
|
||||||
xml.start_tag(self.tokens["tag_name"])
|
xml.start_tag(self.tokens["tag_name"])
|
||||||
|
@ -74,7 +69,7 @@ suffixes = create_node("suffixes", "suffix")
|
||||||
|
|
||||||
|
|
||||||
@completer(
|
@completer(
|
||||||
applies_in=[ast.ObjectContent],
|
applies_in=[ObjectContent],
|
||||||
applies_in_subclass=("Gtk", "FileFilter"),
|
applies_in_subclass=("Gtk", "FileFilter"),
|
||||||
matches=new_statement_patterns,
|
matches=new_statement_patterns,
|
||||||
)
|
)
|
||||||
|
|
|
@ -18,13 +18,9 @@
|
||||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
|
|
||||||
from ..ast import BaseAttribute
|
from .attributes import BaseAttribute
|
||||||
from ..ast_utils import AstNode, validate
|
from .gobject_object import ObjectContent, validate_parent_type
|
||||||
from ..completions_utils import *
|
from .common import *
|
||||||
from ..lsp_utils import Completion, CompletionItemKind
|
|
||||||
from ..parse_tree import *
|
|
||||||
from ..parser_utils import *
|
|
||||||
from ..xml_emitter import XmlEmitter
|
|
||||||
|
|
||||||
|
|
||||||
class LayoutProperty(BaseAttribute):
|
class LayoutProperty(BaseAttribute):
|
||||||
|
@ -41,7 +37,7 @@ layout_prop = Group(
|
||||||
Statement(
|
Statement(
|
||||||
UseIdent("name"),
|
UseIdent("name"),
|
||||||
":",
|
":",
|
||||||
value.expected("a value"),
|
VALUE_HOOKS.expected("a value"),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -55,7 +51,7 @@ class Layout(AstNode):
|
||||||
|
|
||||||
@validate("layout")
|
@validate("layout")
|
||||||
def container_is_widget(self):
|
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):
|
def emit_xml(self, xml: XmlEmitter):
|
||||||
|
@ -66,7 +62,7 @@ class Layout(AstNode):
|
||||||
|
|
||||||
|
|
||||||
@completer(
|
@completer(
|
||||||
applies_in=[ast.ObjectContent],
|
applies_in=[ObjectContent],
|
||||||
applies_in_subclass=("Gtk", "Widget"),
|
applies_in_subclass=("Gtk", "Widget"),
|
||||||
matches=new_statement_patterns,
|
matches=new_statement_patterns,
|
||||||
)
|
)
|
||||||
|
|
|
@ -18,13 +18,10 @@
|
||||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
|
|
||||||
from ..ast import BaseAttribute
|
from .attributes import BaseAttribute
|
||||||
from ..ast_utils import AstNode
|
from .gobject_object import ObjectContent
|
||||||
from ..completions_utils import *
|
from .ui import UI
|
||||||
from ..lsp_utils import Completion, CompletionItemKind
|
from .common import *
|
||||||
from ..parse_tree import *
|
|
||||||
from ..parser_utils import *
|
|
||||||
from ..xml_emitter import XmlEmitter
|
|
||||||
|
|
||||||
|
|
||||||
class Menu(AstNode):
|
class Menu(AstNode):
|
||||||
|
@ -74,7 +71,7 @@ menu_attribute = Group(
|
||||||
[
|
[
|
||||||
UseIdent("name"),
|
UseIdent("name"),
|
||||||
":",
|
":",
|
||||||
value.expected("a value"),
|
VALUE_HOOKS.expected("a value"),
|
||||||
Match(";").expected(),
|
Match(";").expected(),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -98,20 +95,20 @@ menu_item_shorthand = Group(
|
||||||
"(",
|
"(",
|
||||||
Group(
|
Group(
|
||||||
MenuAttribute,
|
MenuAttribute,
|
||||||
[UseLiteral("name", "label"), value],
|
[UseLiteral("name", "label"), VALUE_HOOKS],
|
||||||
),
|
),
|
||||||
Optional([
|
Optional([
|
||||||
",",
|
",",
|
||||||
Optional([
|
Optional([
|
||||||
Group(
|
Group(
|
||||||
MenuAttribute,
|
MenuAttribute,
|
||||||
[UseLiteral("name", "action"), value],
|
[UseLiteral("name", "action"), VALUE_HOOKS],
|
||||||
),
|
),
|
||||||
Optional([
|
Optional([
|
||||||
",",
|
",",
|
||||||
Group(
|
Group(
|
||||||
MenuAttribute,
|
MenuAttribute,
|
||||||
[UseLiteral("name", "icon"), value],
|
[UseLiteral("name", "icon"), VALUE_HOOKS],
|
||||||
),
|
),
|
||||||
])
|
])
|
||||||
])
|
])
|
||||||
|
@ -143,7 +140,7 @@ menu = Group(
|
||||||
|
|
||||||
|
|
||||||
@completer(
|
@completer(
|
||||||
applies_in=[ast.UI],
|
applies_in=[UI],
|
||||||
matches=new_statement_patterns,
|
matches=new_statement_patterns,
|
||||||
)
|
)
|
||||||
def menu_completer(ast_node, match_variables):
|
def menu_completer(ast_node, match_variables):
|
||||||
|
|
|
@ -18,13 +18,8 @@
|
||||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
|
|
||||||
from .. import ast
|
from .gobject_object import ObjectContent, validate_parent_type
|
||||||
from ..ast_utils import AstNode, validate
|
from .common import *
|
||||||
from ..completions_utils import *
|
|
||||||
from ..lsp_utils import Completion, CompletionItemKind
|
|
||||||
from ..parse_tree import *
|
|
||||||
from ..parser_utils import *
|
|
||||||
from ..xml_emitter import XmlEmitter
|
|
||||||
|
|
||||||
|
|
||||||
class Widget(AstNode):
|
class Widget(AstNode):
|
||||||
|
@ -58,7 +53,7 @@ class Widgets(AstNode):
|
||||||
|
|
||||||
@validate("widgets")
|
@validate("widgets")
|
||||||
def container_is_size_group(self):
|
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):
|
def emit_xml(self, xml: XmlEmitter):
|
||||||
xml.start_tag("widgets")
|
xml.start_tag("widgets")
|
||||||
|
@ -68,7 +63,7 @@ class Widgets(AstNode):
|
||||||
|
|
||||||
|
|
||||||
@completer(
|
@completer(
|
||||||
applies_in=[ast.ObjectContent],
|
applies_in=[ObjectContent],
|
||||||
applies_in_subclass=("Gtk", "SizeGroup"),
|
applies_in_subclass=("Gtk", "SizeGroup"),
|
||||||
matches=new_statement_patterns,
|
matches=new_statement_patterns,
|
||||||
)
|
)
|
||||||
|
|
|
@ -18,18 +18,14 @@
|
||||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
|
|
||||||
from ..ast import BaseTypedAttribute, Value, TranslatedStringValue
|
from .attributes import BaseTypedAttribute
|
||||||
from ..ast_utils import AstNode, validate
|
from .gobject_object import ObjectContent, validate_parent_type
|
||||||
from ..completions_utils import *
|
from .values import Value, TranslatedStringValue
|
||||||
from ..gir import StringType
|
from .common import *
|
||||||
from ..lsp_utils import Completion, CompletionItemKind
|
|
||||||
from ..parse_tree import *
|
|
||||||
from ..parser_utils import *
|
|
||||||
from ..xml_emitter import XmlEmitter
|
|
||||||
|
|
||||||
|
|
||||||
class Item(AstNode):
|
class Item(AstNode):
|
||||||
grammar = value
|
grammar = VALUE_HOOKS
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def value_type(self):
|
def value_type(self):
|
||||||
|
@ -53,7 +49,7 @@ class Strings(AstNode):
|
||||||
|
|
||||||
@validate("items")
|
@validate("items")
|
||||||
def container_is_string_list(self):
|
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):
|
def emit_xml(self, xml: XmlEmitter):
|
||||||
|
@ -64,7 +60,7 @@ class Strings(AstNode):
|
||||||
|
|
||||||
|
|
||||||
@completer(
|
@completer(
|
||||||
applies_in=[ast.ObjectContent],
|
applies_in=[ObjectContent],
|
||||||
applies_in_subclass=("Gtk", "StringList"),
|
applies_in_subclass=("Gtk", "StringList"),
|
||||||
matches=new_statement_patterns,
|
matches=new_statement_patterns,
|
||||||
)
|
)
|
||||||
|
|
|
@ -18,13 +18,8 @@
|
||||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
|
|
||||||
from .. import ast
|
from .gobject_object import ObjectContent, validate_parent_type
|
||||||
from ..ast_utils import AstNode, validate
|
from .common import *
|
||||||
from ..completions_utils import *
|
|
||||||
from ..lsp_utils import Completion, CompletionItemKind
|
|
||||||
from ..parse_tree import *
|
|
||||||
from ..parser_utils import *
|
|
||||||
from ..xml_emitter import XmlEmitter
|
|
||||||
|
|
||||||
|
|
||||||
class StyleClass(AstNode):
|
class StyleClass(AstNode):
|
||||||
|
@ -44,7 +39,7 @@ class Styles(AstNode):
|
||||||
|
|
||||||
@validate("styles")
|
@validate("styles")
|
||||||
def container_is_widget(self):
|
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):
|
def emit_xml(self, xml: XmlEmitter):
|
||||||
xml.start_tag("style")
|
xml.start_tag("style")
|
||||||
|
@ -54,7 +49,7 @@ class Styles(AstNode):
|
||||||
|
|
||||||
|
|
||||||
@completer(
|
@completer(
|
||||||
applies_in=[ast.ObjectContent],
|
applies_in=[ObjectContent],
|
||||||
applies_in_subclass=("Gtk", "Widget"),
|
applies_in_subclass=("Gtk", "Widget"),
|
||||||
matches=new_statement_patterns,
|
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
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
|
|
||||||
|
from .. import gir
|
||||||
from .common import *
|
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)
|
||||||
|
|
|
@ -530,5 +530,7 @@ def to_parse_node(value) -> ParseNode:
|
||||||
return Sequence(*value)
|
return Sequence(*value)
|
||||||
elif isinstance(value, type) and hasattr(value, "grammar"):
|
elif isinstance(value, type) and hasattr(value, "grammar"):
|
||||||
return Group(value, getattr(value, "grammar"))
|
return Group(value, getattr(value, "grammar"))
|
||||||
else:
|
elif isinstance(value, ParseNode):
|
||||||
return value
|
return value
|
||||||
|
else:
|
||||||
|
raise CompilerBugError()
|
||||||
|
|
|
@ -18,113 +18,18 @@
|
||||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
|
|
||||||
from . import ast
|
|
||||||
from .errors import MultipleErrors
|
from .errors import MultipleErrors
|
||||||
from .parse_tree import *
|
from .parse_tree import *
|
||||||
from .parser_utils import *
|
from .parser_utils import *
|
||||||
from .tokenizer import TokenType
|
from .tokenizer import TokenType
|
||||||
from .language import OBJECT_HOOKS, OBJECT_CONTENT_HOOKS
|
from .language import OBJECT_HOOKS, OBJECT_CONTENT_HOOKS, VALUE_HOOKS, Template, UI
|
||||||
|
|
||||||
|
|
||||||
def parse(tokens) -> T.Tuple[ast.UI, T.Optional[MultipleErrors]]:
|
def parse(tokens) -> T.Tuple[UI, T.Optional[MultipleErrors]]:
|
||||||
""" Parses a list of tokens into an abstract syntax tree. """
|
""" Parses a list of tokens into an abstract syntax tree. """
|
||||||
|
|
||||||
object = Group(
|
|
||||||
ast.Object,
|
|
||||||
None
|
|
||||||
)
|
|
||||||
|
|
||||||
property = Group(
|
|
||||||
ast.Property,
|
|
||||||
Statement(
|
|
||||||
UseIdent("name"),
|
|
||||||
":",
|
|
||||||
AnyOf(
|
|
||||||
OBJECT_HOOKS,
|
|
||||||
object,
|
|
||||||
value,
|
|
||||||
).expected("a value"),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
binding = Group(
|
|
||||||
ast.Property,
|
|
||||||
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)],
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
child = Group(
|
|
||||||
ast.Child,
|
|
||||||
[
|
|
||||||
Optional([
|
|
||||||
"[",
|
|
||||||
Optional(["internal-child", UseLiteral("internal_child", True)]),
|
|
||||||
UseIdent("child_type").expected("a child type"),
|
|
||||||
"]",
|
|
||||||
]),
|
|
||||||
object,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
object_content = Group(
|
|
||||||
ast.ObjectContent,
|
|
||||||
[
|
|
||||||
"{",
|
|
||||||
Until(AnyOf(
|
|
||||||
OBJECT_CONTENT_HOOKS,
|
|
||||||
binding,
|
|
||||||
property,
|
|
||||||
child,
|
|
||||||
), "}"),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
# work around the recursive reference
|
|
||||||
object.child = Sequence(
|
|
||||||
class_name,
|
|
||||||
Optional(UseIdent("id")),
|
|
||||||
object_content,
|
|
||||||
)
|
|
||||||
|
|
||||||
template = Group(
|
|
||||||
ast.Template,
|
|
||||||
[
|
|
||||||
"template",
|
|
||||||
UseIdent("name").expected("template class name"),
|
|
||||||
Optional([
|
|
||||||
Match(":"),
|
|
||||||
class_name.expected("parent class"),
|
|
||||||
]),
|
|
||||||
object_content.expected("block"),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
ui = Group(
|
|
||||||
ast.UI,
|
|
||||||
[
|
|
||||||
ast.GtkDirective,
|
|
||||||
ZeroOrMore(ast.Import),
|
|
||||||
Until(AnyOf(
|
|
||||||
OBJECT_HOOKS,
|
|
||||||
template,
|
|
||||||
object,
|
|
||||||
), Eof()),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
ctx = ParseContext(tokens)
|
ctx = ParseContext(tokens)
|
||||||
ui.parse(ctx)
|
AnyOf(UI).parse(ctx)
|
||||||
|
|
||||||
ast_node = ctx.last_group.to_ast() if ctx.last_group else None
|
ast_node = ctx.last_group.to_ast() if ctx.last_group else None
|
||||||
errors = MultipleErrors(ctx.errors) if len(ctx.errors) else None
|
errors = MultipleErrors(ctx.errors) if len(ctx.errors) else None
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
|
|
||||||
from . import ast
|
|
||||||
from .parse_tree import *
|
from .parse_tree import *
|
||||||
|
|
||||||
|
|
||||||
|
@ -35,48 +34,3 @@ class_name = AnyOf(
|
||||||
],
|
],
|
||||||
UseIdent("class_name"),
|
UseIdent("class_name"),
|
||||||
)
|
)
|
||||||
|
|
||||||
literal = Group(
|
|
||||||
ast.LiteralValue,
|
|
||||||
AnyOf(
|
|
||||||
UseNumber("value"),
|
|
||||||
UseQuoted("value"),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
ident_value = Group(
|
|
||||||
ast.IdentValue,
|
|
||||||
UseIdent("value"),
|
|
||||||
)
|
|
||||||
|
|
||||||
flags_value = Group(
|
|
||||||
ast.FlagsValue,
|
|
||||||
[
|
|
||||||
Group(ast.Flag, UseIdent("value")),
|
|
||||||
"|",
|
|
||||||
Delimited(Group(ast.Flag, UseIdent("value")), "|"),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
translated_string = Group(
|
|
||||||
ast.TranslatedStringValue,
|
|
||||||
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(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
value = AnyOf(translated_string, literal, flags_value, ident_value)
|
|
||||||
|
|
|
@ -87,8 +87,7 @@ class TestSamples(unittest.TestCase):
|
||||||
print("\n".join(diff))
|
print("\n".join(diff))
|
||||||
raise AssertionError()
|
raise AssertionError()
|
||||||
else: # pragma: no cover
|
else: # pragma: no cover
|
||||||
# Expected a compiler error but there wasn't one
|
raise AssertionError("Expected a compiler error, but none was emitted")
|
||||||
raise AssertionError()
|
|
||||||
|
|
||||||
|
|
||||||
def assert_decompile(self, name):
|
def assert_decompile(self, name):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue