mirror of
https://gitlab.gnome.org/jwestman/blueprint-compiler.git
synced 2025-05-10 16:52:14 -04:00
Compare commits
1 commit
abfa977742
...
af1e234a5a
Author | SHA1 | Date | |
---|---|---|---|
|
af1e234a5a |
47 changed files with 190 additions and 140 deletions
|
@ -160,11 +160,6 @@ class AstNode:
|
||||||
yield e
|
yield e
|
||||||
if e.fatal:
|
if e.fatal:
|
||||||
return
|
return
|
||||||
except MultipleErrors as e:
|
|
||||||
for error in e.errors:
|
|
||||||
yield error
|
|
||||||
if error.fatal:
|
|
||||||
return
|
|
||||||
|
|
||||||
for child in self.children:
|
for child in self.children:
|
||||||
yield from child._get_errors()
|
yield from child._get_errors()
|
||||||
|
@ -254,7 +249,14 @@ def validate(
|
||||||
if skip_incomplete and self.incomplete:
|
if skip_incomplete and self.incomplete:
|
||||||
return
|
return
|
||||||
|
|
||||||
def fill_error(e: CompileError):
|
try:
|
||||||
|
func(self)
|
||||||
|
except CompileError as e:
|
||||||
|
# If the node is only partially complete, then an error must
|
||||||
|
# have already been reported at the parsing stage
|
||||||
|
if self.incomplete:
|
||||||
|
return
|
||||||
|
|
||||||
if e.range is None:
|
if e.range is None:
|
||||||
e.range = (
|
e.range = (
|
||||||
Range.join(
|
Range.join(
|
||||||
|
@ -264,26 +266,8 @@ def validate(
|
||||||
or self.range
|
or self.range
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
|
||||||
func(self)
|
|
||||||
except CompileError as e:
|
|
||||||
# If the node is only partially complete, then an error must
|
|
||||||
# have already been reported at the parsing stage
|
|
||||||
if self.incomplete:
|
|
||||||
return
|
|
||||||
|
|
||||||
fill_error(e)
|
|
||||||
|
|
||||||
# Re-raise the exception
|
# Re-raise the exception
|
||||||
raise e
|
raise e
|
||||||
except MultipleErrors as e:
|
|
||||||
if self.incomplete:
|
|
||||||
return
|
|
||||||
|
|
||||||
for error in e.errors:
|
|
||||||
fill_error(error)
|
|
||||||
|
|
||||||
raise e
|
|
||||||
|
|
||||||
inner._validator = True
|
inner._validator = True
|
||||||
return inner
|
return inner
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
|
import sys
|
||||||
import typing as T
|
import typing as T
|
||||||
|
|
||||||
from . import annotations, gir, language
|
from . import annotations, gir, language
|
||||||
|
@ -30,6 +31,10 @@ from .tokenizer import Token, TokenType
|
||||||
Pattern = T.List[T.Tuple[TokenType, T.Optional[str]]]
|
Pattern = T.List[T.Tuple[TokenType, T.Optional[str]]]
|
||||||
|
|
||||||
|
|
||||||
|
def debug(*args, **kwargs):
|
||||||
|
print(*args, file=sys.stderr, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def _complete(
|
def _complete(
|
||||||
lsp, ast_node: AstNode, tokens: T.List[Token], idx: int, token_idx: int
|
lsp, ast_node: AstNode, tokens: T.List[Token], idx: int, token_idx: int
|
||||||
) -> T.Iterator[Completion]:
|
) -> T.Iterator[Completion]:
|
||||||
|
@ -134,7 +139,7 @@ def gtk_object_completer(lsp, ast_node, match_variables):
|
||||||
matches=new_statement_patterns,
|
matches=new_statement_patterns,
|
||||||
)
|
)
|
||||||
def property_completer(lsp, ast_node, match_variables):
|
def property_completer(lsp, ast_node, match_variables):
|
||||||
if ast_node.gir_class and hasattr(ast_node.gir_class, "properties"):
|
if ast_node.gir_class and not isinstance(ast_node.gir_class, gir.ExternType):
|
||||||
for prop_name, prop in ast_node.gir_class.properties.items():
|
for prop_name, prop in ast_node.gir_class.properties.items():
|
||||||
if (
|
if (
|
||||||
isinstance(prop.type, gir.BoolType)
|
isinstance(prop.type, gir.BoolType)
|
||||||
|
@ -189,7 +194,7 @@ def property_completer(lsp, ast_node, match_variables):
|
||||||
|
|
||||||
|
|
||||||
@completer(
|
@completer(
|
||||||
applies_in=[language.Property, language.A11yProperty],
|
applies_in=[language.Property, language.BaseAttribute],
|
||||||
matches=[[(TokenType.IDENT, None), (TokenType.OP, ":")]],
|
matches=[[(TokenType.IDENT, None), (TokenType.OP, ":")]],
|
||||||
)
|
)
|
||||||
def prop_value_completer(lsp, ast_node, match_variables):
|
def prop_value_completer(lsp, ast_node, match_variables):
|
||||||
|
@ -213,7 +218,7 @@ def prop_value_completer(lsp, ast_node, match_variables):
|
||||||
matches=new_statement_patterns,
|
matches=new_statement_patterns,
|
||||||
)
|
)
|
||||||
def signal_completer(lsp, ast_node, match_variables):
|
def signal_completer(lsp, ast_node, match_variables):
|
||||||
if ast_node.gir_class and hasattr(ast_node.gir_class, "signals"):
|
if ast_node.gir_class and not isinstance(ast_node.gir_class, gir.ExternType):
|
||||||
for signal_name, signal in ast_node.gir_class.signals.items():
|
for signal_name, signal in ast_node.gir_class.signals.items():
|
||||||
if not isinstance(ast_node.parent, language.Object):
|
if not isinstance(ast_node.parent, language.Object):
|
||||||
name = "on"
|
name = "on"
|
||||||
|
|
|
@ -31,6 +31,17 @@ new_statement_patterns = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def applies_to(*ast_types):
|
||||||
|
"""Decorator describing which AST nodes the completer should apply in."""
|
||||||
|
|
||||||
|
def decorator(func):
|
||||||
|
for c in ast_types:
|
||||||
|
c.completers.append(func)
|
||||||
|
return func
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
def completer(applies_in: T.List, matches: T.List = [], applies_in_subclass=None):
|
def completer(applies_in: T.List, matches: T.List = [], applies_in_subclass=None):
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
def inner(prev_tokens: T.List[Token], ast_node, lsp):
|
def inner(prev_tokens: T.List[Token], ast_node, lsp):
|
||||||
|
|
|
@ -20,8 +20,7 @@
|
||||||
import re
|
import re
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
from . import tokenizer
|
from . import tokenizer, utils
|
||||||
from .errors import CompilerBugError
|
|
||||||
from .tokenizer import TokenType
|
from .tokenizer import TokenType
|
||||||
|
|
||||||
OPENING_TOKENS = ("{", "[")
|
OPENING_TOKENS = ("{", "[")
|
||||||
|
@ -193,8 +192,8 @@ def format(data, tab_size=2, insert_space=True):
|
||||||
|
|
||||||
commit_current_line(LineType.COMMENT, newlines_before=newlines)
|
commit_current_line(LineType.COMMENT, newlines_before=newlines)
|
||||||
|
|
||||||
else: # pragma: no cover
|
else:
|
||||||
raise CompilerBugError()
|
commit_current_line()
|
||||||
|
|
||||||
elif str_item == "(" and (
|
elif str_item == "(" and (
|
||||||
re.match(r"^([A-Za-z_\-])+\s*\(", current_line) or watch_parentheses
|
re.match(r"^([A-Za-z_\-])+\s*\(", current_line) or watch_parentheses
|
||||||
|
|
|
@ -467,10 +467,7 @@ class Signature(GirNode):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def return_type(self) -> T.Optional[GirType]:
|
def return_type(self) -> GirType:
|
||||||
if self.tl.SIGNATURE_RETURN_TYPE == 0:
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
return self.get_containing(Repository)._resolve_type_id(
|
return self.get_containing(Repository)._resolve_type_id(
|
||||||
self.tl.SIGNATURE_RETURN_TYPE
|
self.tl.SIGNATURE_RETURN_TYPE
|
||||||
)
|
)
|
||||||
|
@ -493,10 +490,7 @@ class Signal(GirNode):
|
||||||
args = ", ".join(
|
args = ", ".join(
|
||||||
[f"{a.type.full_name} {a.name}" for a in self.gir_signature.args]
|
[f"{a.type.full_name} {a.name}" for a in self.gir_signature.args]
|
||||||
)
|
)
|
||||||
result = f"signal {self.container.full_name}::{self.name} ({args})"
|
return f"signal {self.container.full_name}::{self.name} ({args})"
|
||||||
if self.gir_signature.return_type is not None:
|
|
||||||
result += f" -> {self.gir_signature.return_type.full_name}"
|
|
||||||
return result
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def online_docs(self) -> T.Optional[str]:
|
def online_docs(self) -> T.Optional[str]:
|
||||||
|
@ -908,6 +902,14 @@ class Namespace(GirNode):
|
||||||
if isinstance(entry, Class)
|
if isinstance(entry, Class)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def interfaces(self) -> T.Mapping[str, Interface]:
|
||||||
|
return {
|
||||||
|
name: entry
|
||||||
|
for name, entry in self.entries.items()
|
||||||
|
if isinstance(entry, Interface)
|
||||||
|
}
|
||||||
|
|
||||||
def get_type(self, name) -> T.Optional[GirType]:
|
def get_type(self, name) -> T.Optional[GirType]:
|
||||||
"""Gets a type (class, interface, enum, etc.) from this namespace."""
|
"""Gets a type (class, interface, enum, etc.) from this namespace."""
|
||||||
return self.entries.get(name)
|
return self.entries.get(name)
|
||||||
|
@ -931,8 +933,13 @@ class Namespace(GirNode):
|
||||||
"""Looks up a type in the scope of this namespace (including in the
|
"""Looks up a type in the scope of this namespace (including in the
|
||||||
namespace's dependencies)."""
|
namespace's dependencies)."""
|
||||||
|
|
||||||
|
if type_name in _BASIC_TYPES:
|
||||||
|
return _BASIC_TYPES[type_name]()
|
||||||
|
elif "." in type_name:
|
||||||
ns, name = type_name.split(".", 1)
|
ns, name = type_name.split(".", 1)
|
||||||
return self.get_containing(Repository).get_type(name, ns)
|
return self.get_containing(Repository).get_type(name, ns)
|
||||||
|
else:
|
||||||
|
return self.get_type(type_name)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def online_docs(self) -> T.Optional[str]:
|
def online_docs(self) -> T.Optional[str]:
|
||||||
|
@ -951,7 +958,7 @@ class Repository(GirNode):
|
||||||
self.includes = {
|
self.includes = {
|
||||||
name: get_namespace(name, version) for name, version in deps
|
name: get_namespace(name, version) for name, version in deps
|
||||||
}
|
}
|
||||||
except: # pragma: no cover
|
except:
|
||||||
raise CompilerBugError(f"Failed to load dependencies.")
|
raise CompilerBugError(f"Failed to load dependencies.")
|
||||||
else:
|
else:
|
||||||
self.includes = {}
|
self.includes = {}
|
||||||
|
@ -959,6 +966,12 @@ class Repository(GirNode):
|
||||||
def get_type(self, name: str, ns: str) -> T.Optional[GirType]:
|
def get_type(self, name: str, ns: str) -> T.Optional[GirType]:
|
||||||
return self.lookup_namespace(ns).get_type(name)
|
return self.lookup_namespace(ns).get_type(name)
|
||||||
|
|
||||||
|
def get_type_by_cname(self, name: str) -> T.Optional[GirType]:
|
||||||
|
for ns in [self.namespace, *self.includes.values()]:
|
||||||
|
if type := ns.get_type_by_cname(name):
|
||||||
|
return type
|
||||||
|
return None
|
||||||
|
|
||||||
def lookup_namespace(self, ns: str):
|
def lookup_namespace(self, ns: str):
|
||||||
"""Finds a namespace among this namespace's dependencies."""
|
"""Finds a namespace among this namespace's dependencies."""
|
||||||
if ns == self.namespace.name:
|
if ns == self.namespace.name:
|
||||||
|
|
|
@ -4,6 +4,7 @@ from .adw_breakpoint import (
|
||||||
AdwBreakpointSetters,
|
AdwBreakpointSetters,
|
||||||
)
|
)
|
||||||
from .adw_response_dialog import ExtAdwResponseDialog
|
from .adw_response_dialog import ExtAdwResponseDialog
|
||||||
|
from .attributes import BaseAttribute
|
||||||
from .binding import Binding
|
from .binding import Binding
|
||||||
from .common import *
|
from .common import *
|
||||||
from .contexts import ScopeCtx, ValueTypeCtx
|
from .contexts import ScopeCtx, ValueTypeCtx
|
||||||
|
@ -19,7 +20,7 @@ from .expression import (
|
||||||
from .gobject_object import Object, ObjectContent
|
from .gobject_object import Object, ObjectContent
|
||||||
from .gobject_property import Property
|
from .gobject_property import Property
|
||||||
from .gobject_signal import Signal
|
from .gobject_signal import Signal
|
||||||
from .gtk_a11y import A11yProperty, ExtAccessibility
|
from .gtk_a11y import ExtAccessibility
|
||||||
from .gtk_combo_box_text import ExtComboBoxItems
|
from .gtk_combo_box_text import ExtComboBoxItems
|
||||||
from .gtk_file_filter import (
|
from .gtk_file_filter import (
|
||||||
Filters,
|
Filters,
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
|
|
||||||
from ..decompiler import decompile_translatable, truthy
|
from ..decompiler import decompile_translatable, truthy
|
||||||
from .common import *
|
from .common import *
|
||||||
|
from .contexts import ValueTypeCtx
|
||||||
from .gobject_object import ObjectContent, validate_parent_type
|
from .gobject_object import ObjectContent, validate_parent_type
|
||||||
from .values import StringValue
|
from .values import StringValue
|
||||||
|
|
||||||
|
@ -93,6 +94,10 @@ class ExtAdwResponseDialogResponse(AstNode):
|
||||||
self.value.range.text,
|
self.value.range.text,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@context(ValueTypeCtx)
|
||||||
|
def value_type(self) -> ValueTypeCtx:
|
||||||
|
return ValueTypeCtx(StringType())
|
||||||
|
|
||||||
@validate("id")
|
@validate("id")
|
||||||
def unique_in_parent(self):
|
def unique_in_parent(self):
|
||||||
self.validate_unique_in_parent(
|
self.validate_unique_in_parent(
|
||||||
|
|
32
blueprintcompiler/language/attributes.py
Normal file
32
blueprintcompiler/language/attributes.py
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
# 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 .common import *
|
||||||
|
|
||||||
|
|
||||||
|
class BaseAttribute(AstNode):
|
||||||
|
"""A helper class for attribute syntax of the form `name: literal_value;`"""
|
||||||
|
|
||||||
|
tag_name: str = ""
|
||||||
|
attr_name: str = "name"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return self.tokens["name"]
|
|
@ -38,6 +38,10 @@ class ExprBase(AstNode):
|
||||||
def type(self) -> T.Optional[GirType]:
|
def type(self) -> T.Optional[GirType]:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type_complete(self) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def rhs(self) -> T.Optional["ExprBase"]:
|
def rhs(self) -> T.Optional["ExprBase"]:
|
||||||
if isinstance(self.parent, Expression):
|
if isinstance(self.parent, Expression):
|
||||||
|
@ -61,6 +65,10 @@ class Expression(ExprBase):
|
||||||
def type(self) -> T.Optional[GirType]:
|
def type(self) -> T.Optional[GirType]:
|
||||||
return self.last.type
|
return self.last.type
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type_complete(self) -> bool:
|
||||||
|
return self.last.type_complete
|
||||||
|
|
||||||
|
|
||||||
class InfixExpr(ExprBase):
|
class InfixExpr(ExprBase):
|
||||||
@property
|
@property
|
||||||
|
@ -91,6 +99,15 @@ class LiteralExpr(ExprBase):
|
||||||
def type(self) -> T.Optional[GirType]:
|
def type(self) -> T.Optional[GirType]:
|
||||||
return self.literal.value.type
|
return self.literal.value.type
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type_complete(self) -> bool:
|
||||||
|
from .values import IdentLiteral
|
||||||
|
|
||||||
|
if isinstance(self.literal.value, IdentLiteral):
|
||||||
|
if object := self.context[ScopeCtx].objects.get(self.literal.value.ident):
|
||||||
|
return not object.gir_class.incomplete
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class LookupOp(InfixExpr):
|
class LookupOp(InfixExpr):
|
||||||
grammar = [".", UseIdent("property")]
|
grammar = [".", UseIdent("property")]
|
||||||
|
@ -194,6 +211,10 @@ class CastExpr(InfixExpr):
|
||||||
def type(self) -> T.Optional[GirType]:
|
def type(self) -> T.Optional[GirType]:
|
||||||
return self.children[TypeName][0].gir_type
|
return self.children[TypeName][0].gir_type
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type_complete(self) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
@validate()
|
@validate()
|
||||||
def cast_makes_sense(self):
|
def cast_makes_sense(self):
|
||||||
if self.type is None or self.lhs.type is None:
|
if self.type is None or self.lhs.type is None:
|
||||||
|
|
|
@ -51,7 +51,7 @@ class Property(AstNode):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def document_symbol(self) -> DocumentSymbol:
|
def document_symbol(self) -> DocumentSymbol:
|
||||||
if isinstance(self.value, ObjectValue) or self.value is None:
|
if isinstance(self.value, ObjectValue):
|
||||||
detail = None
|
detail = None
|
||||||
else:
|
else:
|
||||||
detail = self.value.range.text
|
detail = self.value.range.text
|
||||||
|
|
|
@ -122,7 +122,7 @@ class Signal(AstNode):
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_reference(self, idx: int) -> T.Optional[LocationLink]:
|
def get_reference(self, idx: int) -> T.Optional[LocationLink]:
|
||||||
if self.object_id is not None and idx in self.group.tokens["object"].range:
|
if idx in self.group.tokens["object"].range:
|
||||||
obj = self.context[ScopeCtx].objects.get(self.object_id)
|
obj = self.context[ScopeCtx].objects.get(self.object_id)
|
||||||
if obj is not None:
|
if obj is not None:
|
||||||
return LocationLink(
|
return LocationLink(
|
||||||
|
|
|
@ -19,6 +19,8 @@
|
||||||
|
|
||||||
import typing as T
|
import typing as T
|
||||||
|
|
||||||
|
from ..decompiler import escape_quote
|
||||||
|
from .attributes import BaseAttribute
|
||||||
from .common import *
|
from .common import *
|
||||||
from .contexts import ValueTypeCtx
|
from .contexts import ValueTypeCtx
|
||||||
from .gobject_object import ObjectContent, validate_parent_type
|
from .gobject_object import ObjectContent, validate_parent_type
|
||||||
|
@ -117,7 +119,7 @@ def _get_docs(gir, name):
|
||||||
return gir_type.doc
|
return gir_type.doc
|
||||||
|
|
||||||
|
|
||||||
class A11yProperty(AstNode):
|
class A11yProperty(BaseAttribute):
|
||||||
grammar = Statement(
|
grammar = Statement(
|
||||||
UseIdent("name"),
|
UseIdent("name"),
|
||||||
":",
|
":",
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
|
|
||||||
from .common import *
|
from .common import *
|
||||||
|
from .contexts import ValueTypeCtx
|
||||||
from .gobject_object import ObjectContent, validate_parent_type
|
from .gobject_object import ObjectContent, validate_parent_type
|
||||||
from .values import StringValue
|
from .values import StringValue
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ class ExtListItemFactory(AstNode):
|
||||||
else:
|
else:
|
||||||
return self.root.gir.get_type("ListItem", "Gtk")
|
return self.root.gir.get_type("ListItem", "Gtk")
|
||||||
|
|
||||||
@validate("id")
|
@validate("template")
|
||||||
def container_is_builder_list(self):
|
def container_is_builder_list(self):
|
||||||
validate_parent_type(
|
validate_parent_type(
|
||||||
self,
|
self,
|
||||||
|
@ -59,7 +59,7 @@ class ExtListItemFactory(AstNode):
|
||||||
"sub-templates",
|
"sub-templates",
|
||||||
)
|
)
|
||||||
|
|
||||||
@validate("id")
|
@validate("template")
|
||||||
def unique_in_parent(self):
|
def unique_in_parent(self):
|
||||||
self.validate_unique_in_parent("Duplicate template block")
|
self.validate_unique_in_parent("Duplicate template block")
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ class ExtListItemFactory(AstNode):
|
||||||
f"Only Gtk.ListItem, Gtk.ListHeader, Gtk.ColumnViewRow, or Gtk.ColumnViewCell is allowed as a type here"
|
f"Only Gtk.ListItem, Gtk.ListHeader, Gtk.ColumnViewRow, or Gtk.ColumnViewCell is allowed as a type here"
|
||||||
)
|
)
|
||||||
|
|
||||||
@validate("id")
|
@validate("template")
|
||||||
def type_name_upgrade(self):
|
def type_name_upgrade(self):
|
||||||
if self.type_name is None:
|
if self.type_name is None:
|
||||||
raise UpgradeWarning(
|
raise UpgradeWarning(
|
||||||
|
@ -103,7 +103,10 @@ class ExtListItemFactory(AstNode):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def action_widgets(self):
|
def action_widgets(self):
|
||||||
# The sub-template shouldn't have its own actions, this is just here to satisfy XmlOutput._emit_object_or_template
|
"""
|
||||||
|
The sub-template shouldn't have it`s own actions this is
|
||||||
|
just hear to satisfy XmlOutput._emit_object_or_template
|
||||||
|
"""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@docs("id")
|
@docs("id")
|
||||||
|
|
|
@ -59,6 +59,12 @@ class GtkDirective(AstNode):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def gir_namespace(self):
|
def gir_namespace(self):
|
||||||
|
# validate the GTK version first to make sure the more specific error
|
||||||
|
# message is emitted
|
||||||
|
self.gtk_version()
|
||||||
|
if self.tokens["version"] is not None:
|
||||||
|
return gir.get_namespace("Gtk", self.tokens["version"])
|
||||||
|
else:
|
||||||
# For better error handling, just assume it's 4.0
|
# For better error handling, just assume it's 4.0
|
||||||
return gir.get_namespace("Gtk", "4.0")
|
return gir.get_namespace("Gtk", "4.0")
|
||||||
|
|
||||||
|
@ -84,7 +90,7 @@ class Import(AstNode):
|
||||||
|
|
||||||
@validate("namespace", "version")
|
@validate("namespace", "version")
|
||||||
def namespace_exists(self):
|
def namespace_exists(self):
|
||||||
gir.get_namespace(self.namespace, self.version)
|
gir.get_namespace(self.tokens["namespace"], self.tokens["version"])
|
||||||
|
|
||||||
@validate()
|
@validate()
|
||||||
def unused(self):
|
def unused(self):
|
||||||
|
@ -100,7 +106,7 @@ class Import(AstNode):
|
||||||
@property
|
@property
|
||||||
def gir_namespace(self):
|
def gir_namespace(self):
|
||||||
try:
|
try:
|
||||||
return gir.get_namespace(self.namespace, self.version)
|
return gir.get_namespace(self.tokens["namespace"], self.tokens["version"])
|
||||||
except CompileError:
|
except CompileError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
|
@ -389,13 +389,12 @@ class XmlOutput(OutputFormat):
|
||||||
|
|
||||||
elif isinstance(extension, ExtScaleMarks):
|
elif isinstance(extension, ExtScaleMarks):
|
||||||
xml.start_tag("marks")
|
xml.start_tag("marks")
|
||||||
for mark in extension.marks:
|
for mark in extension.children:
|
||||||
label = mark.label.child if mark.label is not None else None
|
|
||||||
xml.start_tag(
|
xml.start_tag(
|
||||||
"mark",
|
"mark",
|
||||||
value=mark.value,
|
value=mark.value,
|
||||||
position=mark.position,
|
position=mark.position,
|
||||||
**self._translated_string_attrs(label),
|
**self._translated_string_attrs(mark.label and mark.label.child),
|
||||||
)
|
)
|
||||||
if mark.label is not None:
|
if mark.label is not None:
|
||||||
xml.put_text(mark.label.string)
|
xml.put_text(mark.label.string)
|
||||||
|
|
|
@ -40,9 +40,7 @@ class XmlEmitter:
|
||||||
self._tag_stack = []
|
self._tag_stack = []
|
||||||
self._needs_newline = False
|
self._needs_newline = False
|
||||||
|
|
||||||
def start_tag(
|
def start_tag(self, tag, **attrs: T.Union[str, GirType, ClassName, bool, None]):
|
||||||
self, tag, **attrs: T.Union[str, GirType, ClassName, bool, None, float]
|
|
||||||
):
|
|
||||||
self._indent()
|
self._indent()
|
||||||
self.result += f"<{tag}"
|
self.result += f"<{tag}"
|
||||||
for key, val in attrs.items():
|
for key, val in attrs.items():
|
||||||
|
|
|
@ -95,11 +95,19 @@ class ParseGroup:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return self.ast_type(self, children, self.keys, incomplete=self.incomplete)
|
return self.ast_type(self, children, self.keys, incomplete=self.incomplete)
|
||||||
except TypeError: # pragma: no cover
|
except TypeError as e:
|
||||||
raise CompilerBugError(
|
raise CompilerBugError(
|
||||||
f"Failed to construct ast.{self.ast_type.__name__} from ParseGroup. See the previous stacktrace."
|
f"Failed to construct ast.{self.ast_type.__name__} from ParseGroup. See the previous stacktrace."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
result = str(self.ast_type.__name__)
|
||||||
|
result += "".join([f"\n{key}: {val}" for key, val in self.keys.items()]) + "\n"
|
||||||
|
result += "\n".join(
|
||||||
|
[str(child) for children in self.children.values() for child in children]
|
||||||
|
)
|
||||||
|
return result.replace("\n", "\n ")
|
||||||
|
|
||||||
|
|
||||||
class ParseContext:
|
class ParseContext:
|
||||||
"""Contains the state of the parser."""
|
"""Contains the state of the parser."""
|
||||||
|
@ -257,6 +265,10 @@ class ParseNode:
|
||||||
"""Convenience method for err()."""
|
"""Convenience method for err()."""
|
||||||
return self.err("Expected " + expect)
|
return self.err("Expected " + expect)
|
||||||
|
|
||||||
|
def warn(self, message) -> "Warning":
|
||||||
|
"""Causes this ParseNode to emit a warning if it parses successfully."""
|
||||||
|
return Warning(self, message)
|
||||||
|
|
||||||
|
|
||||||
class Err(ParseNode):
|
class Err(ParseNode):
|
||||||
"""ParseNode that emits a compile error if it fails to parse."""
|
"""ParseNode that emits a compile error if it fails to parse."""
|
||||||
|
@ -278,6 +290,27 @@ class Err(ParseNode):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class Warning(ParseNode):
|
||||||
|
"""ParseNode that emits a compile warning if it parses successfully."""
|
||||||
|
|
||||||
|
def __init__(self, child, message: str):
|
||||||
|
self.child = to_parse_node(child)
|
||||||
|
self.message = message
|
||||||
|
|
||||||
|
def _parse(self, ctx: ParseContext):
|
||||||
|
ctx.skip()
|
||||||
|
start_idx = ctx.index
|
||||||
|
if self.child.parse(ctx).succeeded():
|
||||||
|
start_token = ctx.tokens[start_idx]
|
||||||
|
end_token = ctx.tokens[ctx.index]
|
||||||
|
ctx.warnings.append(
|
||||||
|
CompileWarning(self.message, start_token.start, end_token.end)
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class Fail(ParseNode):
|
class Fail(ParseNode):
|
||||||
"""ParseNode that emits a compile error if it parses successfully."""
|
"""ParseNode that emits a compile error if it parses successfully."""
|
||||||
|
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
using Gtk 4.0;
|
|
||||||
//comment
|
|
|
@ -1,2 +0,0 @@
|
||||||
using Gtk 4.0;
|
|
||||||
// comment
|
|
|
@ -1,5 +0,0 @@
|
||||||
using Gtk 4.0;
|
|
||||||
|
|
||||||
Entry {
|
|
||||||
margin-bottom: 10.5;
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
4,18,4,Cannot convert 10.5 to integer
|
|
|
@ -1,3 +0,0 @@
|
||||||
using Gtk 4.0;
|
|
||||||
|
|
||||||
int {}
|
|
|
@ -1 +0,0 @@
|
||||||
3,1,3,int is not a class
|
|
|
@ -1,7 +0,0 @@
|
||||||
using Gtk 4.0;
|
|
||||||
|
|
||||||
Overlay {
|
|
||||||
child: my_menu;
|
|
||||||
}
|
|
||||||
|
|
||||||
menu my_menu {}
|
|
|
@ -1 +0,0 @@
|
||||||
4,10,7,Cannot assign Gio.Menu to Gtk.Widget
|
|
|
@ -1,5 +0,0 @@
|
||||||
using Gtk 4.0;
|
|
||||||
|
|
||||||
Entry {
|
|
||||||
margin-bottom: "10";
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
4,18,4,Cannot convert string to number
|
|
|
@ -1,5 +0,0 @@
|
||||||
using Gtk 4.0;
|
|
||||||
|
|
||||||
Button {
|
|
||||||
child: "Click me";
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
4,10,10,Cannot convert string to Gtk.Widget
|
|
|
@ -1,6 +0,0 @@
|
||||||
using Gtk 4.0;
|
|
||||||
using Gio 2.0;
|
|
||||||
|
|
||||||
Gio.ListStore {
|
|
||||||
item-type: "Button";
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
5,14,8,Cannot convert string to GType
|
|
|
@ -1,5 +0,0 @@
|
||||||
using Gtk 4.0;
|
|
||||||
|
|
||||||
Button {
|
|
||||||
child: _("Click me");
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
4,10,13,Cannot convert translated string to Gtk.Widget
|
|
|
@ -1,5 +0,0 @@
|
||||||
using Gtk 4.0;
|
|
||||||
|
|
||||||
Button {
|
|
||||||
label: typeof<Button>;
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
4,10,14,Cannot convert GType to string
|
|
|
@ -1 +0,0 @@
|
||||||
~
|
|
|
@ -1 +0,0 @@
|
||||||
1,1,0,Could not determine what kind of syntax is meant here
|
|
|
@ -1,5 +0,0 @@
|
||||||
using Gtk 4.0;
|
|
||||||
|
|
||||||
Button btn {
|
|
||||||
label: bind btn.label sync-create;
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
4,25,11,'sync-create' is now the default. Use 'no-sync-create' if this is not wanted.
|
|
|
@ -1,5 +0,0 @@
|
||||||
using Gtk 4.0;
|
|
||||||
|
|
||||||
BuilderListItemFactory {
|
|
||||||
template {}
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
4,3,8,Expected type name after 'template' keyword
|
|
|
@ -4,7 +4,6 @@ Box {
|
||||||
visible: bind box2.visible inverted;
|
visible: bind box2.visible inverted;
|
||||||
orientation: bind box2.orientation;
|
orientation: bind box2.orientation;
|
||||||
spacing: bind box2.spacing no-sync-create;
|
spacing: bind box2.spacing no-sync-create;
|
||||||
tooltip-text: bind box2.tooltip-text bidirectional;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Box box2 {
|
Box box2 {
|
||||||
|
|
|
@ -10,7 +10,6 @@ corresponding .blp file and regenerate this file with blueprint-compiler.
|
||||||
<property name="visible" bind-source="box2" bind-property="visible" bind-flags="sync-create|invert-boolean"/>
|
<property name="visible" bind-source="box2" bind-property="visible" bind-flags="sync-create|invert-boolean"/>
|
||||||
<property name="orientation" bind-source="box2" bind-property="orientation" bind-flags="sync-create"/>
|
<property name="orientation" bind-source="box2" bind-property="orientation" bind-flags="sync-create"/>
|
||||||
<property name="spacing" bind-source="box2" bind-property="spacing"/>
|
<property name="spacing" bind-source="box2" bind-property="spacing"/>
|
||||||
<property name="tooltip-text" bind-source="box2" bind-property="tooltip-text" bind-flags="sync-create|bidirectional"/>
|
|
||||||
</object>
|
</object>
|
||||||
<object class="GtkBox" id="box2">
|
<object class="GtkBox" id="box2">
|
||||||
<property name="spacing">6</property>
|
<property name="spacing">6</property>
|
||||||
|
|
11
tests/samples/property_binding_dec.blp
Normal file
11
tests/samples/property_binding_dec.blp
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
|
||||||
|
Box {
|
||||||
|
visible: bind box2.visible inverted;
|
||||||
|
orientation: bind box2.orientation;
|
||||||
|
spacing: bind box2.spacing no-sync-create;
|
||||||
|
}
|
||||||
|
|
||||||
|
Box box2 {
|
||||||
|
spacing: 6;
|
||||||
|
}
|
|
@ -46,4 +46,3 @@ class TestFormatter(unittest.TestCase):
|
||||||
self.assert_format_test("in2.blp", "out.blp")
|
self.assert_format_test("in2.blp", "out.blp")
|
||||||
self.assert_format_test("correct1.blp", "correct1.blp")
|
self.assert_format_test("correct1.blp", "correct1.blp")
|
||||||
self.assert_format_test("string_in.blp", "string_out.blp")
|
self.assert_format_test("string_in.blp", "string_out.blp")
|
||||||
self.assert_format_test("comment_in.blp", "comment_out.blp")
|
|
||||||
|
|
|
@ -28,7 +28,6 @@ gi.require_version("Gtk", "4.0")
|
||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
|
|
||||||
from blueprintcompiler import decompiler, parser, tokenizer, utils
|
from blueprintcompiler import decompiler, parser, tokenizer, utils
|
||||||
from blueprintcompiler.ast_utils import AstNode
|
|
||||||
from blueprintcompiler.completions import complete
|
from blueprintcompiler.completions import complete
|
||||||
from blueprintcompiler.errors import (
|
from blueprintcompiler.errors import (
|
||||||
CompileError,
|
CompileError,
|
||||||
|
@ -62,14 +61,11 @@ class TestSamples(unittest.TestCase):
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def assert_ast_doesnt_crash(self, text, tokens, ast: AstNode):
|
def assert_ast_doesnt_crash(self, text, tokens, ast):
|
||||||
lsp = LanguageServer()
|
|
||||||
for i in range(len(text)):
|
for i in range(len(text)):
|
||||||
ast.get_docs(i)
|
ast.get_docs(i)
|
||||||
for i in range(len(text)):
|
for i in range(len(text)):
|
||||||
list(complete(lsp, ast, tokens, i))
|
list(complete(LanguageServer(), ast, tokens, i))
|
||||||
for i in range(len(text)):
|
|
||||||
ast.get_reference(i)
|
|
||||||
ast.get_document_symbols()
|
ast.get_document_symbols()
|
||||||
|
|
||||||
def assert_sample(self, name, skip_run=False):
|
def assert_sample(self, name, skip_run=False):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue