diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 6d373cc..93ef254 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -3,12 +3,11 @@ stages:
- pages
build:
- image: registry.gitlab.gnome.org/gnome/blueprint-compiler
+ image: registry.gitlab.gnome.org/jwestman/blueprint-compiler
stage: build
script:
- - black --check --diff ./ tests
- - isort --check --diff --profile black ./ tests
- - mypy --python-version=3.9 blueprintcompiler/
+ - black --check --diff blueprintcompiler tests
+ - mypy --python-version=3.9 blueprintcompiler
- G_DEBUG=fatal-warnings xvfb-run coverage run -m unittest
- coverage report
- coverage html
@@ -19,7 +18,7 @@ build:
- ninja -C _build docs/en
- git clone https://gitlab.gnome.org/jwestman/blueprint-regression-tests.git
- cd blueprint-regression-tests
- - git checkout 5f9e155c1333e84e6f683cdb26b02a5925fd8db3
+ - git checkout 3077f669fc9c8e3ceb4da85e6bda680c297c58a2
- ./test.sh
- cd ..
coverage: '/TOTAL.*\s([.\d]+)%/'
@@ -33,7 +32,7 @@ build:
path: coverage.xml
fuzz:
- image: registry.gitlab.gnome.org/gnome/blueprint-compiler
+ image: registry.gitlab.gnome.org/jwestman/blueprint-compiler
stage: build
script:
- meson _build
diff --git a/MAINTENANCE.md b/MAINTENANCE.md
index 3ab4fa2..3f62476 100644
--- a/MAINTENANCE.md
+++ b/MAINTENANCE.md
@@ -5,14 +5,8 @@ in the NEWS file.
2. Update the version number, according to semver:
- At the top of meson.build
- In docs/flatpak.rst
-3. Make a new commit with just these two changes. Use `Release v{version}` as the commit message. Tag the commit as `v{version}` and push the tag.
+3. Make a new commit with just these two changes. Use `Release v{version}`
+as the commit message. Tag the commit as `v{version}` and push the tag.
4. Create a "Post-release version bump" commit.
5. Go to the Releases page in GitLab and create a new release from the tag.
-6. Announce the release through relevant channels (Mastodon, TWIG, etc.)
-
-## Related projects
-
-Blueprint is supported by the following syntax highlighters. If changes are made to the syntax, remember to update these projects as well.
-
-- Pygments (https://github.com/pygments/pygments/blob/master/pygments/lexers/blueprint.py)
-- GtkSourceView (https://gitlab.gnome.org/GNOME/gtksourceview/-/blob/master/data/language-specs/blueprint.lang)
\ No newline at end of file
+6. Announce the release through relevant channels (Twitter, TWIG, etc.)
diff --git a/NEWS.md b/NEWS.md
index a12dab0..544b4a0 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,137 +1,3 @@
-# v0.16.0
-
-## Added
-- Added more "go to reference" implementations in the language server
-- Added semantic token support for flag members in the language server
-- Added property documentation to the hover tooltip for notify signals
-- The language server now shows relevant sections of the reference documentation when hovering over keywords and symbols
-- Added `not-swapped` flag to signal handlers, which may be needed for signal handlers that specify an object
-- Added expression literals, which allow you to specify a Gtk.Expression property (as opposed to the existing expression support, which is for property bindings)
-
-## Changed
-- The formatter adds trailing commas to lists (Alexey Yerin)
-- The formatter removes trailing whitespace from comments (Alexey Yerin)
-- Autocompleting a commonly translated property automatically adds the `_("")` syntax
-- Marking a single-quoted string as translatable now generates a warning, since gettext does not recognize it when using the configuration recommended in the blueprint documentation
-
-## Fixed
-- Added support for libgirepository-2.0 so that blueprint doesn't crash due to import conflicts on newer versions of PyGObject (Jordan Petridis)
-- Fixed a bug when decompiling/porting files with enum values
-- Fixed several issues where tests would fail with versions of GTK that added new deprecations
-- Addressed a problem with the language server protocol in some editors (Luoyayu)
-- Fixed an issue where the compiler would crash instead of reporting compiler errors
-- Fixed a crash in the language server that occurred when a detailed signal (e.g. `notify::*`) was not complete
-- The language server now properly implements the shutdown command, fixing support for some editors and improving robustness when restarting (Alexey Yerin)
-- Marking a string in an array as translatable now generates an error, since it doesn't work
--
-
-## Documentation
-- Added mention of `null` in the Literal Values section
-- Add apps to Built with Blueprint section (Benedek Dévényi, Vladimir Vaskov)
-- Corrected and updated many parts of the documentation
-
-# v0.14.0
-
-## Added
-- Added a warning for unused imports.
-- Added an option to not print the diff when formatting with the CLI. (Gregor Niehl)
-- Added support for building Gtk.ColumnViewRow, Gtk.ColumnViewCell, and Gtk.ListHeader widgets with Gtk.BuilderListItemFactory.
-- Added support for the `after` keyword for signals. This was previously documented but not implemented. (Gregor Niehl)
-- Added support for string arrays. (Diego Augusto)
-- Added hover documentation for properties in lookup expressions.
-- The decompiler supports action widgets, translation domains, `typeof<>` syntax, and expressions. It also supports extension syntax for Adw.Breakpoint, Gtk.BuilderListItemFactory, Gtk.ComboBoxText, Gtk.SizeGroup, and Gtk.StringList.
-- Added a `decompile` subcommand to the CLI, which decompiles an XML .ui file to blueprint.
-- Accessibility relations that allow multiple values are supported using list syntax. (Julian Schmidhuber)
-
-## Changed
-- The decompiler sorts imports alphabetically.
-- Translatable strings use `translatable="yes"` instead of `translatable="true"` for compatibility with xgettext. (Marco Köpcke)
-- The first line of the documentation is shown in the completion list when using the language server. (Sonny Piers)
-- Object autocomplete uses a snippet to add the braces and position the cursor inside them. (Sonny Piers)
-- The carets in the CLI diagnostic output now span the whole error message up to the end of the first line, rather than just the first character.
-- The decompiler emits double quotes, which are compatible with gettext.
-
-## Fixed
-- Fixed deprecation warnings in the language server.
-- The decompiler no longer duplicates translator comments on properties.
-- Subtemplates no longer output a redundant `@generated` comment.
-- When extension syntax from a library that is not available is used, the compiler emits an error instead of crashing.
-- The language server reports semantic token positions correctly. (Szepesi Tibor)
-- The decompiler no longer emits the deprecated `bind-property` syntax. (Sonny Piers)
-- Fixed the tests when used as a Meson subproject. (Benoit Pierre)
-- Signal autocomplete generates correct syntax. (Sonny Piers)
-- The decompiler supports templates that do not specify a parent class. (Sonny Piers)
-- Adw.Breakpoint setters that set a property on the template no longer cause a crash.
-- Fixed type checking with templates that do not have a parent class.
-- Fixed online documentation links for interfaces.
-- The wording of edit suggestions is fixed for insertions and deletions.
-- When an input file uses tabs instead of spaces, the diagnostic output on the CLI aligns the caret correctly.
-- The decompiler emits correct syntax when a property binding refers to the template object.
-
-## Documentation
-- Fixed typos in "Built with Blueprint" section. (Valéry Febvre, Dexter Reed)
-
-# v0.12.0
-
-## Added
-
-- Add support for Adw.AlertDialog (Sonny Piers)
-- Emit warnings for deprecated APIs - lsp and compiler
-- lsp: Document symbols
-- lsp: "Go to definition" (ctrl+click)
-- lsp: Code action for "namespace not imported" diagnostics, that adds the missing import
-- Add a formatter - cli and lsp (Gregor Niehl)
-- Support for translation domain - see documentation
-- cli: Print code actions in error messages
-
-## Changed
-
-- compiler: Add a header notice mentionning the file is generated (Urtsi Santsi)
-- decompiler: Use single quotes for output
-
-## Fixed
-
-- Fixed multine strings support with the escape newline character
-- lsp: Fixed the signal completion, which was missing the "$"
-- lsp: Fixed property value completion (Ivan Kalinin)
-- lsp: Added a missing semantic highlight (for the enum in Gtk.Scale marks)
-- Handle big endian bitfields correctly (Jerry James)
-- batch-compile: Fix mixing relative and absolute paths (Marco Köpcke )
-
-## Documentation
-
-- Fix grammar for bindings
-- Add section on referencing templates
-
-# v0.10.0
-
-## Added
-
-- The hover documentation now includes a link to the online documentation for the symbol, if available.
-- Added hover documentation for the Adw.Breakpoint extensions, `condition` and `setters`.
-
-## Changed
-
-- Decompiling an empty file now produces an empty file rather than an error. (AkshayWarrier)
-- More relevant documentation is shown when hovering over an identifier literal (such as an enum value or an object ID).
-
-## Fixed
-
-- Fixed an issue with the language server not conforming the spec. (seshotake)
-- Fixed the signature section of the hover documentation for properties and signals.
-- Fixed a bug where documentation was sometimes shown for a different symbol with the same name.
-- Fixed a bug where documentation was not shown for accessibility properties that contain `-`.
-- Number literals are now correctly parsed as floats if they contain a `.`, even if they are divisible by 1.
-
-## Removed
-
-- The `bind-property` keyword has been removed. Use `bind` instead. The old syntax is still accepted with a warning.
-
-## Documentation
-
-- Fixed the grammar for Extension, which was missing ExtAdwBreakpoint.
-
-
# v0.8.1
## Breaking Changes
diff --git a/blueprint-compiler.doap b/blueprint-compiler.doap
deleted file mode 100644
index f3e4000..0000000
--- a/blueprint-compiler.doap
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
- Blueprint
- A modern language for creating GTK interfaces
- Blueprint is a language and associated tooling for building user interfaces for GTK.
-
- Python
-
-
-
-
-
-
-
- James Westman
-
- jwestman
-
-
-
diff --git a/blueprint-compiler.py b/blueprint-compiler.py
index 0c5c3fd..61eae79 100755
--- a/blueprint-compiler.py
+++ b/blueprint-compiler.py
@@ -19,8 +19,7 @@
#
# SPDX-License-Identifier: LGPL-3.0-or-later
-import os
-import sys
+import os, sys
# These variables should be set by meson. If they aren't, we're running
# uninstalled, and we might have to guess some values.
diff --git a/blueprintcompiler/annotations.py b/blueprintcompiler/annotations.py
deleted file mode 100644
index c40de13..0000000
--- a/blueprintcompiler/annotations.py
+++ /dev/null
@@ -1,191 +0,0 @@
-# annotations.py
-#
-# Copyright 2024 James Westman
-#
-# 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 .
-#
-# SPDX-License-Identifier: LGPL-3.0-or-later
-
-# Extra information about types in common libraries that's used for things like completions.
-
-import typing as T
-from dataclasses import dataclass
-
-from . import gir
-
-
-@dataclass
-class Annotation:
- translatable_properties: T.List[str]
-
-
-def is_property_translated(property: gir.Property):
- ns = property.get_containing(gir.Namespace)
- ns_name = ns.name + "-" + ns.version
- if annotation := _ANNOTATIONS.get(ns_name):
- assert property.container is not None
- return (
- property.container.name + ":" + property.name
- in annotation.translatable_properties
- )
- else:
- return False
-
-
-_ANNOTATIONS = {
- "Gtk-4.0": Annotation(
- translatable_properties=[
- "AboutDialog:comments",
- "AboutDialog:translator-credits",
- "AboutDialog:website-label",
- "AlertDialog:detail",
- "AlertDialog:message",
- "AppChooserButton:heading",
- "AppChooserDialog:heading",
- "AppChooserWidget:default-text",
- "AssistantPage:title",
- "Button:label",
- "CellRendererText:markup",
- "CellRendererText:placeholder-text",
- "CellRendererText:text",
- "CheckButton:label",
- "ColorButton:title",
- "ColorDialog:title",
- "ColumnViewColumn:title",
- "ColumnViewRow:accessible-description",
- "ColumnViewRow:accessible-label",
- "Entry:placeholder-text",
- "Entry:primary-icon-tooltip-markup",
- "Entry:primary-icon-tooltip-text",
- "Entry:secondary-icon-tooltip-markup",
- "Entry:secondary-icon-tooltip-text",
- "EntryBuffer:text",
- "Expander:label",
- "FileChooserNative:accept-label",
- "FileChooserNative:cancel-label",
- "FileChooserWidget:subtitle",
- "FileDialog:accept-label",
- "FileDialog:title",
- "FileDialog:initial-name",
- "FileFilter:name",
- "FontButton:title",
- "FontDialog:title",
- "Frame:label",
- "Inscription:markup",
- "Inscription:text",
- "Label:label",
- "ListItem:accessible-description",
- "ListItem:accessible-label",
- "LockButton:text-lock",
- "LockButton:text-unlock",
- "LockButton:tooltip-lock",
- "LockButton:tooltip-not-authorized",
- "LockButton:tooltip-unlock",
- "MenuButton:label",
- "MessageDialog:secondary-text",
- "MessageDialog:text",
- "NativeDialog:title",
- "NotebookPage:menu-label",
- "NotebookPage:tab-label",
- "PasswordEntry:placeholder-text",
- "Picture:alternative-text",
- "PrintDialog:accept-label",
- "PrintDialog:title",
- "Printer:name",
- "PrintJob:title",
- "PrintOperation:custom-tab-label",
- "PrintOperation:export-filename",
- "PrintOperation:job-name",
- "ProgressBar:text",
- "SearchEntry:placeholder-text",
- "ShortcutLabel:disabled-text",
- "ShortcutsGroup:title",
- "ShortcutsSection:title",
- "ShortcutsShortcut:title",
- "ShortcutsShortcut:subtitle",
- "StackPage:title",
- "Text:placeholder-text",
- "TextBuffer:text",
- "TreeViewColumn:title",
- "Widget:tooltip-markup",
- "Widget:tooltip-text",
- "Window:title",
- "Editable:text",
- "FontChooser:preview-text",
- ]
- ),
- "Adw-1": Annotation(
- translatable_properties=[
- "AboutDialog:comments",
- "AboutDialog:translator-credits",
- "AboutWindow:comments",
- "AboutWindow:translator-credits",
- "ActionRow:subtitle",
- "ActionRow:title",
- "AlertDialog:body",
- "AlertDialog:heading",
- "Avatar:text",
- "Banner:button-label",
- "Banner:title",
- "ButtonContent:label",
- "Dialog:title",
- "ExpanderRow:subtitle",
- "MessageDialog:body",
- "MessageDialog:heading",
- "NavigationPage:title",
- "PreferencesGroup:description",
- "PreferencesGroup:title",
- "PreferencesPage:description",
- "PreferencesPage:title",
- "PreferencesRow:title",
- "SplitButton:dropdown-tooltip",
- "SplitButton:label",
- "StatusPage:description",
- "StatusPage:title",
- "TabPage:indicator-tooltip",
- "TabPage:keyword",
- "TabPage:title",
- "Toast:button-label",
- "Toast:title",
- "ViewStackPage:title",
- "ViewSwitcherTitle:subtitle",
- "ViewSwitcherTitle:title",
- "WindowTitle:subtitle",
- "WindowTitle:title",
- ]
- ),
- "Shumate-1.0": Annotation(
- translatable_properties=[
- "License:extra-text",
- "MapSource:license",
- "MapSource:name",
- ]
- ),
- "GtkSource-5": Annotation(
- translatable_properties=[
- "CompletionCell:markup",
- "CompletionCell:text",
- "CompletionSnippets:title",
- "CompletionWords:title",
- "GutterRendererText:markup",
- "GutterRendererText:text",
- "SearchSettings:search-text",
- "Snippet:description",
- "Snippet:name",
- "SnippetChunk:tooltip-text",
- "StyleScheme:description",
- "StyleScheme:name",
- ]
- ),
-}
diff --git a/blueprintcompiler/ast_utils.py b/blueprintcompiler/ast_utils.py
index 8f742e0..13f6eb1 100644
--- a/blueprintcompiler/ast_utils.py
+++ b/blueprintcompiler/ast_utils.py
@@ -17,13 +17,12 @@
#
# SPDX-License-Identifier: LGPL-3.0-or-later
-import typing as T
from collections import ChainMap, defaultdict
from functools import cached_property
+import typing as T
from .errors import *
-from .lsp_utils import DocumentSymbol, LocationLink, SemanticToken
-from .tokenizer import Range
+from .lsp_utils import SemanticToken
TType = T.TypeVar("TType")
@@ -38,10 +37,12 @@ class Children:
return iter(self._children)
@T.overload
- def __getitem__(self, key: T.Type[TType]) -> T.List[TType]: ...
+ def __getitem__(self, key: T.Type[TType]) -> T.List[TType]:
+ ...
@T.overload
- def __getitem__(self, key: int) -> "AstNode": ...
+ def __getitem__(self, key: int) -> "AstNode":
+ ...
def __getitem__(self, key):
if isinstance(key, int):
@@ -53,18 +54,6 @@ class Children:
return [child for child in self._children if isinstance(child, key)]
-class Ranges:
- def __init__(self, ranges: T.Dict[str, Range]):
- self._ranges = ranges
-
- def __getitem__(self, key: T.Union[str, tuple[str, str]]) -> T.Optional[Range]:
- if isinstance(key, str):
- return self._ranges.get(key)
- elif isinstance(key, tuple):
- start, end = key
- return Range.join(self._ranges.get(start), self._ranges.get(end))
-
-
TCtx = T.TypeVar("TCtx")
TAttr = T.TypeVar("TAttr")
@@ -113,10 +102,6 @@ class AstNode:
def context(self):
return Ctx(self)
- @cached_property
- def ranges(self):
- return Ranges(self.group.ranges)
-
@cached_property
def root(self):
if self.parent is None:
@@ -124,10 +109,6 @@ class AstNode:
else:
return self.parent.root
- @property
- def range(self) -> Range:
- return Range(self.group.start, self.group.end, self.group.text)
-
def parent_by_type(self, type: T.Type[TType]) -> TType:
if self.parent is None:
raise CompilerBugError()
@@ -160,11 +141,6 @@ class AstNode:
yield e
if e.fatal:
return
- except MultipleErrors as e:
- for error in e.errors:
- yield error
- if error.fatal:
- return
for child in self.children:
yield from child._get_errors()
@@ -184,43 +160,21 @@ class AstNode:
token = self.group.tokens.get(attr.token_name)
if token and token.start <= idx < token.end:
return getattr(self, name)
+ else:
+ return getattr(self, name)
for child in self.children:
- if idx in child.range:
- if docs := child.get_docs(idx):
+ if child.group.start <= idx < child.group.end:
+ docs = child.get_docs(idx)
+ if docs is not None:
return docs
- for name, attr in self._attrs_by_type(Docs):
- if not attr.token_name:
- return getattr(self, name)
-
return None
def get_semantic_tokens(self) -> T.Iterator[SemanticToken]:
for child in self.children:
yield from child.get_semantic_tokens()
- def get_reference(self, idx: int) -> T.Optional[LocationLink]:
- for child in self.children:
- if idx in child.range:
- if ref := child.get_reference(idx):
- return ref
- return None
-
- @property
- def document_symbol(self) -> T.Optional[DocumentSymbol]:
- return None
-
- def get_document_symbols(self) -> T.List[DocumentSymbol]:
- result = []
- for child in self.children:
- if s := child.document_symbol:
- s.children = child.get_document_symbols()
- result.append(s)
- else:
- result.extend(child.get_document_symbols())
- return result
-
def validate_unique_in_parent(
self, error: str, check: T.Optional[T.Callable[["AstNode"], bool]] = None
):
@@ -234,36 +188,23 @@ class AstNode:
error,
references=[
ErrorReference(
- child.range,
+ child.group.start,
+ child.group.end,
"previous declaration was here",
)
],
)
-def validate(
- token_name: T.Optional[str] = None,
- end_token_name: T.Optional[str] = None,
- skip_incomplete: bool = False,
-):
+def validate(token_name=None, end_token_name=None, skip_incomplete=False):
"""Decorator for functions that validate an AST node. Exceptions raised
during validation are marked with range information from the tokens."""
def decorator(func):
- def inner(self: AstNode):
+ def inner(self):
if skip_incomplete and self.incomplete:
return
- def fill_error(e: CompileError):
- if e.range is None:
- e.range = (
- Range.join(
- self.ranges[token_name],
- self.ranges[end_token_name],
- )
- or self.range
- )
-
try:
func(self)
except CompileError as e:
@@ -272,18 +213,25 @@ def validate(
if self.incomplete:
return
- fill_error(e)
+ # This mess of code sets the error's start and end positions
+ # from the tokens passed to the decorator, if they have not
+ # already been set
+ if e.start is None:
+ if token := self.group.tokens.get(token_name):
+ e.start = token.start
+ else:
+ e.start = self.group.start
+
+ if e.end is None:
+ if token := self.group.tokens.get(end_token_name):
+ e.end = token.end
+ elif token := self.group.tokens.get(token_name):
+ e.end = token.end
+ else:
+ e.end = self.group.end
# Re-raise the exception
raise e
- except MultipleErrors as e:
- if self.incomplete:
- return
-
- for error in e.errors:
- fill_error(error)
-
- raise e
inner._validator = True
return inner
diff --git a/blueprintcompiler/completions.py b/blueprintcompiler/completions.py
index b10ec3e..fc6eeee 100644
--- a/blueprintcompiler/completions.py
+++ b/blueprintcompiler/completions.py
@@ -19,25 +19,25 @@
import typing as T
-from . import annotations, gir, language
+from . import gir, language
+from .language.types import ClassName
from .ast_utils import AstNode
from .completions_utils import *
-from .language.types import ClassName
from .lsp_utils import Completion, CompletionItemKind
from .parser import SKIP_TOKENS
-from .tokenizer import Token, TokenType
+from .tokenizer import TokenType, Token
Pattern = T.List[T.Tuple[TokenType, T.Optional[str]]]
def _complete(
- lsp, ast_node: AstNode, tokens: T.List[Token], idx: int, token_idx: int
+ ast_node: AstNode, tokens: T.List[Token], idx: int, token_idx: int
) -> T.Iterator[Completion]:
for child in ast_node.children:
if child.group.start <= idx and (
idx < child.group.end or (idx == child.group.end and child.incomplete)
):
- yield from _complete(lsp, child, tokens, idx, token_idx)
+ yield from _complete(child, tokens, idx, token_idx)
return
prev_tokens: T.List[Token] = []
@@ -50,11 +50,11 @@ def _complete(
token_idx -= 1
for completer in ast_node.completers:
- yield from completer(prev_tokens, ast_node, lsp)
+ yield from completer(prev_tokens, ast_node)
def complete(
- lsp, ast_node: AstNode, tokens: T.List[Token], idx: int
+ ast_node: AstNode, tokens: T.List[Token], idx: int
) -> T.Iterator[Completion]:
token_idx = 0
# find the current token
@@ -67,21 +67,19 @@ def complete(
idx = tokens[token_idx].start
token_idx -= 1
- yield from _complete(lsp, ast_node, tokens, idx, token_idx)
+ yield from _complete(ast_node, tokens, idx, token_idx)
@completer([language.GtkDirective])
-def using_gtk(lsp, ast_node, match_variables):
- yield Completion(
- "using Gtk 4.0", CompletionItemKind.Keyword, snippet="using Gtk 4.0;\n"
- )
+def using_gtk(ast_node, match_variables):
+ yield Completion("using Gtk 4.0;", CompletionItemKind.Keyword)
@completer(
applies_in=[language.UI, language.ObjectContent, language.Template],
matches=new_statement_patterns,
)
-def namespace(lsp, ast_node, match_variables):
+def namespace(ast_node, match_variables):
yield Completion("Gtk", CompletionItemKind.Module, text="Gtk.")
for ns in ast_node.root.children[language.Import]:
if ns.gir_namespace is not None:
@@ -99,131 +97,55 @@ def namespace(lsp, ast_node, match_variables):
[(TokenType.IDENT, None), (TokenType.OP, ".")],
],
)
-def object_completer(lsp, ast_node, match_variables):
+def object_completer(ast_node, match_variables):
ns = ast_node.root.gir.namespaces.get(match_variables[0])
if ns is not None:
for c in ns.classes.values():
- yield Completion(
- c.name,
- CompletionItemKind.Class,
- snippet=f"{c.name} {{\n $0\n}}",
- docs=c.doc,
- detail=c.detail,
- )
+ yield Completion(c.name, CompletionItemKind.Class, docs=c.doc)
@completer(
applies_in=[language.UI, language.ObjectContent, language.Template],
matches=new_statement_patterns,
)
-def gtk_object_completer(lsp, ast_node, match_variables):
+def gtk_object_completer(ast_node, match_variables):
ns = ast_node.root.gir.namespaces.get("Gtk")
if ns is not None:
for c in ns.classes.values():
- yield Completion(
- c.name,
- CompletionItemKind.Class,
- snippet=f"{c.name} {{\n $0\n}}",
- docs=c.doc,
- detail=c.detail,
- )
+ yield Completion(c.name, CompletionItemKind.Class, docs=c.doc)
@completer(
applies_in=[language.ObjectContent],
matches=new_statement_patterns,
)
-def property_completer(lsp, ast_node, match_variables):
- if ast_node.gir_class and hasattr(ast_node.gir_class, "properties"):
- for prop_name, prop in ast_node.gir_class.properties.items():
- if (
- isinstance(prop.type, gir.BoolType)
- and lsp.client_supports_completion_choice
- ):
- yield Completion(
- prop_name,
- CompletionItemKind.Property,
- sort_text=f"0 {prop_name}",
- snippet=f"{prop_name}: ${{1|true,false|}};",
- docs=prop.doc,
- detail=prop.detail,
- )
- elif isinstance(prop.type, gir.StringType):
- snippet = (
- f'{prop_name}: _("$0");'
- if annotations.is_property_translated(prop)
- else f'{prop_name}: "$0";'
- )
-
- yield Completion(
- prop_name,
- CompletionItemKind.Property,
- sort_text=f"0 {prop_name}",
- snippet=snippet,
- docs=prop.doc,
- detail=prop.detail,
- )
- elif (
- isinstance(prop.type, gir.Enumeration)
- and len(prop.type.members) <= 10
- and lsp.client_supports_completion_choice
- ):
- choices = ",".join(prop.type.members.keys())
- yield Completion(
- prop_name,
- CompletionItemKind.Property,
- sort_text=f"0 {prop_name}",
- snippet=f"{prop_name}: ${{1|{choices}|}};",
- docs=prop.doc,
- detail=prop.detail,
- )
- elif prop.type.full_name == "Gtk.Expression":
- yield Completion(
- prop_name,
- CompletionItemKind.Property,
- sort_text=f"0 {prop_name}",
- snippet=f"{prop_name}: expr $0;",
- docs=prop.doc,
- detail=prop.detail,
- )
- else:
- yield Completion(
- prop_name,
- CompletionItemKind.Property,
- sort_text=f"0 {prop_name}",
- snippet=f"{prop_name}: $0;",
- docs=prop.doc,
- detail=prop.detail,
- )
+def property_completer(ast_node, match_variables):
+ if ast_node.gir_class and not isinstance(ast_node.gir_class, gir.ExternType):
+ for prop in ast_node.gir_class.properties:
+ yield Completion(prop, CompletionItemKind.Property, snippet=f"{prop}: $0;")
@completer(
- applies_in=[language.Property, language.A11yProperty],
+ applies_in=[language.Property, language.BaseAttribute],
matches=[[(TokenType.IDENT, None), (TokenType.OP, ":")]],
)
-def prop_value_completer(lsp, ast_node, match_variables):
- if (vt := ast_node.value_type) is not None:
- if isinstance(vt.value_type, gir.Enumeration):
- for name, member in vt.value_type.members.items():
- yield Completion(
- name,
- CompletionItemKind.EnumMember,
- docs=member.doc,
- detail=member.detail,
- )
+def prop_value_completer(ast_node, match_variables):
+ if isinstance(ast_node.value_type, gir.Enumeration):
+ for name, member in ast_node.value_type.members.items():
+ yield Completion(name, CompletionItemKind.EnumMember, docs=member.doc)
- elif isinstance(vt.value_type, gir.BoolType):
- yield Completion("true", CompletionItemKind.Constant)
- yield Completion("false", CompletionItemKind.Constant)
+ elif isinstance(ast_node.value_type, gir.BoolType):
+ yield Completion("true", CompletionItemKind.Constant)
+ yield Completion("false", CompletionItemKind.Constant)
@completer(
applies_in=[language.ObjectContent],
matches=new_statement_patterns,
)
-def signal_completer(lsp, ast_node, match_variables):
- if ast_node.gir_class and hasattr(ast_node.gir_class, "signals"):
- for signal_name, signal in ast_node.gir_class.signals.items():
+def signal_completer(ast_node, match_variables):
+ if ast_node.gir_class and not isinstance(ast_node.gir_class, gir.ExternType):
+ for signal in ast_node.gir_class.signals:
if not isinstance(ast_node.parent, language.Object):
name = "on"
else:
@@ -234,17 +156,14 @@ def signal_completer(lsp, ast_node, match_variables):
.lower()
)
yield Completion(
- signal_name,
- CompletionItemKind.Event,
- sort_text=f"1 {signal_name}",
- snippet=f"{signal_name} => \\$${{1:${name}_{signal_name.replace('-', '_')}}}()$0;",
- docs=signal.doc,
- detail=signal.detail,
+ signal,
+ CompletionItemKind.Property,
+ snippet=f"{signal} => ${{1:{name}_{signal.replace('-', '_')}}}()$0;",
)
@completer(applies_in=[language.UI], matches=new_statement_patterns)
-def template_completer(lsp, ast_node, match_variables):
+def template_completer(ast_node, match_variables):
yield Completion(
"template",
CompletionItemKind.Snippet,
diff --git a/blueprintcompiler/completions_utils.py b/blueprintcompiler/completions_utils.py
index eccf125..094e449 100644
--- a/blueprintcompiler/completions_utils.py
+++ b/blueprintcompiler/completions_utils.py
@@ -20,8 +20,9 @@
import typing as T
-from .lsp_utils import Completion
from .tokenizer import Token, TokenType
+from .lsp_utils import Completion
+
new_statement_patterns = [
[(TokenType.PUNCTUATION, "{")],
@@ -31,16 +32,27 @@ 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 decorator(func):
- def inner(prev_tokens: T.List[Token], ast_node, lsp):
+ def inner(prev_tokens: T.List[Token], ast_node):
# For completers that apply in ObjectContent nodes, we can further
# check that the object is the right class
if applies_in_subclass is not None:
type = ast_node.root.gir.get_type(
applies_in_subclass[1], applies_in_subclass[0]
)
- if not ast_node.gir_class or not ast_node.gir_class.assignable_to(type):
+ if ast_node.gir_class and not ast_node.gir_class.assignable_to(type):
return
any_match = len(matches) == 0
@@ -66,7 +78,7 @@ def completer(applies_in: T.List, matches: T.List = [], applies_in_subclass=None
if not any_match:
return
- yield from func(lsp, ast_node, match_variables)
+ yield from func(ast_node, match_variables)
for c in applies_in:
c.completers.append(inner)
diff --git a/blueprintcompiler/decompiler.py b/blueprintcompiler/decompiler.py
index 850b6d8..d3bf773 100644
--- a/blueprintcompiler/decompiler.py
+++ b/blueprintcompiler/decompiler.py
@@ -17,20 +17,20 @@
#
# SPDX-License-Identifier: LGPL-3.0-or-later
-import typing as T
-from collections import defaultdict
-from dataclasses import dataclass
+import re
from enum import Enum
+import typing as T
+from dataclasses import dataclass
-from . import formatter
-from .gir import *
-from .utils import Colors, escape_quote
from .xml_reader import Element, parse, parse_string
+from .gir import *
+from .utils import Colors
+
__all__ = ["decompile"]
-_DECOMPILERS: dict[str, list] = defaultdict(list)
+_DECOMPILERS: T.Dict = {}
_CLOSING = {
"{": "}",
"[": "]",
@@ -51,32 +51,25 @@ class LineType(Enum):
class DecompileCtx:
- def __init__(self, parent_gir: T.Optional[GirContext] = None) -> None:
- self.sub_decompiler = parent_gir is not None
+ def __init__(self) -> None:
self._result: str = ""
- self.gir = parent_gir or GirContext()
+ self.gir = GirContext()
+ self._indent: int = 0
self._blocks_need_end: T.List[str] = []
self._last_line_type: LineType = LineType.NONE
- self._obj_type_stack: list[T.Optional[GirType]] = []
- self._node_stack: list[Element] = []
+ self.template_class: T.Optional[str] = None
self.gir.add_namespace(get_namespace("Gtk", "4.0"))
@property
def result(self) -> str:
- imports = ""
-
- if not self.sub_decompiler:
- import_lines = sorted(
- [
- f"using {ns} {namespace.version};"
- for ns, namespace in self.gir.namespaces.items()
- if ns != "Gtk"
- ]
- )
- imports += "\n".join(["using Gtk 4.0;", *import_lines])
-
- return formatter.format(imports + self._result)
+ imports = "\n".join(
+ [
+ f"using {ns} {namespace.version};"
+ for ns, namespace in self.gir.namespaces.items()
+ ]
+ )
+ return imports + "\n" + self._result
def type_by_cname(self, cname: str) -> T.Optional[GirType]:
if type := self.gir.get_type_by_cname(cname):
@@ -95,86 +88,46 @@ class DecompileCtx:
def start_block(self) -> None:
self._blocks_need_end.append("")
- self._obj_type_stack.append(None)
def end_block(self) -> None:
if close := self._blocks_need_end.pop():
self.print(close)
- self._obj_type_stack.pop()
-
- @property
- def current_obj_type(self) -> T.Optional[GirType]:
- return next((x for x in reversed(self._obj_type_stack) if x is not None), None)
-
- def push_obj_type(self, type: T.Optional[GirType]) -> None:
- self._obj_type_stack[-1] = type
-
- @property
- def current_node(self) -> T.Optional[Element]:
- if len(self._node_stack) == 0:
- return None
- else:
- return self._node_stack[-1]
-
- @property
- def parent_node(self) -> T.Optional[Element]:
- if len(self._node_stack) < 2:
- return None
- else:
- return self._node_stack[-2]
-
- @property
- def root_node(self) -> T.Optional[Element]:
- if len(self._node_stack) == 0:
- return None
- else:
- return self._node_stack[0]
-
- @property
- def template_class(self) -> T.Optional[str]:
- assert self.root_node is not None
- for child in self.root_node.children:
- if child.tag == "template":
- return child["class"]
-
- return None
-
- def find_object(self, id: str) -> T.Optional[Element]:
- assert self.root_node is not None
- for child in self.root_node.children:
- if child.tag == "template" and child["class"] == id:
- return child
-
- def find_in_children(node: Element) -> T.Optional[Element]:
- if node.tag in ["object", "menu"] and node["id"] == id:
- return node
- else:
- for child in node.children:
- if result := find_in_children(child):
- return result
- return None
-
- return find_in_children(self.root_node)
def end_block_with(self, text: str) -> None:
self._blocks_need_end[-1] = text
def print(self, line: str, newline: bool = True) -> None:
- self._result += line
+ if line == "}" or line == "]":
+ self._indent -= 1
+
+ # Add blank lines between different types of lines, for neatness
+ if newline:
+ if line == "}" or line == "]":
+ line_type = LineType.BLOCK_END
+ elif line.endswith("{") or line.endswith("]"):
+ line_type = LineType.BLOCK_START
+ elif line.endswith(";"):
+ line_type = LineType.STMT
+ else:
+ line_type = LineType.NONE
+ if (
+ line_type != self._last_line_type
+ and self._last_line_type != LineType.BLOCK_START
+ and line_type != LineType.BLOCK_END
+ ):
+ self._result += "\n"
+ self._last_line_type = line_type
+
+ self._result += (" " * self._indent) + line
+ if newline:
+ self._result += "\n"
if line.endswith("{") or line.endswith("["):
if len(self._blocks_need_end):
self._blocks_need_end[-1] = _CLOSING[line[-1]]
+ self._indent += 1
- # Converts a value from an XML element to a blueprint string
- # based on the given type. Returns a tuple of translator comments
- # (if any) and the decompiled syntax.
- def decompile_value(
- self,
- value: str,
- type: T.Optional[GirType],
- translatable: T.Optional[T.Tuple[str, str, str]] = None,
- ) -> T.Tuple[str, str]:
+ def print_attribute(self, name: str, value: str, type: GirType) -> None:
def get_enum_name(value):
for member in type.members.values():
if (
@@ -185,18 +138,13 @@ class DecompileCtx:
return member.name
return value.replace("-", "_")
- if translatable is not None and truthy(translatable[0]):
- return decompile_translatable(value, *translatable)
- elif type is None:
- return "", f"{escape_quote(value)}"
+ if type is None:
+ self.print(f'{name}: "{escape_quote(value)}";')
elif type.assignable_to(FloatType()):
- return "", str(value)
+ self.print(f"{name}: {value};")
elif type.assignable_to(BoolType()):
val = truthy(value)
- return "", ("true" if val else "false")
- elif type.assignable_to(ArrayType(StringType())):
- items = ", ".join([escape_quote(x) for x in value.split("\n")])
- return "", f"[{items}]"
+ self.print(f"{name}: {'true' if val else 'false'};")
elif (
type.assignable_to(self.gir.namespaces["Gtk"].lookup_type("Gdk.Pixbuf"))
or type.assignable_to(self.gir.namespaces["Gtk"].lookup_type("Gdk.Texture"))
@@ -210,82 +158,67 @@ class DecompileCtx:
self.gir.namespaces["Gtk"].lookup_type("Gtk.ShortcutTrigger")
)
):
- return "", escape_quote(value)
+ self.print(f'{name}: "{escape_quote(value)}";')
elif value == self.template_class:
- return "", "template"
+ self.print(f"{name}: template;")
elif type.assignable_to(
self.gir.namespaces["Gtk"].lookup_type("GObject.Object")
- ) or isinstance(type, Interface):
- return "", ("null" if value == "" else value)
+ ):
+ self.print(f"{name}: {value};")
elif isinstance(type, Bitfield):
flags = [get_enum_name(flag) for flag in value.split("|")]
- return "", " | ".join(flags)
+ self.print(f"{name}: {' | '.join(flags)};")
elif isinstance(type, Enumeration):
- return "", get_enum_name(value)
- elif isinstance(type, TypeType):
- if t := self.type_by_cname(value):
- return "", f"typeof<{full_name(t)}>"
- else:
- return "", f"typeof<${value}>"
+ self.print(f"{name}: {get_enum_name(value)};")
else:
- return "", escape_quote(value)
+ self.print(f'{name}: "{escape_quote(value)}";')
-def decompile_element(
+def _decompile_element(
ctx: DecompileCtx, gir: T.Optional[GirContext], xml: Element
) -> None:
try:
- decompilers = [d for d in _DECOMPILERS[xml.tag] if d._filter(ctx)]
- if len(decompilers) == 0:
+ decompiler = _DECOMPILERS.get(xml.tag)
+ if decompiler is None:
raise UnsupportedError(f"unsupported XML tag: <{xml.tag}>")
- decompiler = decompilers[0]
+ args: T.Dict[str, T.Optional[str]] = {
+ canon(name): value for name, value in xml.attrs.items()
+ }
+ if decompiler._cdata:
+ if len(xml.children):
+ args["cdata"] = None
+ else:
+ args["cdata"] = xml.cdata
- if decompiler._element:
- args = [ctx, gir, xml]
- kwargs: T.Dict[str, T.Optional[str]] = {}
- else:
- args = [ctx, gir]
- kwargs = {canon(name): value for name, value in xml.attrs.items()}
- if decompiler._cdata:
- if len(xml.children):
- kwargs["cdata"] = None
- else:
- kwargs["cdata"] = xml.cdata
-
- ctx._node_stack.append(xml)
ctx.start_block()
+ gir = decompiler(ctx, gir, **args)
- try:
- gir = decompiler(*args, **kwargs)
- except TypeError as e:
- raise UnsupportedError(tag=xml.tag)
-
- if not decompiler._skip_children:
- for child in xml.children:
- decompile_element(ctx, gir, child)
+ for child in xml.children:
+ _decompile_element(ctx, gir, child)
ctx.end_block()
- ctx._node_stack.pop()
except UnsupportedError as e:
raise e
+ except TypeError as e:
+ raise UnsupportedError(tag=xml.tag)
def decompile(data: str) -> str:
ctx = DecompileCtx()
xml = parse(data)
- decompile_element(ctx, None, xml)
+ _decompile_element(ctx, None, xml)
return ctx.result
-def decompile_string(data: str) -> str:
+def decompile_string(data):
ctx = DecompileCtx()
xml = parse_string(data)
- decompile_element(ctx, None, xml)
+ _decompile_element(ctx, None, xml)
return ctx.result
@@ -298,10 +231,10 @@ def canon(string: str) -> str:
def truthy(string: str) -> bool:
- return string is not None and string.lower() in ["yes", "true", "t", "y", "1"]
+ return string.lower() in ["yes", "true", "t", "y", "1"]
-def full_name(gir: GirType) -> str:
+def full_name(gir) -> str:
return gir.name if gir.full_name.startswith("Gtk.") else gir.full_name
@@ -312,45 +245,26 @@ def lookup_by_cname(gir, cname: str) -> T.Optional[GirType]:
return gir.get_containing(Repository).get_type_by_cname(cname)
-def decompiler(
- tag,
- cdata=False,
- parent_type: T.Optional[str] = None,
- parent_tag: T.Optional[str] = None,
- skip_children=False,
- element=False,
-):
+def decompiler(tag, cdata=False):
def decorator(func):
func._cdata = cdata
- func._skip_children = skip_children
- func._element = element
-
- def filter(ctx):
- if parent_type is not None:
- if (
- ctx.current_obj_type is None
- or ctx.current_obj_type.full_name != parent_type
- ):
- return False
-
- if parent_tag is not None:
- if not any(x.tag == parent_tag for x in ctx._node_stack):
- return False
-
- return True
-
- func._filter = filter
-
- _DECOMPILERS[tag].append(func)
+ _DECOMPILERS[tag] = func
return func
return decorator
+def escape_quote(string: str) -> str:
+ return (
+ string.replace("\\", "\\\\")
+ .replace("'", "\\'")
+ .replace('"', '\\"')
+ .replace("\n", "\\n")
+ )
+
+
@decompiler("interface")
-def decompile_interface(ctx, gir, domain=None):
- if domain is not None:
- ctx.print(f"translation-domain {escape_quote(domain)};")
+def decompile_interface(ctx, gir):
return gir
@@ -369,20 +283,18 @@ def decompile_translatable(
translatable: T.Optional[str],
context: T.Optional[str],
comments: T.Optional[str],
-) -> T.Tuple[str, str]:
+) -> T.Tuple[T.Optional[str], str]:
if translatable is not None and truthy(translatable):
- if comments is None:
- comments = ""
- else:
+ if comments is not None:
comments = comments.replace("/*", " ").replace("*/", " ")
comments = f"/* Translators: {comments} */"
if context is not None:
- return comments, f"C_({escape_quote(context)}, {escape_quote(string)})"
+ return comments, f'C_("{escape_quote(context)}", "{escape_quote(string)}")'
else:
- return comments, f"_({escape_quote(string)})"
+ return comments, f'_("{escape_quote(string)}")'
else:
- return "", f"{escape_quote(string)}"
+ return comments, f'"{escape_quote(string)}"'
@decompiler("property", cdata=True)
@@ -399,8 +311,11 @@ def decompile_property(
context=None,
):
name = name.replace("_", "-")
+ if comments is not None:
+ ctx.print(f"/* Translators: {comments} */")
+
if cdata is None:
- ctx.print(f"{name}: ")
+ ctx.print(f"{name}: ", False)
ctx.end_block_with(";")
elif bind_source:
flags = ""
@@ -411,11 +326,7 @@ def decompile_property(
flags += " inverted"
if "bidirectional" in bind_flags:
flags += " bidirectional"
-
- if bind_source == ctx.template_class:
- bind_source = "template"
-
- ctx.print(f"{name}: bind {bind_source}.{bind_property}{flags};")
+ ctx.print(f"{name}: bind-property {bind_source}.{bind_property}{flags};")
elif truthy(translatable):
comments, translatable = decompile_translatable(
cdata, translatable, context, comments
@@ -424,20 +335,9 @@ def decompile_property(
ctx.print(comments)
ctx.print(f"{name}: {translatable};")
elif gir is None or gir.properties.get(name) is None:
- ctx.print(f"{name}: {escape_quote(cdata)};")
- elif (
- gir.assignable_to(ctx.gir.get_class("BuilderListItemFactory", "Gtk"))
- and name == "bytes"
- ):
- sub_ctx = DecompileCtx(ctx.gir)
-
- xml = parse_string(cdata)
- decompile_element(sub_ctx, None, xml)
-
- ctx.print(sub_ctx.result)
+ ctx.print(f'{name}: "{escape_quote(cdata)}";')
else:
- _, string = ctx.decompile_value(cdata, gir.properties.get(name).type)
- ctx.print(f"{name}: {string};")
+ ctx.print_attribute(name, cdata, gir.properties.get(name).type)
return gir
diff --git a/blueprintcompiler/errors.py b/blueprintcompiler/errors.py
index df1c2e1..3fc2666 100644
--- a/blueprintcompiler/errors.py
+++ b/blueprintcompiler/errors.py
@@ -17,13 +17,10 @@
#
# SPDX-License-Identifier: LGPL-3.0-or-later
-import sys
-import traceback
-import typing as T
from dataclasses import dataclass
-
+import typing as T
+import sys, traceback
from . import utils
-from .tokenizer import Range
from .utils import Colors
@@ -37,7 +34,8 @@ class PrintableError(Exception):
@dataclass
class ErrorReference:
- range: Range
+ start: int
+ end: int
message: str
@@ -50,7 +48,8 @@ class CompileError(PrintableError):
def __init__(
self,
message: str,
- range: T.Optional[Range] = None,
+ start: T.Optional[int] = None,
+ end: T.Optional[int] = None,
did_you_mean: T.Optional[T.Tuple[str, T.List[str]]] = None,
hints: T.Optional[T.List[str]] = None,
actions: T.Optional[T.List["CodeAction"]] = None,
@@ -60,7 +59,8 @@ class CompileError(PrintableError):
super().__init__(message)
self.message = message
- self.range = range
+ self.start = start
+ self.end = end
self.hints = hints or []
self.actions = actions or []
self.references = references or []
@@ -90,56 +90,25 @@ class CompileError(PrintableError):
self.hint("Are your dependencies up to date?")
def pretty_print(self, filename: str, code: str, stream=sys.stdout) -> None:
- assert self.range is not None
+ assert self.start is not None
- line_num, col_num = utils.idx_to_pos(self.range.start + 1, code)
- end_line_num, end_col_num = utils.idx_to_pos(self.range.end + 1, code)
- line = code.splitlines(True)[line_num] if code != "" else ""
+ line_num, col_num = utils.idx_to_pos(self.start + 1, code)
+ line = code.splitlines(True)[line_num]
# Display 1-based line numbers
line_num += 1
- end_line_num += 1
-
- n_spaces = col_num - 1
- n_carets = (
- (end_col_num - col_num)
- if line_num == end_line_num
- else (len(line) - n_spaces - 1)
- )
-
- n_spaces += line.count("\t", 0, col_num)
- n_carets += line.count("\t", col_num, col_num + n_carets)
- line = line.replace("\t", " ")
stream.write(
f"""{self.color}{Colors.BOLD}{self.category}: {self.message}{Colors.CLEAR}
at {filename} line {line_num} column {col_num}:
-{Colors.FAINT}{line_num :>4} |{Colors.CLEAR}{line.rstrip()}\n {Colors.FAINT}|{" "*n_spaces}{"^"*n_carets}{Colors.CLEAR}\n"""
+{Colors.FAINT}{line_num :>4} |{Colors.CLEAR}{line.rstrip()}\n {Colors.FAINT}|{" "*(col_num-1)}^{Colors.CLEAR}\n"""
)
for hint in self.hints:
stream.write(f"{Colors.FAINT}hint: {hint}{Colors.CLEAR}\n")
- for i, action in enumerate(self.actions):
- old = (
- action.edit_range.text
- if action.edit_range is not None
- else self.range.text
- )
-
- if old == "":
- stream.write(
- f"suggestion: insert {Colors.GREEN}{action.replace_with}{Colors.CLEAR}\n"
- )
- elif action.replace_with == "":
- stream.write(f"suggestion: remove {Colors.RED}{old}{Colors.CLEAR}\n")
- else:
- stream.write(
- f"suggestion: replace {Colors.RED}{old}{Colors.CLEAR} with {Colors.GREEN}{action.replace_with}{Colors.CLEAR}\n"
- )
-
for ref in self.references:
- line_num, col_num = utils.idx_to_pos(ref.range.start + 1, code)
+ line_num, col_num = utils.idx_to_pos(ref.start + 1, code)
line = code.splitlines(True)[line_num]
line_num += 1
@@ -157,29 +126,20 @@ class CompileWarning(CompileError):
color = Colors.YELLOW
-class DeprecatedWarning(CompileWarning):
- pass
-
-
-class UnusedWarning(CompileWarning):
- pass
-
-
class UpgradeWarning(CompileWarning):
category = "upgrade"
color = Colors.PURPLE
class UnexpectedTokenError(CompileError):
- def __init__(self, range: Range) -> None:
- super().__init__("Unexpected tokens", range)
+ def __init__(self, start, end) -> None:
+ super().__init__("Unexpected tokens", start, end)
@dataclass
class CodeAction:
title: str
replace_with: str
- edit_range: T.Optional[Range] = None
class MultipleErrors(PrintableError):
@@ -219,7 +179,7 @@ def report_bug(): # pragma: no cover
f"""{Colors.BOLD}{Colors.RED}***** COMPILER BUG *****
The blueprint-compiler program has crashed. Please report the above stacktrace,
along with the input file(s) if possible, on GitLab:
-{Colors.BOLD}{Colors.BLUE}{Colors.UNDERLINE}https://gitlab.gnome.org/GNOME/blueprint-compiler/-/issues/new?issue
+{Colors.BOLD}{Colors.BLUE}{Colors.UNDERLINE}https://gitlab.gnome.org/jwestman/blueprint-compiler/-/issues/new?issue
{Colors.CLEAR}"""
)
diff --git a/blueprintcompiler/formatter.py b/blueprintcompiler/formatter.py
deleted file mode 100644
index f438675..0000000
--- a/blueprintcompiler/formatter.py
+++ /dev/null
@@ -1,232 +0,0 @@
-# formatter.py
-#
-# Copyright 2023 Gregor Niehl
-#
-# 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 .
-#
-# SPDX-License-Identifier: LGPL-3.0-or-later
-
-import re
-from enum import Enum
-
-from . import tokenizer
-from .errors import CompilerBugError
-from .tokenizer import TokenType
-
-OPENING_TOKENS = ("{", "[")
-CLOSING_TOKENS = ("}", "]")
-
-NEWLINE_AFTER = tuple(";") + OPENING_TOKENS + CLOSING_TOKENS
-
-NO_WHITESPACE_BEFORE = (",", ":", "::", ";", ")", ".", ">", "]", "=")
-NO_WHITESPACE_AFTER = ("C_", "_", "(", ".", "$", "<", "::", "[", "=")
-
-# NO_WHITESPACE_BEFORE takes precedence over WHITESPACE_AFTER
-WHITESPACE_AFTER = (":", ",", ">", ")", "|", "=>")
-WHITESPACE_BEFORE = ("{", "|")
-
-
-class LineType(Enum):
- STATEMENT = 0
- BLOCK_OPEN = 1
- BLOCK_CLOSE = 2
- CHILD_TYPE = 3
- COMMENT = 4
-
-
-def format(data, tab_size=2, insert_space=True):
- indent_levels = 0
- tokens = tokenizer.tokenize(data)
- end_str = ""
- last_not_whitespace = tokens[0]
- current_line = ""
- prev_line_type = None
- is_child_type = False
- indent_item = " " * tab_size if insert_space else "\t"
- watch_parentheses = False
- parentheses_balance = 0
- bracket_tracker = [None]
- last_whitespace_contains_newline = False
-
- def commit_current_line(
- line_type=prev_line_type, redo_whitespace=False, newlines_before=1
- ):
- nonlocal end_str, current_line, prev_line_type
-
- indent_whitespace = indent_levels * indent_item
- whitespace_to_add = "\n" + indent_whitespace
-
- if redo_whitespace or newlines_before != 1:
- end_str = end_str.strip() + "\n" * newlines_before
- if newlines_before > 0:
- end_str += indent_whitespace
-
- end_str += current_line + whitespace_to_add
-
- current_line = ""
- prev_line_type = line_type
-
- for item in tokens:
- str_item = str(item)
-
- if item.type == TokenType.WHITESPACE:
- last_whitespace_contains_newline = "\n" in str_item
- continue
-
- whitespace_required = (
- str_item in WHITESPACE_BEFORE
- or str(last_not_whitespace) in WHITESPACE_AFTER
- or (str_item == "(" and end_str.endswith(": bind"))
- )
- whitespace_blockers = (
- str_item in NO_WHITESPACE_BEFORE
- or str(last_not_whitespace) in NO_WHITESPACE_AFTER
- or (str_item == "<" and str(last_not_whitespace) == "typeof")
- )
-
- this_or_last_is_ident = TokenType.IDENT in (item.type, last_not_whitespace.type)
- current_line_is_empty = len(current_line) == 0
- is_function = str_item == "(" and not re.match(
- r"^([A-Za-z_\-])+(: bind)?$", current_line
- )
-
- any_blockers = whitespace_blockers or current_line_is_empty or is_function
- if (whitespace_required or this_or_last_is_ident) and not any_blockers:
- current_line += " "
-
- current_line += str_item
-
- if str_item in ("[", "("):
- bracket_tracker.append(str_item)
- elif str_item in ("]", ")"):
- bracket_tracker.pop()
-
- needs_newline_treatment = (
- str_item in NEWLINE_AFTER or item.type == TokenType.COMMENT
- )
- if needs_newline_treatment:
- if str_item in OPENING_TOKENS:
- list_or_child_type = str_item == "["
- if list_or_child_type:
- is_child_type = current_line.startswith("[")
-
- if is_child_type:
- if str(last_not_whitespace) not in OPENING_TOKENS:
- end_str = (
- end_str.strip() + "\n\n" + (indent_item * indent_levels)
- )
- last_not_whitespace = item
- continue
-
- indent_levels += 1
- keep_same_indent = prev_line_type not in (
- LineType.CHILD_TYPE,
- LineType.COMMENT,
- LineType.BLOCK_OPEN,
- )
- if keep_same_indent:
- end_str = (
- end_str.strip() + "\n\n" + indent_item * (indent_levels - 1)
- )
- commit_current_line(LineType.BLOCK_OPEN)
-
- elif str_item == "]" and is_child_type:
- commit_current_line(LineType.CHILD_TYPE, False)
- is_child_type = False
-
- elif str_item in CLOSING_TOKENS:
- if str_item == "]" and str(last_not_whitespace) != "[":
- current_line = current_line[:-1]
- if str(last_not_whitespace) != ",":
- current_line += ","
- commit_current_line()
- current_line = "]"
- elif str(last_not_whitespace) in OPENING_TOKENS:
- end_str = end_str.strip()
- commit_current_line(LineType.BLOCK_CLOSE, True, 0)
-
- indent_levels -= 1
- commit_current_line(LineType.BLOCK_CLOSE, True)
-
- elif str_item == ";":
- line_type = LineType.STATEMENT
- newlines = 1
-
- if len(current_line) == 1:
- newlines = 0
- line_type = LineType.BLOCK_CLOSE
- elif prev_line_type == LineType.BLOCK_CLOSE:
- newlines = 2
-
- commit_current_line(line_type, newlines_before=newlines)
-
- elif item.type == TokenType.COMMENT:
- require_extra_newline = (
- LineType.BLOCK_CLOSE,
- LineType.STATEMENT,
- LineType.COMMENT,
- )
-
- single_line_comment = str_item.startswith("//")
- newlines = 1
- if single_line_comment:
- if not str_item.startswith("// "):
- current_line = f"// {current_line[2:]}"
-
- if not last_whitespace_contains_newline:
- current_line = " " + current_line
- newlines = 0
- elif prev_line_type == LineType.BLOCK_CLOSE:
- newlines = 2
-
- elif prev_line_type in require_extra_newline:
- newlines = 2
-
- current_line = "\n".join(
- [line.rstrip() for line in current_line.split("\n")]
- )
- commit_current_line(LineType.COMMENT, newlines_before=newlines)
-
- else: # pragma: no cover
- raise CompilerBugError()
-
- elif str_item == "(" and (
- re.match(r"^([A-Za-z_\-])+\s*\(", current_line) or watch_parentheses
- ):
- watch_parentheses = True
- parentheses_balance += 1
-
- elif str_item == ")" and watch_parentheses:
- parentheses_balance -= 1
- all_parentheses_closed = parentheses_balance == 0
- if all_parentheses_closed:
- commit_current_line(
- newlines_before=2 if prev_line_type == LineType.BLOCK_CLOSE else 1
- )
- watch_parentheses = False
-
- tracker_is_empty = len(bracket_tracker) > 0
- if tracker_is_empty:
- last_in_tracker = bracket_tracker[-1]
- is_list_comma = last_in_tracker == "[" and str_item == ","
- if is_list_comma:
- last_was_list_item = end_str.strip()[-1] not in ("[", ",")
- if last_was_list_item:
- end_str = end_str.strip()
- commit_current_line()
-
- last_not_whitespace = item
- last_whitespace_contains_newline = False
-
- return end_str.strip() + "\n"
diff --git a/blueprintcompiler/gir.py b/blueprintcompiler/gir.py
index 333f4ac..e85190b 100644
--- a/blueprintcompiler/gir.py
+++ b/blueprintcompiler/gir.py
@@ -17,31 +17,17 @@
#
# SPDX-License-Identifier: LGPL-3.0-or-later
-import os
-import sys
-import typing as T
from functools import cached_property
+import typing as T
+import os, sys
import gi # type: ignore
-try:
- gi.require_version("GIRepository", "3.0")
- from gi.repository import GIRepository # type: ignore
+gi.require_version("GIRepository", "2.0")
+from gi.repository import GIRepository # type: ignore
- _repo = GIRepository.Repository()
-except ValueError:
- # We can remove this once we can bump the minimum dependencies
- # to glib 2.80 and pygobject 3.52
- # dependency('glib-2.0', version: '>= 2.80.0')
- # dependency('girepository-2.0', version: '>= 2.80.0')
- gi.require_version("GIRepository", "2.0")
- from gi.repository import GIRepository # type: ignore
-
- _repo = GIRepository.Repository
-
-from . import typelib, xml_reader
from .errors import CompileError, CompilerBugError
-from .lsp_utils import CodeAction
+from . import typelib, xml_reader
_namespace_cache: T.Dict[str, "Namespace"] = {}
_xml_cache = {}
@@ -54,7 +40,7 @@ def add_typelib_search_path(path: str):
def get_namespace(namespace: str, version: str) -> "Namespace":
- search_paths = [*_repo.get_search_path(), *_user_search_paths]
+ search_paths = [*GIRepository.Repository.get_search_path(), *_user_search_paths]
filename = f"{namespace}-{version}.typelib"
@@ -78,32 +64,6 @@ def get_namespace(namespace: str, version: str) -> "Namespace":
return _namespace_cache[filename]
-_available_namespaces: list[tuple[str, str]] = []
-
-
-def get_available_namespaces() -> T.List[T.Tuple[str, str]]:
- if len(_available_namespaces):
- return _available_namespaces
-
- search_paths: list[str] = [
- *_repo.get_search_path(),
- *_user_search_paths,
- ]
-
- for search_path in search_paths:
- try:
- filenames = os.listdir(search_path)
- except FileNotFoundError:
- continue
-
- for filename in filenames:
- if filename.endswith(".typelib"):
- namespace, version = filename.removesuffix(".typelib").rsplit("-", 1)
- _available_namespaces.append((namespace, version))
-
- return _available_namespaces
-
-
def get_xml(namespace: str, version: str):
search_paths = []
@@ -131,23 +91,6 @@ def get_xml(namespace: str, version: str):
return _xml_cache[filename]
-ONLINE_DOCS = {
- "Adw-1": "https://gnome.pages.gitlab.gnome.org/libadwaita/doc/1-latest/",
- "Gdk-4.0": "https://docs.gtk.org/gdk4/",
- "GdkPixbuf-2.0": "https://docs.gtk.org/gdk-pixbuf/",
- "Gio-2.0": "https://docs.gtk.org/gio/",
- "GLib-2.0": "https://docs.gtk.org/glib/",
- "GModule-2.0": "https://docs.gtk.org/gmodule/",
- "GObject-2.0": "https://docs.gtk.org/gobject/",
- "Gsk-4.0": "https://docs.gtk.org/gsk4/",
- "Gtk-4.0": "https://docs.gtk.org/gtk4/",
- "GtkSource-5": "https://gnome.pages.gitlab.gnome.org/gtksourceview/gtksourceview5",
- "Pango-1.0": "https://docs.gtk.org/Pango/",
- "Shumate-1.0": "https://gnome.pages.gitlab.gnome.org/libshumate/",
- "WebKit2-4.1": "https://webkitgtk.org/reference/webkit2gtk/stable/",
-}
-
-
class GirType:
@property
def doc(self) -> T.Optional[str]:
@@ -175,14 +118,6 @@ class GirType:
def incomplete(self) -> bool:
return False
- @property
- def deprecated(self) -> bool:
- return False
-
- @property
- def deprecated_doc(self) -> T.Optional[str]:
- return None
-
class ExternType(GirType):
def __init__(self, name: str) -> None:
@@ -212,10 +147,6 @@ class ArrayType(GirType):
def assignable_to(self, other: GirType) -> bool:
return isinstance(other, ArrayType) and self._inner.assignable_to(other._inner)
- @property
- def inner(self) -> GirType:
- return self._inner
-
@property
def name(self) -> str:
return self._inner.name + "[]"
@@ -304,8 +235,6 @@ TNode = T.TypeVar("TNode", bound="GirNode")
class GirNode:
- xml_tag: str
-
def __init__(self, container: T.Optional["GirNode"], tl: typelib.Typelib) -> None:
self.container = container
self.tl = tl
@@ -322,8 +251,7 @@ class GirNode:
def xml(self):
for el in self.container.xml.children:
if el.attrs.get("name") == self.name:
- if el.tag == self.xml_tag:
- return el
+ return el
@cached_property
def glib_type_name(self) -> str:
@@ -348,17 +276,6 @@ class GirNode:
def available_in(self) -> str:
return self.xml.get("version")
- @cached_property
- def detail(self) -> T.Optional[str]:
- try:
- el = self.xml.get_elements("doc")
- if len(el) == 1:
- return el[0].cdata.strip().partition("\n")[0]
- else:
- return None
- except:
- return None
-
@cached_property
def doc(self) -> T.Optional[str]:
sections = []
@@ -373,17 +290,10 @@ class GirNode:
except:
# Not a huge deal, but if you want docs in the language server you
# should ensure .gir files are installed
- sections.append("Documentation is not installed")
-
- if self.online_docs:
- sections.append(f"[Online documentation]({self.online_docs})")
+ pass
return "\n\n---\n\n".join(sections)
- @property
- def online_docs(self) -> T.Optional[str]:
- return None
-
@property
def signature(self) -> T.Optional[str]:
return None
@@ -392,17 +302,8 @@ class GirNode:
def type(self) -> GirType:
raise NotImplementedError()
- @property
- def deprecated_doc(self) -> T.Optional[str]:
- try:
- return self.xml.get_elements("doc-deprecated")[0].cdata.strip()
- except:
- return None
-
class Property(GirNode):
- xml_tag = "property"
-
def __init__(self, klass: T.Union["Class", "Interface"], tl: typelib.Typelib):
super().__init__(klass, tl)
@@ -416,7 +317,7 @@ class Property(GirNode):
@cached_property
def signature(self):
- return f"{self.type.full_name} {self.container.name}:{self.name}"
+ return f"{self.full_name} {self.container.name}.{self.name}"
@property
def writable(self) -> bool:
@@ -426,94 +327,31 @@ class Property(GirNode):
def construct_only(self) -> bool:
return self.tl.PROP_CONSTRUCT_ONLY == 1
- @property
- def online_docs(self) -> T.Optional[str]:
- if ns := self.get_containing(Namespace).online_docs:
- assert self.container is not None
- return f"{ns}property.{self.container.name}.{self.name}.html"
- else:
- return None
- @property
- def deprecated(self) -> bool:
- return self.tl.PROP_DEPRECATED == 1
-
-
-class Argument(GirNode):
+class Parameter(GirNode):
def __init__(self, container: GirNode, tl: typelib.Typelib) -> None:
super().__init__(container, tl)
- @cached_property
- def name(self) -> str:
- return self.tl.ARG_NAME
-
- @cached_property
- def type(self) -> GirType:
- return self.get_containing(Repository)._resolve_type_id(self.tl.ARG_TYPE)
-
-
-class Signature(GirNode):
- def __init__(self, container: GirNode, tl: typelib.Typelib) -> None:
- super().__init__(container, tl)
-
- @cached_property
- def args(self) -> T.List[Argument]:
- n_arguments = self.tl.SIGNATURE_N_ARGUMENTS
- blob_size = self.tl.header.HEADER_ARG_BLOB_SIZE
- result = []
- for i in range(n_arguments):
- entry = self.tl.SIGNATURE_ARGUMENTS[i * blob_size]
- result.append(Argument(self, entry))
- return result
-
- @cached_property
- def return_type(self) -> T.Optional[GirType]:
- if self.tl.SIGNATURE_RETURN_TYPE == 0:
- return None
- else:
- return self.get_containing(Repository)._resolve_type_id(
- self.tl.SIGNATURE_RETURN_TYPE
- )
-
class Signal(GirNode):
- xml_tag = "glib:signal"
-
def __init__(
self, klass: T.Union["Class", "Interface"], tl: typelib.Typelib
) -> None:
super().__init__(klass, tl)
-
- @cached_property
- def gir_signature(self) -> Signature:
- return Signature(self, self.tl.SIGNAL_SIGNATURE)
+ # if parameters := xml.get_elements('parameters'):
+ # self.params = [Parameter(self, child) for child in parameters[0].get_elements('parameter')]
+ # else:
+ # self.params = []
@property
def signature(self):
- args = ", ".join(
- [f"{a.type.full_name} {a.name}" for a in self.gir_signature.args]
- )
- result = 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
- def online_docs(self) -> T.Optional[str]:
- if ns := self.get_containing(Namespace).online_docs:
- assert self.container is not None
- return f"{ns}signal.{self.container.name}.{self.name}.html"
- else:
- return None
-
- @property
- def deprecated(self) -> bool:
- return self.tl.SIGNAL_DEPRECATED == 1
+ # TODO: fix
+ # args = ", ".join([f"{p.type_name} {p.name}" for p in self.params])
+ args = ""
+ return f"signal {self.container.name}.{self.name} ({args})"
class Interface(GirNode, GirType):
- xml_tag = "interface"
-
def __init__(self, ns: "Namespace", tl: typelib.Typelib):
super().__init__(ns, tl)
@@ -564,21 +402,8 @@ class Interface(GirNode, GirType):
return True
return False
- @property
- def online_docs(self) -> T.Optional[str]:
- if ns := self.get_containing(Namespace).online_docs:
- return f"{ns}iface.{self.name}.html"
- else:
- return None
-
- @property
- def deprecated(self) -> bool:
- return self.tl.INTERFACE_DEPRECATED == 1
-
class Class(GirNode, GirType):
- xml_tag = "class"
-
def __init__(self, ns: "Namespace", tl: typelib.Typelib) -> None:
super().__init__(ns, tl)
@@ -689,17 +514,6 @@ class Class(GirNode, GirType):
for impl in self.implements:
yield from impl.signals.values()
- @property
- def online_docs(self) -> T.Optional[str]:
- if ns := self.get_containing(Namespace).online_docs:
- return f"{ns}class.{self.name}.html"
- else:
- return None
-
- @property
- def deprecated(self) -> bool:
- return self.tl.OBJ_DEPRECATED == 1
-
class TemplateType(GirType):
def __init__(self, name: str, parent: T.Optional[GirType]):
@@ -739,7 +553,7 @@ class TemplateType(GirType):
# we don't know the template type's interfaces, assume yes
return True
elif self.parent is None or isinstance(self.parent, ExternType):
- return isinstance(other, Class) or isinstance(other, ExternType)
+ return isinstance(other, Class)
else:
return self.parent.assignable_to(other)
@@ -756,8 +570,6 @@ class TemplateType(GirType):
class EnumMember(GirNode):
- xml_tag = "member"
-
def __init__(self, enum: "Enumeration", tl: typelib.Typelib) -> None:
super().__init__(enum, tl)
@@ -783,8 +595,6 @@ class EnumMember(GirNode):
class Enumeration(GirNode, GirType):
- xml_tag = "enumeration"
-
def __init__(self, ns: "Namespace", tl: typelib.Typelib) -> None:
super().__init__(ns, tl)
@@ -806,21 +616,8 @@ class Enumeration(GirNode, GirType):
def assignable_to(self, type: GirType) -> bool:
return type == self
- @property
- def online_docs(self) -> T.Optional[str]:
- if ns := self.get_containing(Namespace).online_docs:
- return f"{ns}enum.{self.name}.html"
- else:
- return None
-
- @property
- def deprecated(self) -> bool:
- return self.tl.ENUM_DEPRECATED == 1
-
class Boxed(GirNode, GirType):
- xml_tag = "glib:boxed"
-
def __init__(self, ns: "Namespace", tl: typelib.Typelib) -> None:
super().__init__(ns, tl)
@@ -831,21 +628,8 @@ class Boxed(GirNode, GirType):
def assignable_to(self, type) -> bool:
return type == self
- @property
- def online_docs(self) -> T.Optional[str]:
- if ns := self.get_containing(Namespace).online_docs:
- return f"{ns}boxed.{self.name}.html"
- else:
- return None
-
- @property
- def deprecated(self) -> bool:
- return self.tl.STRUCT_DEPRECATED == 1
-
class Bitfield(Enumeration):
- xml_tag = "bitfield"
-
def __init__(self, ns: "Namespace", tl: typelib.Typelib) -> None:
super().__init__(ns, tl)
@@ -854,35 +638,29 @@ class Namespace(GirNode):
def __init__(self, repo: "Repository", tl: typelib.Typelib) -> None:
super().__init__(repo, tl)
- @cached_property
- def entries(self) -> T.Mapping[str, GirType]:
- entries: dict[str, GirType] = {}
-
- n_local_entries: int = self.tl.HEADER_N_ENTRIES
- directory: typelib.Typelib = self.tl.HEADER_DIRECTORY
- blob_size: int = self.tl.header.HEADER_ENTRY_BLOB_SIZE
+ self.entries: T.Dict[str, GirType] = {}
+ n_local_entries: int = tl.HEADER_N_ENTRIES
+ directory: typelib.Typelib = tl.HEADER_DIRECTORY
for i in range(n_local_entries):
- entry = directory[i * blob_size]
+ entry = directory[i * tl.HEADER_ENTRY_BLOB_SIZE]
entry_name: str = entry.DIR_ENTRY_NAME
entry_type: int = entry.DIR_ENTRY_BLOB_TYPE
entry_blob: typelib.Typelib = entry.DIR_ENTRY_OFFSET
if entry_type == typelib.BLOB_TYPE_ENUM:
- entries[entry_name] = Enumeration(self, entry_blob)
+ self.entries[entry_name] = Enumeration(self, entry_blob)
elif entry_type == typelib.BLOB_TYPE_FLAGS:
- entries[entry_name] = Bitfield(self, entry_blob)
+ self.entries[entry_name] = Bitfield(self, entry_blob)
elif entry_type == typelib.BLOB_TYPE_OBJECT:
- entries[entry_name] = Class(self, entry_blob)
+ self.entries[entry_name] = Class(self, entry_blob)
elif entry_type == typelib.BLOB_TYPE_INTERFACE:
- entries[entry_name] = Interface(self, entry_blob)
+ self.entries[entry_name] = Interface(self, entry_blob)
elif (
entry_type == typelib.BLOB_TYPE_BOXED
or entry_type == typelib.BLOB_TYPE_STRUCT
):
- entries[entry_name] = Boxed(self, entry_blob)
-
- return entries
+ self.entries[entry_name] = Boxed(self, entry_blob)
@cached_property
def xml(self):
@@ -908,22 +686,22 @@ class Namespace(GirNode):
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]:
"""Gets a type (class, interface, enum, etc.) from this namespace."""
return self.entries.get(name)
def get_type_by_cname(self, cname: str) -> T.Optional[GirType]:
"""Gets a type from this namespace by its C name."""
- for basic in _BASIC_TYPES.values():
- if basic.glib_type_name == cname:
- return basic()
-
for item in self.entries.values():
- if (
- hasattr(item, "cname")
- and item.cname is not None
- and item.cname == cname
- ):
+ if hasattr(item, "cname") and item.cname == cname:
return item
return None
@@ -931,12 +709,13 @@ class Namespace(GirNode):
"""Looks up a type in the scope of this namespace (including in the
namespace's dependencies)."""
- ns, name = type_name.split(".", 1)
- return self.get_containing(Repository).get_type(name, ns)
-
- @property
- def online_docs(self) -> T.Optional[str]:
- return ONLINE_DOCS.get(f"{self.name}-{self.version}")
+ if type_name in _BASIC_TYPES:
+ return _BASIC_TYPES[type_name]()
+ elif "." in type_name:
+ ns, name = type_name.split(".", 1)
+ return self.get_containing(Repository).get_type(name, ns)
+ else:
+ return self.get_type(type_name)
class Repository(GirNode):
@@ -951,7 +730,7 @@ class Repository(GirNode):
self.includes = {
name: get_namespace(name, version) for name, version in deps
}
- except: # pragma: no cover
+ except:
raise CompilerBugError(f"Failed to load dependencies.")
else:
self.includes = {}
@@ -959,6 +738,12 @@ class Repository(GirNode):
def get_type(self, name: str, ns: str) -> T.Optional[GirType]:
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):
"""Finds a namespace among this namespace's dependencies."""
if ns == self.namespace.name:
@@ -976,8 +761,8 @@ class Repository(GirNode):
return self.lookup_namespace(ns).get_type(dir_entry.DIR_ENTRY_NAME)
def _resolve_type_id(self, type_id: int) -> GirType:
- if type_id & (0xFFFFFF if sys.byteorder == "little" else 0xFFFFFF00) == 0:
- type_id = ((type_id >> 27) if sys.byteorder == "little" else type_id) & 0x1F
+ if type_id & 0xFFFFFF == 0:
+ type_id = (type_id >> 27) & 0x1F
# simple type
if type_id == typelib.TYPE_BOOLEAN:
return BoolType()
@@ -1060,11 +845,9 @@ class GirContext:
ns = ns or "Gtk"
if ns not in self.namespaces and ns not in self.not_found_namespaces:
- all_available = list(set(ns for ns, _version in get_available_namespaces()))
-
raise CompileError(
f"Namespace {ns} was not imported",
- did_you_mean=(ns, all_available),
+ did_you_mean=(ns, self.namespaces.keys()),
)
def validate_type(self, name: str, ns: str) -> None:
diff --git a/blueprintcompiler/interactive_port.py b/blueprintcompiler/interactive_port.py
index 12dd485..1286f0b 100644
--- a/blueprintcompiler/interactive_port.py
+++ b/blueprintcompiler/interactive_port.py
@@ -18,15 +18,16 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
+import typing as T
import difflib
import os
-import typing as T
-from . import decompiler, parser, tokenizer
-from .errors import CompilerBugError, MultipleErrors, PrintableError
+from . import decompiler, tokenizer, parser
from .outputs.xml import XmlOutput
+from .errors import MultipleErrors, PrintableError, CompilerBugError
from .utils import Colors
+
# A tool to interactively port projects to blueprints.
@@ -71,7 +72,7 @@ def decompile_file(in_file, out_file) -> T.Union[str, CouldNotPort]:
print(
f"""{Colors.FAINT}Either the original XML file had an error, or there is a bug in the
porting tool. If you think it's a bug (which is likely), please file an issue on GitLab:
-{Colors.BLUE}{Colors.UNDERLINE}https://gitlab.gnome.org/GNOME/blueprint-compiler/-/issues/new?issue{Colors.CLEAR}\n"""
+{Colors.BLUE}{Colors.UNDERLINE}https://gitlab.gnome.org/jwestman/blueprint-compiler/-/issues/new?issue{Colors.CLEAR}\n"""
)
return CouldNotPort("does not compile")
@@ -136,7 +137,7 @@ def step1():
wrap.write(
f"""[wrap-git]
directory = blueprint-compiler
-url = https://gitlab.gnome.org/GNOME/blueprint-compiler.git
+url = https://gitlab.gnome.org/jwestman/blueprint-compiler.git
revision = {VERSION}
depth = 1
@@ -301,7 +302,9 @@ def step5(in_files):
(
Colors.GREEN
if line.startswith("+")
- else Colors.RED + Colors.FAINT if line.startswith("-") else ""
+ else Colors.RED + Colors.FAINT
+ if line.startswith("-")
+ else ""
)
+ line
+ Colors.CLEAR
diff --git a/blueprintcompiler/language/__init__.py b/blueprintcompiler/language/__init__.py
index 5eb2b60..d785e56 100644
--- a/blueprintcompiler/language/__init__.py
+++ b/blueprintcompiler/language/__init__.py
@@ -1,11 +1,12 @@
+from .gtk_list_item_factory import ExtListItemFactory
+from .adw_message_dialog import ExtAdwMessageDialog
+from .attributes import BaseAttribute
from .adw_breakpoint import (
- AdwBreakpointCondition,
- AdwBreakpointSetter,
AdwBreakpointSetters,
+ AdwBreakpointSetter,
+ AdwBreakpointCondition,
)
-from .adw_response_dialog import ExtAdwResponseDialog
from .binding import Binding
-from .common import *
from .contexts import ScopeCtx, ValueTypeCtx
from .expression import (
CastExpr,
@@ -19,29 +20,27 @@ from .expression import (
from .gobject_object import Object, ObjectContent
from .gobject_property import Property
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_file_filter import (
- Filters,
ext_file_filter_mime_types,
ext_file_filter_patterns,
ext_file_filter_suffixes,
+ Filters,
)
from .gtk_layout import ExtLayout
-from .gtk_list_item_factory import ExtListItemFactory
-from .gtk_menu import Menu, MenuAttribute, menu
+from .gtk_menu import menu, Menu, MenuAttribute
from .gtk_scale import ExtScaleMarks
from .gtk_size_group import ExtSizeGroupWidgets
from .gtk_string_list import ExtStringListStrings
from .gtk_styles import ExtStyles
-from .gtkbuilder_child import Child, ChildExtension, ChildInternal, ChildType
+from .gtkbuilder_child import Child, ChildType, ChildInternal, ChildExtension
from .gtkbuilder_template import Template
from .imports import GtkDirective, Import
-from .types import ClassName
+from .property_binding import PropertyBinding
from .ui import UI
+from .types import ClassName
from .values import (
- ArrayValue,
- ExprValue,
Flag,
Flags,
IdentLiteral,
@@ -55,13 +54,15 @@ from .values import (
Value,
)
+from .common import *
+
OBJECT_CONTENT_HOOKS.children = [
Signal,
Property,
AdwBreakpointCondition,
AdwBreakpointSetters,
ExtAccessibility,
- ExtAdwResponseDialog,
+ ExtAdwMessageDialog,
ExtComboBoxItems,
ext_file_filter_mime_types,
ext_file_filter_patterns,
diff --git a/blueprintcompiler/language/adw_breakpoint.py b/blueprintcompiler/language/adw_breakpoint.py
index 3d2c10d..addbd8a 100644
--- a/blueprintcompiler/language/adw_breakpoint.py
+++ b/blueprintcompiler/language/adw_breakpoint.py
@@ -24,36 +24,12 @@ from .values import Value
class AdwBreakpointCondition(AstNode):
- grammar = [
- UseExact("kw", "condition"),
- "(",
- UseQuoted("condition"),
- Match(")").expected(),
- ]
+ grammar = ["condition", "(", UseQuoted("condition"), Match(")").expected()]
@property
def condition(self) -> str:
return self.tokens["condition"]
- @property
- def document_symbol(self) -> DocumentSymbol:
- return DocumentSymbol(
- "condition",
- SymbolKind.Property,
- self.range,
- self.group.tokens["kw"].range,
- self.condition,
- )
-
- @docs("kw")
- def keyword_docs(self):
- klass = self.root.gir.get_type("Breakpoint", "Adw")
- if klass is None:
- return None
- prop = klass.properties.get("condition")
- assert isinstance(prop, gir.Property)
- return prop.doc
-
@validate()
def unique(self):
self.validate_unique_in_parent("Duplicate condition statement")
@@ -81,8 +57,8 @@ class AdwBreakpointSetter(AstNode):
return self.tokens["property"]
@property
- def value(self) -> T.Optional[Value]:
- return self.children[Value][0] if len(self.children[Value]) > 0 else None
+ def value(self) -> Value:
+ return self.children[Value][0]
@property
def gir_class(self) -> T.Optional[GirType]:
@@ -92,42 +68,9 @@ class AdwBreakpointSetter(AstNode):
return None
@property
- def gir_property(self) -> T.Optional[gir.Property]:
- if (
- self.gir_class is not None
- and not isinstance(self.gir_class, ExternType)
- and self.property_name is not None
- ):
- assert isinstance(self.gir_class, gir.Class) or isinstance(
- self.gir_class, gir.TemplateType
- )
+ def gir_property(self):
+ if self.gir_class is not None and not isinstance(self.gir_class, ExternType):
return self.gir_class.properties.get(self.property_name)
- else:
- return None
-
- @property
- def document_symbol(self) -> T.Optional[DocumentSymbol]:
- if self.value is None:
- return None
-
- return DocumentSymbol(
- f"{self.object_id}.{self.property_name}",
- SymbolKind.Property,
- self.range,
- self.group.tokens["object"].range,
- self.value.range.text,
- )
-
- def get_reference(self, idx: int) -> T.Optional[LocationLink]:
- if idx in self.group.tokens["object"].range:
- if self.object is not None:
- return LocationLink(
- self.group.tokens["object"].range,
- self.object.range,
- self.object.ranges["id"],
- )
-
- return None
@context(ValueTypeCtx)
def value_type(self) -> ValueTypeCtx:
@@ -138,20 +81,6 @@ class AdwBreakpointSetter(AstNode):
return ValueTypeCtx(type, allow_null=True)
- @docs("object")
- def object_docs(self):
- if self.object is not None:
- return f"```\n{self.object.signature}\n```"
- else:
- return None
-
- @docs("property")
- def property_docs(self):
- if self.gir_property is not None:
- return self.gir_property.doc
- else:
- return None
-
@validate("object")
def object_exists(self):
if self.object is None:
@@ -183,25 +112,12 @@ class AdwBreakpointSetter(AstNode):
class AdwBreakpointSetters(AstNode):
- grammar = [
- Keyword("setters"),
- Match("{").expected(),
- Until(AdwBreakpointSetter, "}"),
- ]
+ grammar = ["setters", Match("{").expected(), Until(AdwBreakpointSetter, "}")]
@property
def setters(self) -> T.List[AdwBreakpointSetter]:
return self.children[AdwBreakpointSetter]
- @property
- def document_symbol(self) -> DocumentSymbol:
- return DocumentSymbol(
- "setters",
- SymbolKind.Struct,
- self.range,
- self.group.tokens["setters"].range,
- )
-
@validate()
def container_is_breakpoint(self):
validate_parent_type(self, "Adw", "Breakpoint", "breakpoint setters")
@@ -209,46 +125,3 @@ class AdwBreakpointSetters(AstNode):
@validate()
def unique(self):
self.validate_unique_in_parent("Duplicate setters block")
-
- @docs("setters")
- def ref_docs(self):
- return get_docs_section("Syntax ExtAdwBreakpoint")
-
-
-@decompiler("condition", cdata=True)
-def decompile_condition(ctx: DecompileCtx, gir, cdata):
- ctx.print(f"condition({escape_quote(cdata)})")
-
-
-@decompiler("setter", element=True)
-def decompile_setter(ctx: DecompileCtx, gir, element):
- assert ctx.parent_node is not None
- # only run for the first setter
- for child in ctx.parent_node.children:
- if child.tag == "setter":
- if child != element:
- # already decompiled
- return
- else:
- break
-
- ctx.print("setters {")
- for child in ctx.parent_node.children:
- if child.tag == "setter":
- object_id = child["object"]
- property_name = child["property"]
- obj = ctx.find_object(object_id)
- if obj is not None:
- gir_class = ctx.type_by_cname(obj["class"])
- else:
- gir_class = None
-
- if object_id == ctx.template_class:
- object_id = "template"
-
- comments, string = ctx.decompile_value(
- child.cdata,
- gir_class,
- (child["translatable"], child["context"], child["comments"]),
- )
- ctx.print(f"{comments} {object_id}.{property_name}: {string};")
diff --git a/blueprintcompiler/language/adw_response_dialog.py b/blueprintcompiler/language/adw_message_dialog.py
similarity index 68%
rename from blueprintcompiler/language/adw_response_dialog.py
rename to blueprintcompiler/language/adw_message_dialog.py
index d2680fd..2823f77 100644
--- a/blueprintcompiler/language/adw_response_dialog.py
+++ b/blueprintcompiler/language/adw_message_dialog.py
@@ -1,4 +1,4 @@
-# adw_response_dialog.py
+# adw_message_dialog.py
#
# Copyright 2023 James Westman
#
@@ -18,13 +18,14 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
-from ..decompiler import decompile_translatable, truthy
+from ..decompiler import truthy, decompile_translatable
from .common import *
+from .contexts import ValueTypeCtx
from .gobject_object import ObjectContent, validate_parent_type
from .values import StringValue
-class ExtAdwResponseDialogFlag(AstNode):
+class ExtAdwMessageDialogFlag(AstNode):
grammar = AnyOf(
UseExact("flag", "destructive"),
UseExact("flag", "suggested"),
@@ -50,12 +51,12 @@ class ExtAdwResponseDialogFlag(AstNode):
)
-class ExtAdwResponseDialogResponse(AstNode):
+class ExtAdwMessageDialogResponse(AstNode):
grammar = [
UseIdent("id"),
Match(":").expected(),
to_parse_node(StringValue).expected("a string or translatable string"),
- ZeroOrMore(ExtAdwResponseDialogFlag),
+ ZeroOrMore(ExtAdwMessageDialogFlag),
]
@property
@@ -63,8 +64,8 @@ class ExtAdwResponseDialogResponse(AstNode):
return self.tokens["id"]
@property
- def flags(self) -> T.List[ExtAdwResponseDialogFlag]:
- return self.children[ExtAdwResponseDialogFlag]
+ def flags(self) -> T.List[ExtAdwMessageDialogFlag]:
+ return self.children[ExtAdwMessageDialogFlag]
@property
def appearance(self) -> T.Optional[str]:
@@ -83,15 +84,9 @@ class ExtAdwResponseDialogResponse(AstNode):
def value(self) -> StringValue:
return self.children[0]
- @property
- def document_symbol(self) -> DocumentSymbol:
- return DocumentSymbol(
- self.id,
- SymbolKind.Field,
- self.range,
- self.group.tokens["id"].range,
- self.value.range.text,
- )
+ @context(ValueTypeCtx)
+ def value_type(self) -> ValueTypeCtx:
+ return ValueTypeCtx(StringType())
@validate("id")
def unique_in_parent(self):
@@ -101,60 +96,33 @@ class ExtAdwResponseDialogResponse(AstNode):
)
-class ExtAdwResponseDialog(AstNode):
+class ExtAdwMessageDialog(AstNode):
grammar = [
Keyword("responses"),
Match("[").expected(),
- Delimited(ExtAdwResponseDialogResponse, ","),
+ Delimited(ExtAdwMessageDialogResponse, ","),
"]",
]
@property
- def responses(self) -> T.List[ExtAdwResponseDialogResponse]:
+ def responses(self) -> T.List[ExtAdwMessageDialogResponse]:
return self.children
- @property
- def document_symbol(self) -> DocumentSymbol:
- return DocumentSymbol(
- "responses",
- SymbolKind.Array,
- self.range,
- self.group.tokens["responses"].range,
- )
-
@validate("responses")
- def container_is_message_dialog_or_alert_dialog(self):
- try:
- validate_parent_type(self, "Adw", "MessageDialog", "responses")
- except:
- validate_parent_type(self, "Adw", "AlertDialog", "responses")
+ def container_is_message_dialog(self):
+ validate_parent_type(self, "Adw", "MessageDialog", "responses")
@validate("responses")
def unique_in_parent(self):
self.validate_unique_in_parent("Duplicate responses block")
- @docs()
- def ref_docs(self):
- return get_docs_section("Syntax ExtAdwMessageDialog")
-
@completer(
applies_in=[ObjectContent],
applies_in_subclass=("Adw", "MessageDialog"),
matches=new_statement_patterns,
)
-def complete_adw_message_dialog(lsp, ast_node, match_variables):
- yield Completion(
- "responses", CompletionItemKind.Keyword, snippet="responses [\n\t$0\n]"
- )
-
-
-@completer(
- applies_in=[ObjectContent],
- applies_in_subclass=("Adw", "AlertDialog"),
- matches=new_statement_patterns,
-)
-def complete_adw_alert_dialog(lsp, ast_node, match_variables):
+def style_completer(ast_node, match_variables):
yield Completion(
"responses", CompletionItemKind.Keyword, snippet="responses [\n\t$0\n]"
)
diff --git a/blueprintcompiler/language/translation_domain.py b/blueprintcompiler/language/attributes.py
similarity index 72%
rename from blueprintcompiler/language/translation_domain.py
rename to blueprintcompiler/language/attributes.py
index 0f60af9..8ff1f0b 100644
--- a/blueprintcompiler/language/translation_domain.py
+++ b/blueprintcompiler/language/attributes.py
@@ -1,4 +1,4 @@
-# translation_domain.py
+# attributes.py
#
# Copyright 2022 James Westman
#
@@ -17,19 +17,16 @@
#
# SPDX-License-Identifier: LGPL-3.0-or-later
+
from .common import *
-class TranslationDomain(AstNode):
- grammar = Statement(
- "translation-domain",
- UseQuoted("domain"),
- )
+class BaseAttribute(AstNode):
+ """A helper class for attribute syntax of the form `name: literal_value;`"""
+
+ tag_name: str = ""
+ attr_name: str = "name"
@property
- def domain(self):
- return self.tokens["domain"]
-
- @docs()
- def ref_docs(self):
- return get_docs_section("Syntax TranslationDomain")
+ def name(self):
+ return self.tokens["name"]
diff --git a/blueprintcompiler/language/binding.py b/blueprintcompiler/language/binding.py
index 07572a9..b13d5da 100644
--- a/blueprintcompiler/language/binding.py
+++ b/blueprintcompiler/language/binding.py
@@ -20,64 +20,19 @@
from dataclasses import dataclass
from .common import *
-from .expression import Expression, LiteralExpr, LookupOp
-
-
-class BindingFlag(AstNode):
- grammar = [
- AnyOf(
- UseExact("flag", "inverted"),
- UseExact("flag", "bidirectional"),
- UseExact("flag", "no-sync-create"),
- UseExact("flag", "sync-create"),
- )
- ]
-
- @property
- def flag(self) -> str:
- return self.tokens["flag"]
-
- @validate()
- def sync_create(self):
- if self.flag == "sync-create":
- raise UpgradeWarning(
- "'sync-create' is now the default. Use 'no-sync-create' if this is not wanted.",
- actions=[CodeAction("remove 'sync-create'", "")],
- )
-
- @validate()
- def unique(self):
- self.validate_unique_in_parent(
- f"Duplicate flag '{self.flag}'", lambda x: x.flag == self.flag
- )
-
- @validate()
- def flags_only_if_simple(self):
- if self.parent.simple_binding is None:
- raise CompileError(
- "Only bindings with a single lookup can have flags",
- )
-
- @docs()
- def ref_docs(self):
- return get_docs_section("Syntax Binding")
+from .expression import Expression, LookupOp, LiteralExpr
class Binding(AstNode):
grammar = [
- AnyOf(Keyword("bind"), UseExact("bind", "bind-property")),
+ Keyword("bind"),
Expression,
- ZeroOrMore(BindingFlag),
]
@property
def expression(self) -> Expression:
return self.children[Expression][0]
- @property
- def flags(self) -> T.List[BindingFlag]:
- return self.children[BindingFlag]
-
@property
def simple_binding(self) -> T.Optional["SimpleBinding"]:
if isinstance(self.expression.last, LookupOp):
@@ -85,39 +40,14 @@ class Binding(AstNode):
from .values import IdentLiteral
if isinstance(self.expression.last.lhs.literal.value, IdentLiteral):
- flags = [x.flag for x in self.flags]
return SimpleBinding(
self.expression.last.lhs.literal.value.ident,
self.expression.last.property_name,
- no_sync_create="no-sync-create" in flags,
- bidirectional="bidirectional" in flags,
- inverted="inverted" in flags,
)
return None
- @validate("bind")
- def bind_property(self):
- if self.tokens["bind"] == "bind-property":
- raise UpgradeWarning(
- "'bind-property' is no longer needed. Use 'bind' instead. (blueprint 0.8.2)",
- actions=[CodeAction("use 'bind'", "bind")],
- )
-
- @docs("bind")
- def ref_docs(self):
- return get_docs_section("Syntax Binding")
-
@dataclass
class SimpleBinding:
source: str
property_name: str
- no_sync_create: bool = False
- bidirectional: bool = False
- inverted: bool = False
-
-
-@decompiler("binding")
-def decompile_binding(ctx: DecompileCtx, gir: gir.GirContext, name: str):
- ctx.end_block_with(";")
- ctx.print(f"{name}: bind ")
diff --git a/blueprintcompiler/language/common.py b/blueprintcompiler/language/common.py
index 1cc1b3b..9938bec 100644
--- a/blueprintcompiler/language/common.py
+++ b/blueprintcompiler/language/common.py
@@ -18,46 +18,36 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
-from .. import decompiler as decompile
from .. import gir
-from ..ast_utils import AstNode, context, docs, validate
+from ..ast_utils import AstNode, validate, docs, context
+from ..errors import (
+ CompileError,
+ MultipleErrors,
+ UpgradeWarning,
+ CompileWarning,
+ CodeAction,
+)
from ..completions_utils import *
+from .. import decompiler as decompile
from ..decompiler import (
DecompileCtx,
- decompile_translatable,
decompiler,
escape_quote,
truthy,
-)
-from ..errors import (
- CodeAction,
- CompileError,
- CompileWarning,
- DeprecatedWarning,
- MultipleErrors,
- UnusedWarning,
- UpgradeWarning,
+ decompile_translatable,
)
from ..gir import (
+ StringType,
BoolType,
- Enumeration,
- ExternType,
+ IntType,
FloatType,
GirType,
- IntType,
- StringType,
-)
-from ..lsp_utils import (
- Completion,
- CompletionItemKind,
- DocumentSymbol,
- LocationLink,
- SemanticToken,
- SemanticTokenType,
- SymbolKind,
- get_docs_section,
+ Enumeration,
+ ExternType,
)
+from ..lsp_utils import Completion, CompletionItemKind, SemanticToken, SemanticTokenType
from ..parse_tree import *
+
OBJECT_CONTENT_HOOKS = AnyOf()
LITERAL = AnyOf()
diff --git a/blueprintcompiler/language/contexts.py b/blueprintcompiler/language/contexts.py
index 6e26048..29d95de 100644
--- a/blueprintcompiler/language/contexts.py
+++ b/blueprintcompiler/language/contexts.py
@@ -30,7 +30,6 @@ from .gtkbuilder_template import Template
class ValueTypeCtx:
value_type: T.Optional[GirType]
allow_null: bool = False
- must_infer_type: bool = False
@dataclass
@@ -39,8 +38,8 @@ class ScopeCtx:
@cached_property
def template(self):
- from .gtk_list_item_factory import ExtListItemFactory
from .ui import UI
+ from .gtk_list_item_factory import ExtListItemFactory
if isinstance(self.node, UI):
return self.node.template
@@ -70,7 +69,8 @@ class ScopeCtx:
):
raise CompileError(
f"Duplicate object ID '{obj.tokens['id']}'",
- token.range,
+ token.start,
+ token.end,
)
passed[obj.tokens["id"]] = obj
@@ -79,9 +79,3 @@ class ScopeCtx:
for child in node.children:
if child.context[ScopeCtx] is self:
yield from self._iter_recursive(child)
-
-
-@dataclass
-class ExprValueCtx:
- """Indicates that the context is an expression literal, where the
- "item" keyword may be used."""
diff --git a/blueprintcompiler/language/expression.py b/blueprintcompiler/language/expression.py
index de6fbf1..314c753 100644
--- a/blueprintcompiler/language/expression.py
+++ b/blueprintcompiler/language/expression.py
@@ -18,10 +18,11 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
-from ..decompiler import decompile_element
from .common import *
from .contexts import ScopeCtx, ValueTypeCtx
from .types import TypeName
+from .gtkbuilder_template import Template
+
expr = Sequence()
@@ -38,6 +39,10 @@ class ExprBase(AstNode):
def type(self) -> T.Optional[GirType]:
raise NotImplementedError()
+ @property
+ def type_complete(self) -> bool:
+ return True
+
@property
def rhs(self) -> T.Optional["ExprBase"]:
if isinstance(self.parent, Expression):
@@ -61,6 +66,10 @@ class Expression(ExprBase):
def type(self) -> T.Optional[GirType]:
return self.last.type
+ @property
+ def type_complete(self) -> bool:
+ return self.last.type_complete
+
class InfixExpr(ExprBase):
@property
@@ -81,16 +90,6 @@ class LiteralExpr(ExprBase):
or self.root.is_legacy_template(self.literal.value.ident)
)
- @property
- def is_this(self) -> bool:
- from .values import IdentLiteral
-
- return (
- not self.is_object
- and isinstance(self.literal.value, IdentLiteral)
- and self.literal.value.ident == "item"
- )
-
@property
def literal(self):
from .values import Literal
@@ -101,14 +100,14 @@ class LiteralExpr(ExprBase):
def type(self) -> T.Optional[GirType]:
return self.literal.value.type
- @validate()
- def item_validations(self):
- if self.is_this:
- if not isinstance(self.rhs, CastExpr):
- raise CompileError('"item" must be cast to its object type')
+ @property
+ def type_complete(self) -> bool:
+ from .values import IdentLiteral
- if not isinstance(self.rhs.rhs, LookupOp):
- raise CompileError('"item" can only be used for looking up properties')
+ 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):
@@ -116,7 +115,7 @@ class LookupOp(InfixExpr):
@context(ValueTypeCtx)
def value_type(self) -> ValueTypeCtx:
- return ValueTypeCtx(None, must_infer_type=True)
+ return ValueTypeCtx(None)
@property
def property_name(self) -> str:
@@ -132,24 +131,9 @@ class LookupOp(InfixExpr):
return None
- @docs("property")
- def property_docs(self):
- if not (
- isinstance(self.lhs.type, gir.Class)
- or isinstance(self.lhs.type, gir.Interface)
- ):
- return None
-
- if property := self.lhs.type.properties.get(self.property_name):
- return property.doc
-
@validate("property")
def property_exists(self):
if self.lhs.type is None:
- # Literal values throw their own errors if the type isn't known
- if isinstance(self.lhs, LiteralExpr):
- return
-
raise CompileError(
f"Could not determine the type of the preceding expression",
hints=[
@@ -173,28 +157,10 @@ class LookupOp(InfixExpr):
did_you_mean=(self.property_name, self.lhs.type.properties.keys()),
)
- @validate("property")
- def property_deprecated(self):
- if self.lhs.type is None or not (
- isinstance(self.lhs.type, gir.Class)
- or isinstance(self.lhs.type, gir.Interface)
- ):
- return
-
- if property := self.lhs.type.properties.get(self.property_name):
- if property.deprecated:
- hints = []
- if property.deprecated_doc:
- hints.append(property.deprecated_doc)
- raise DeprecatedWarning(
- f"{property.signature} is deprecated",
- hints=hints,
- )
-
class CastExpr(InfixExpr):
grammar = [
- Keyword("as"),
+ "as",
AnyOf(
["<", TypeName, Match(">").expected()],
[
@@ -213,6 +179,10 @@ class CastExpr(InfixExpr):
def type(self) -> T.Optional[GirType]:
return self.children[TypeName][0].gir_type
+ @property
+ def type_complete(self) -> bool:
+ return True
+
@validate()
def cast_makes_sense(self):
if self.type is None or self.lhs.type is None:
@@ -236,10 +206,6 @@ class CastExpr(InfixExpr):
],
)
- @docs("as")
- def ref_docs(self):
- return get_docs_section("Syntax CastExpression")
-
class ClosureArg(AstNode):
grammar = Expression
@@ -289,96 +255,8 @@ class ClosureExpr(ExprBase):
if not self.tokens["extern"]:
raise CompileError(f"{self.closure_name} is not a builtin function")
- @docs("name")
- def ref_docs(self):
- return get_docs_section("Syntax ClosureExpression")
-
expr.children = [
AnyOf(ClosureExpr, LiteralExpr, ["(", Expression, ")"]),
ZeroOrMore(AnyOf(LookupOp, CastExpr)),
]
-
-
-@decompiler("lookup", skip_children=True, cdata=True)
-def decompile_lookup(
- ctx: DecompileCtx,
- gir: gir.GirContext,
- cdata: str,
- name: str,
- type: T.Optional[str] = None,
-):
- if ctx.parent_node is not None and ctx.parent_node.tag == "property":
- ctx.print("expr ")
-
- if type is None:
- type = ""
- elif t := ctx.type_by_cname(type):
- type = decompile.full_name(t)
- else:
- type = "$" + type
-
- assert ctx.current_node is not None
-
- constant = None
- if len(ctx.current_node.children) == 0:
- constant = cdata
- elif (
- len(ctx.current_node.children) == 1
- and ctx.current_node.children[0].tag == "constant"
- ):
- constant = ctx.current_node.children[0].cdata
-
- if constant is not None:
- if constant == ctx.template_class:
- ctx.print("template." + name)
- elif constant == "":
- ctx.print(f"item as <{type}>.{name}")
- else:
- ctx.print(constant + "." + name)
- return
- else:
- for child in ctx.current_node.children:
- decompile.decompile_element(ctx, gir, child)
-
- ctx.print(f" as <{type}>.{name}")
-
-
-@decompiler("constant", cdata=True)
-def decompile_constant(
- ctx: DecompileCtx, gir: gir.GirContext, cdata: str, type: T.Optional[str] = None
-):
- if ctx.parent_node is not None and ctx.parent_node.tag == "property":
- ctx.print("expr ")
-
- if type is None:
- if cdata == ctx.template_class:
- ctx.print("template")
- else:
- ctx.print(cdata)
- else:
- _, string = ctx.decompile_value(cdata, ctx.type_by_cname(type))
- ctx.print(string)
-
-
-@decompiler("closure", skip_children=True)
-def decompile_closure(ctx: DecompileCtx, gir: gir.GirContext, function: str, type: str):
- if ctx.parent_node is not None and ctx.parent_node.tag == "property":
- ctx.print("expr ")
-
- if t := ctx.type_by_cname(type):
- type = decompile.full_name(t)
- else:
- type = "$" + type
-
- ctx.print(f"${function}(")
-
- assert ctx.current_node is not None
- for i, node in enumerate(ctx.current_node.children):
- decompile_element(ctx, gir, node)
-
- assert ctx.current_node is not None
- if i < len(ctx.current_node.children) - 1:
- ctx.print(", ")
-
- ctx.end_block_with(f") as <{type}>")
diff --git a/blueprintcompiler/language/gobject_object.py b/blueprintcompiler/language/gobject_object.py
index 1def15b..183ad8e 100644
--- a/blueprintcompiler/language/gobject_object.py
+++ b/blueprintcompiler/language/gobject_object.py
@@ -21,25 +21,12 @@
import typing as T
from functools import cached_property
-from blueprintcompiler.errors import T
-from blueprintcompiler.lsp_utils import DocumentSymbol
-
from .common import *
from .response_id import ExtResponse
from .types import ClassName, ConcreteClassName
-RESERVED_IDS = {
- "this",
- "self",
- "template",
- "true",
- "false",
- "null",
- "none",
- "item",
- "expr",
- "typeof",
-}
+
+RESERVED_IDS = {"this", "self", "template", "true", "false", "null", "none"}
class ObjectContent(AstNode):
@@ -69,25 +56,6 @@ class Object(AstNode):
def content(self) -> ObjectContent:
return self.children[ObjectContent][0]
- @property
- def signature(self) -> str:
- if self.id:
- return f"{self.class_name.gir_type.full_name} {self.id}"
- elif t := self.class_name.gir_type:
- return f"{t.full_name}"
- else:
- return f"{self.class_name.as_string}"
-
- @property
- def document_symbol(self) -> T.Optional[DocumentSymbol]:
- return DocumentSymbol(
- self.class_name.as_string,
- SymbolKind.Object,
- self.range,
- self.children[ClassName][0].range,
- self.id,
- )
-
@property
def gir_class(self) -> GirType:
if self.class_name is None:
@@ -121,12 +89,12 @@ def validate_parent_type(node, ns: str, name: str, err_msg: str):
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 {ns}.{name}, so it doesn't have {err_msg}"
+ f"{container_type.full_name} is not a {parent.full_name}, so it doesn't have {err_msg}"
)
@decompiler("object")
-def decompile_object(ctx: DecompileCtx, gir, klass, id=None):
+def decompile_object(ctx, gir, klass, id=None):
gir_class = ctx.type_by_cname(klass)
klass_name = (
decompile.full_name(gir_class) if gir_class is not None else "$" + klass
@@ -135,5 +103,4 @@ def decompile_object(ctx: DecompileCtx, gir, klass, id=None):
ctx.print(f"{klass_name} {{")
else:
ctx.print(f"{klass_name} {id} {{")
- ctx.push_obj_type(gir_class)
return gir_class
diff --git a/blueprintcompiler/language/gobject_property.py b/blueprintcompiler/language/gobject_property.py
index 50a7512..09873bc 100644
--- a/blueprintcompiler/language/gobject_property.py
+++ b/blueprintcompiler/language/gobject_property.py
@@ -18,15 +18,17 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
-from .binding import Binding
+from .gtkbuilder_template import Template
+from .values import Value, ObjectValue
from .common import *
from .contexts import ValueTypeCtx
-from .values import ArrayValue, ExprValue, ObjectValue, Value
+from .property_binding import PropertyBinding
+from .binding import Binding
class Property(AstNode):
grammar = Statement(
- UseIdent("name"), ":", AnyOf(Binding, ExprValue, ObjectValue, Value, ArrayValue)
+ UseIdent("name"), ":", AnyOf(PropertyBinding, Binding, ObjectValue, Value)
)
@property
@@ -34,7 +36,7 @@ class Property(AstNode):
return self.tokens["name"]
@property
- def value(self) -> T.Union[Binding, ExprValue, ObjectValue, Value, ArrayValue]:
+ def value(self) -> T.Union[PropertyBinding, Binding, ObjectValue, Value]:
return self.children[0]
@property
@@ -42,31 +44,14 @@ class Property(AstNode):
return self.parent.parent.gir_class
@property
- def gir_property(self) -> T.Optional[gir.Property]:
+ def gir_property(self):
if self.gir_class is not None and not isinstance(self.gir_class, ExternType):
return self.gir_class.properties.get(self.tokens["name"])
- else:
- return None
-
- @property
- def document_symbol(self) -> DocumentSymbol:
- if isinstance(self.value, ObjectValue) or self.value is None:
- detail = None
- else:
- detail = self.value.range.text
-
- return DocumentSymbol(
- self.name,
- SymbolKind.Property,
- self.range,
- self.group.tokens["name"].range,
- detail,
- )
@validate()
def binding_valid(self):
if (
- isinstance(self.value, Binding)
+ (isinstance(self.value, PropertyBinding) or isinstance(self.value, Binding))
and self.gir_property is not None
and self.gir_property.construct_only
):
@@ -109,17 +94,6 @@ class Property(AstNode):
check=lambda child: child.tokens["name"] == self.tokens["name"],
)
- @validate("name")
- def deprecated(self) -> None:
- if self.gir_property is not None and self.gir_property.deprecated:
- hints = []
- if self.gir_property.deprecated_doc:
- hints.append(self.gir_property.deprecated_doc)
- raise DeprecatedWarning(
- f"{self.gir_property.signature} is deprecated",
- hints=hints,
- )
-
@docs("name")
def property_docs(self):
if self.gir_property is not None:
diff --git a/blueprintcompiler/language/gobject_signal.py b/blueprintcompiler/language/gobject_signal.py
index 3b4235f..f08168f 100644
--- a/blueprintcompiler/language/gobject_signal.py
+++ b/blueprintcompiler/language/gobject_signal.py
@@ -19,15 +19,14 @@
import typing as T
-from .common import *
-from .contexts import ScopeCtx
from .gtkbuilder_template import Template
+from .contexts import ScopeCtx
+from .common import *
class SignalFlag(AstNode):
grammar = AnyOf(
UseExact("flag", "swapped"),
- UseExact("flag", "not-swapped"),
UseExact("flag", "after"),
)
@@ -41,31 +40,6 @@ class SignalFlag(AstNode):
f"Duplicate flag '{self.flag}'", lambda x: x.flag == self.flag
)
- @validate()
- def swapped_exclusive(self):
- if self.flag in ["swapped", "not-swapped"]:
- self.validate_unique_in_parent(
- "'swapped' and 'not-swapped' flags cannot be used together",
- lambda x: x.flag in ["swapped", "not-swapped"],
- )
-
- @validate()
- def swapped_unnecessary(self):
- if self.flag == "not-swapped" and self.parent.object_id is None:
- raise CompileWarning(
- "'not-swapped' is the default for handlers that do not specify an object",
- actions=[CodeAction("Remove 'not-swapped' flag", "")],
- )
- elif self.flag == "swapped" and self.parent.object_id is not None:
- raise CompileWarning(
- "'swapped' is the default for handlers that specify an object",
- actions=[CodeAction("Remove 'swapped' flag", "")],
- )
-
- @docs()
- def ref_docs(self):
- return get_docs_section("Syntax Signal")
-
class Signal(AstNode):
grammar = Statement(
@@ -76,15 +50,13 @@ class Signal(AstNode):
UseIdent("detail_name").expected("a signal detail name"),
]
),
- Keyword("=>"),
- Mark("detail_start"),
+ "=>",
Optional(["$", UseLiteral("extern", True)]),
UseIdent("handler").expected("the name of a function to handle the signal"),
Match("(").expected("argument list"),
Optional(UseIdent("object")).expected("object identifier"),
Match(")").expected(),
ZeroOrMore(SignalFlag),
- Mark("detail_end"),
)
@property
@@ -114,54 +86,23 @@ class Signal(AstNode):
def flags(self) -> T.List[SignalFlag]:
return self.children[SignalFlag]
- # Returns True if the "swapped" flag is present, False if "not-swapped" is present, and None if neither are present.
- # GtkBuilder's default if swapped is not specified is to not swap the arguments if no object is specified, and to
- # swap them if an object is specified.
@property
- def is_swapped(self) -> T.Optional[bool]:
- for flag in self.flags:
- if flag.flag == "swapped":
- return True
- elif flag.flag == "not-swapped":
- return False
- return None
+ def is_swapped(self) -> bool:
+ return any(x.flag == "swapped" for x in self.flags)
@property
def is_after(self) -> bool:
return any(x.flag == "after" for x in self.flags)
@property
- def gir_signal(self) -> T.Optional[gir.Signal]:
+ def gir_signal(self):
if self.gir_class is not None and not isinstance(self.gir_class, ExternType):
return self.gir_class.signals.get(self.tokens["name"])
- else:
- return None
@property
def gir_class(self):
return self.parent.parent.gir_class
- @property
- def document_symbol(self) -> DocumentSymbol:
- detail = self.ranges["detail_start", "detail_end"]
- return DocumentSymbol(
- self.full_name,
- SymbolKind.Event,
- self.range,
- self.group.tokens["name"].range,
- detail.text if detail is not None else None,
- )
-
- def get_reference(self, idx: int) -> T.Optional[LocationLink]:
- if self.object_id is not None and idx in self.group.tokens["object"].range:
- obj = self.context[ScopeCtx].objects.get(self.object_id)
- if obj is not None:
- return LocationLink(
- self.group.tokens["object"].range, obj.range, obj.ranges["id"]
- )
-
- return None
-
@validate("handler")
def old_extern(self):
if not self.tokens["extern"]:
@@ -193,57 +134,18 @@ class Signal(AstNode):
if self.context[ScopeCtx].objects.get(object_id) is None:
raise CompileError(f"Could not find object with ID '{object_id}'")
- @validate("name")
- def deprecated(self) -> None:
- if self.gir_signal is not None and self.gir_signal.deprecated:
- hints = []
- if self.gir_signal.deprecated_doc:
- hints.append(self.gir_signal.deprecated_doc)
- raise DeprecatedWarning(
- f"{self.gir_signal.signature} is deprecated",
- hints=hints,
- )
-
@docs("name")
def signal_docs(self):
if self.gir_signal is not None:
return self.gir_signal.doc
- @docs("detail_name")
- def detail_docs(self):
- if self.name == "notify":
- if self.gir_class is not None and not isinstance(
- self.gir_class, ExternType
- ):
- prop = self.gir_class.properties.get(self.tokens["detail_name"])
- if prop is not None:
- return prop.doc
-
- @docs("=>")
- def ref_docs(self):
- return get_docs_section("Syntax Signal")
-
@decompiler("signal")
-def decompile_signal(
- ctx: DecompileCtx, gir, name, handler, swapped=None, after="false", object=None
-):
+def decompile_signal(ctx, gir, name, handler, swapped="false", object=None):
object_name = object or ""
-
- if object_name == ctx.template_class:
- object_name = "template"
-
name = name.replace("_", "-")
- line = f"{name} => ${handler}({object_name})"
-
if decompile.truthy(swapped):
- line += " swapped"
- elif swapped is not None:
- line += " not-swapped"
-
- if decompile.truthy(after):
- line += " after"
-
- line += ";"
- ctx.print(line)
+ ctx.print(f"{name} => ${handler}({object_name}) swapped;")
+ else:
+ ctx.print(f"{name} => ${handler}({object_name});")
return gir
diff --git a/blueprintcompiler/language/gtk_a11y.py b/blueprintcompiler/language/gtk_a11y.py
index 0cc3cb3..c378927 100644
--- a/blueprintcompiler/language/gtk_a11y.py
+++ b/blueprintcompiler/language/gtk_a11y.py
@@ -17,12 +17,12 @@
#
# SPDX-License-Identifier: LGPL-3.0-or-later
-import typing as T
-
+from .gobject_object import ObjectContent, validate_parent_type
+from .attributes import BaseAttribute
+from .values import Value
from .common import *
from .contexts import ValueTypeCtx
-from .gobject_object import ObjectContent, validate_parent_type
-from .values import Value
+from ..decompiler import escape_quote
def get_property_types(gir):
@@ -97,18 +97,7 @@ def get_types(gir):
}
-allow_duplicates = [
- "controls",
- "described-by",
- "details",
- "flow-to",
- "labelled-by",
- "owns",
-]
-
-
def _get_docs(gir, name):
- name = name.replace("-", "_")
if gir_type := (
gir.get_type("AccessibleProperty", "Gtk").members.get(name)
or gir.get_type("AccessibleRelation", "Gtk").members.get(name)
@@ -117,11 +106,11 @@ def _get_docs(gir, name):
return gir_type.doc
-class A11yProperty(AstNode):
+class A11yProperty(BaseAttribute):
grammar = Statement(
UseIdent("name"),
":",
- AnyOf(Value, ["[", UseLiteral("list_form", True), Delimited(Value, ","), "]"]),
+ Value,
)
@property
@@ -142,23 +131,13 @@ class A11yProperty(AstNode):
return self.tokens["name"].replace("_", "-")
@property
- def values(self) -> T.List[Value]:
- return list(self.children)
+ def value(self) -> Value:
+ return self.children[0]
@context(ValueTypeCtx)
def value_type(self) -> ValueTypeCtx:
return ValueTypeCtx(get_types(self.root.gir).get(self.tokens["name"]))
- @property
- def document_symbol(self) -> DocumentSymbol:
- return DocumentSymbol(
- self.name,
- SymbolKind.Field,
- self.range,
- self.group.tokens["name"].range,
- ", ".join(v.range.text for v in self.values),
- )
-
@validate("name")
def is_valid_property(self):
types = get_types(self.root.gir)
@@ -175,20 +154,6 @@ class A11yProperty(AstNode):
check=lambda child: child.tokens["name"] == self.tokens["name"],
)
- @validate("name")
- def list_only_allowed_for_subset(self):
- if self.tokens["list_form"] and self.tokens["name"] not in allow_duplicates:
- raise CompileError(
- f"'{self.tokens['name']}' does not allow a list of values",
- )
-
- @validate("name")
- def list_non_empty(self):
- if len(self.values) == 0:
- raise CompileError(
- f"'{self.tokens['name']}' may not be empty",
- )
-
@docs("name")
def prop_docs(self):
if self.tokens["name"] in get_types(self.root.gir):
@@ -206,15 +171,6 @@ class ExtAccessibility(AstNode):
def properties(self) -> T.List[A11yProperty]:
return self.children[A11yProperty]
- @property
- def document_symbol(self) -> DocumentSymbol:
- return DocumentSymbol(
- "accessibility",
- SymbolKind.Struct,
- self.range,
- self.group.tokens["accessibility"].range,
- )
-
@validate("accessibility")
def container_is_widget(self):
validate_parent_type(self, "Gtk", "Widget", "accessibility properties")
@@ -223,16 +179,12 @@ class ExtAccessibility(AstNode):
def unique_in_parent(self):
self.validate_unique_in_parent("Duplicate accessibility block")
- @docs("accessibility")
- def ref_docs(self):
- return get_docs_section("Syntax ExtAccessibility")
-
@completer(
applies_in=[ObjectContent],
matches=new_statement_patterns,
)
-def a11y_completer(lsp, ast_node, match_variables):
+def a11y_completer(ast_node, match_variables):
yield Completion(
"accessibility", CompletionItemKind.Snippet, snippet="accessibility {\n $0\n}"
)
@@ -242,46 +194,26 @@ def a11y_completer(lsp, ast_node, match_variables):
applies_in=[ExtAccessibility],
matches=new_statement_patterns,
)
-def a11y_name_completer(lsp, ast_node, match_variables):
+def a11y_name_completer(ast_node, match_variables):
for name, type in get_types(ast_node.root.gir).items():
yield Completion(
- name,
- CompletionItemKind.Property,
- docs=_get_docs(ast_node.root.gir, type.name),
+ name, CompletionItemKind.Property, docs=_get_docs(ast_node.root.gir, type)
)
-@decompiler("accessibility", skip_children=True, element=True)
-def decompile_accessibility(ctx: DecompileCtx, _gir, element):
+@decompiler("relation", cdata=True)
+def decompile_relation(ctx, gir, name, cdata):
+ ctx.print_attribute(name, cdata, get_types(ctx.gir).get(name))
+
+
+@decompiler("state", cdata=True)
+def decompile_state(ctx, gir, name, cdata, translatable="false"):
+ if decompile.truthy(translatable):
+ ctx.print(f'{name}: _("{escape_quote(cdata)}");')
+ else:
+ ctx.print_attribute(name, cdata, get_types(ctx.gir).get(name))
+
+
+@decompiler("accessibility")
+def decompile_accessibility(ctx, gir):
ctx.print("accessibility {")
- already_printed = set()
- types = get_types(ctx.gir)
-
- for child in element.children:
- name = child["name"]
-
- if name in allow_duplicates:
- if name in already_printed:
- continue
-
- ctx.print(f"{name}: [")
- for value in element.children:
- if value["name"] == name:
- comments, string = ctx.decompile_value(
- value.cdata,
- types.get(value["name"]),
- (value["translatable"], value["context"], value["comments"]),
- )
- ctx.print(f"{comments} {string},")
- ctx.print("];")
- else:
- comments, string = ctx.decompile_value(
- child.cdata,
- types.get(child["name"]),
- (child["translatable"], child["context"], child["comments"]),
- )
- ctx.print(f"{comments} {name}: {string};")
-
- already_printed.add(name)
- ctx.print("}")
- ctx.end_block_with("")
diff --git a/blueprintcompiler/language/gtk_combo_box_text.py b/blueprintcompiler/language/gtk_combo_box_text.py
index 32b3486..275327a 100644
--- a/blueprintcompiler/language/gtk_combo_box_text.py
+++ b/blueprintcompiler/language/gtk_combo_box_text.py
@@ -18,8 +18,9 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
-from .common import *
from .gobject_object import ObjectContent, validate_parent_type
+from .common import *
+from .contexts import ValueTypeCtx
from .values import StringValue
@@ -30,23 +31,13 @@ class Item(AstNode):
]
@property
- def name(self) -> T.Optional[str]:
+ def name(self) -> str:
return self.tokens["name"]
@property
def value(self) -> StringValue:
return self.children[StringValue][0]
- @property
- def document_symbol(self) -> DocumentSymbol:
- return DocumentSymbol(
- self.value.range.text,
- SymbolKind.String,
- self.range,
- self.value.range,
- self.name,
- )
-
@validate("name")
def unique_in_parent(self):
if self.name is not None:
@@ -54,10 +45,6 @@ class Item(AstNode):
f"Duplicate item '{self.name}'", lambda x: x.name == self.name
)
- @docs("name")
- def ref_docs(self):
- return get_docs_section("Syntax ExtComboBoxItems")
-
class ExtComboBoxItems(AstNode):
grammar = [
@@ -67,15 +54,6 @@ class ExtComboBoxItems(AstNode):
"]",
]
- @property
- def document_symbol(self) -> DocumentSymbol:
- return DocumentSymbol(
- "items",
- SymbolKind.Array,
- self.range,
- self.group.tokens["items"].range,
- )
-
@validate("items")
def container_is_combo_box_text(self):
validate_parent_type(self, "Gtk", "ComboBoxText", "combo box items")
@@ -84,41 +62,11 @@ class ExtComboBoxItems(AstNode):
def unique_in_parent(self):
self.validate_unique_in_parent("Duplicate items block")
- @docs("items")
- def ref_docs(self):
- return get_docs_section("Syntax ExtComboBoxItems")
-
@completer(
applies_in=[ObjectContent],
applies_in_subclass=("Gtk", "ComboBoxText"),
matches=new_statement_patterns,
)
-def items_completer(lsp, ast_node, match_variables):
+def items_completer(ast_node, match_variables):
yield Completion("items", CompletionItemKind.Snippet, snippet="items [$0]")
-
-
-@decompiler("items", parent_type="Gtk.ComboBoxText")
-def decompile_items(ctx: DecompileCtx, gir: gir.GirContext):
- ctx.print("items [")
-
-
-@decompiler("item", parent_type="Gtk.ComboBoxText", cdata=True)
-def decompile_item(
- ctx: DecompileCtx,
- gir: gir.GirContext,
- cdata: str,
- id: T.Optional[str] = None,
- translatable="false",
- comments=None,
- context=None,
-):
- comments, translatable = decompile_translatable(
- cdata, translatable, context, comments
- )
- if comments:
- ctx.print(comments)
- if id:
- ctx.print(f"{id}: ")
- ctx.print(translatable)
- ctx.print(",")
diff --git a/blueprintcompiler/language/gtk_file_filter.py b/blueprintcompiler/language/gtk_file_filter.py
index e84afc7..492f19e 100644
--- a/blueprintcompiler/language/gtk_file_filter.py
+++ b/blueprintcompiler/language/gtk_file_filter.py
@@ -18,34 +18,27 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
-from .common import *
from .gobject_object import ObjectContent, validate_parent_type
+from .common import *
class Filters(AstNode):
- @property
- def document_symbol(self) -> DocumentSymbol:
- return DocumentSymbol(
- self.tokens["tag_name"],
- SymbolKind.Array,
- self.range,
- self.group.tokens["tag_name"].range,
- )
-
@validate()
def container_is_file_filter(self):
validate_parent_type(self, "Gtk", "FileFilter", "file filter properties")
- @validate("tag_name")
+ @validate()
def unique_in_parent(self):
- self.validate_unique_in_parent(
- f"Duplicate {self.tokens['tag_name']} block",
- check=lambda child: child.tokens["tag_name"] == self.tokens["tag_name"],
- )
+ # The token argument to validate() needs to be calculated based on
+ # the instance, hence wrapping it like this.
+ @validate(self.tokens["tag_name"])
+ def wrapped_validator(self):
+ self.validate_unique_in_parent(
+ f"Duplicate {self.tokens['tag_name']} block",
+ check=lambda child: child.tokens["tag_name"] == self.tokens["tag_name"],
+ )
- @docs("tag_name")
- def ref_docs(self):
- return get_docs_section("Syntax ExtFileFilter")
+ wrapped_validator(self)
class FilterString(AstNode):
@@ -53,15 +46,6 @@ class FilterString(AstNode):
def item(self) -> str:
return self.tokens["name"]
- @property
- def document_symbol(self) -> DocumentSymbol:
- return DocumentSymbol(
- self.item,
- SymbolKind.String,
- self.range,
- self.group.tokens["name"].range,
- )
-
@validate()
def unique_in_parent(self):
self.validate_unique_in_parent(
@@ -74,7 +58,8 @@ def create_node(tag_name: str, singular: str):
return Group(
Filters,
[
- UseExact("tag_name", tag_name),
+ Keyword(tag_name),
+ UseLiteral("tag_name", tag_name),
"[",
Delimited(
Group(
@@ -101,7 +86,7 @@ ext_file_filter_suffixes = create_node("suffixes", "suffix")
applies_in_subclass=("Gtk", "FileFilter"),
matches=new_statement_patterns,
)
-def file_filter_completer(lsp, ast_node, match_variables):
+def file_filter_completer(ast_node, match_variables):
yield Completion(
"mime-types", CompletionItemKind.Snippet, snippet='mime-types ["$0"]'
)
@@ -116,7 +101,7 @@ def decompile_mime_types(ctx, gir):
@decompiler("mime-type", cdata=True)
def decompile_mime_type(ctx, gir, cdata):
- ctx.print(f"{escape_quote(cdata)},")
+ ctx.print(f'"{cdata}",')
@decompiler("patterns")
@@ -126,7 +111,7 @@ def decompile_patterns(ctx, gir):
@decompiler("pattern", cdata=True)
def decompile_pattern(ctx, gir, cdata):
- ctx.print(f"{escape_quote(cdata)},")
+ ctx.print(f'"{cdata}",')
@decompiler("suffixes")
@@ -136,4 +121,4 @@ def decompile_suffixes(ctx, gir):
@decompiler("suffix", cdata=True)
def decompile_suffix(ctx, gir, cdata):
- ctx.print(f"{escape_quote(cdata)},")
+ ctx.print(f'"{cdata}",')
diff --git a/blueprintcompiler/language/gtk_layout.py b/blueprintcompiler/language/gtk_layout.py
index 8d3e37a..cceb6c6 100644
--- a/blueprintcompiler/language/gtk_layout.py
+++ b/blueprintcompiler/language/gtk_layout.py
@@ -18,9 +18,9 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
+from .gobject_object import ObjectContent, validate_parent_type
from .common import *
from .contexts import ValueTypeCtx
-from .gobject_object import ObjectContent, validate_parent_type
from .values import Value
@@ -36,16 +36,6 @@ class LayoutProperty(AstNode):
def value(self) -> Value:
return self.children[Value][0]
- @property
- def document_symbol(self) -> DocumentSymbol:
- return DocumentSymbol(
- self.name,
- SymbolKind.Field,
- self.range,
- self.group.tokens["name"].range,
- self.value.range.text,
- )
-
@context(ValueTypeCtx)
def value_type(self) -> ValueTypeCtx:
# there isn't really a way to validate these
@@ -66,15 +56,6 @@ class ExtLayout(AstNode):
Until(LayoutProperty, "}"),
)
- @property
- def document_symbol(self) -> DocumentSymbol:
- return DocumentSymbol(
- "layout",
- SymbolKind.Struct,
- self.range,
- self.group.tokens["layout"].range,
- )
-
@validate("layout")
def container_is_widget(self):
validate_parent_type(self, "Gtk", "Widget", "layout properties")
@@ -83,17 +64,13 @@ class ExtLayout(AstNode):
def unique_in_parent(self):
self.validate_unique_in_parent("Duplicate layout block")
- @docs("layout")
- def ref_docs(self):
- return get_docs_section("Syntax ExtLayout")
-
@completer(
applies_in=[ObjectContent],
applies_in_subclass=("Gtk", "Widget"),
matches=new_statement_patterns,
)
-def layout_completer(lsp, ast_node, match_variables):
+def layout_completer(ast_node, match_variables):
yield Completion("layout", CompletionItemKind.Snippet, snippet="layout {\n $0\n}")
diff --git a/blueprintcompiler/language/gtk_list_item_factory.py b/blueprintcompiler/language/gtk_list_item_factory.py
index c9e1399..bbb3bda 100644
--- a/blueprintcompiler/language/gtk_list_item_factory.py
+++ b/blueprintcompiler/language/gtk_list_item_factory.py
@@ -1,40 +1,13 @@
-import typing as T
-
-from blueprintcompiler.errors import T
-from blueprintcompiler.lsp_utils import DocumentSymbol
-
+from .gobject_object import ObjectContent, validate_parent_type
+from ..parse_tree import Keyword
from ..ast_utils import AstNode, validate
from .common import *
-from .contexts import ScopeCtx
-from .gobject_object import ObjectContent, validate_parent_type
from .types import TypeName
+from .contexts import ScopeCtx
class ExtListItemFactory(AstNode):
- grammar = [
- UseExact("id", "template"),
- Mark("typename_start"),
- Optional(TypeName),
- Mark("typename_end"),
- ObjectContent,
- ]
-
- @property
- def id(self) -> str:
- return "template"
-
- @property
- def signature(self) -> str:
- return f"template {self.gir_class.full_name}"
-
- @property
- def document_symbol(self) -> DocumentSymbol:
- return DocumentSymbol(
- self.signature,
- SymbolKind.Object,
- self.range,
- self.group.tokens["id"].range,
- )
+ grammar = [UseExact("id", "template"), Optional(TypeName), ObjectContent]
@property
def type_name(self) -> T.Optional[TypeName]:
@@ -45,12 +18,9 @@ class ExtListItemFactory(AstNode):
@property
def gir_class(self):
- if self.type_name is not None:
- return self.type_name.gir_type
- 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):
validate_parent_type(
self,
@@ -59,24 +29,17 @@ class ExtListItemFactory(AstNode):
"sub-templates",
)
- @validate("id")
+ @validate("template")
def unique_in_parent(self):
self.validate_unique_in_parent("Duplicate template block")
- @validate("typename_start", "typename_end")
+ @validate()
def type_is_list_item(self):
if self.type_name is not None:
- if self.type_name.glib_type_name not in (
- "GtkListItem",
- "GtkListHeader",
- "GtkColumnViewRow",
- "GtkColumnViewCell",
- ):
- raise CompileError(
- f"Only Gtk.ListItem, Gtk.ListHeader, Gtk.ColumnViewRow, or Gtk.ColumnViewCell is allowed as a type here"
- )
+ if self.type_name.glib_type_name != "GtkListItem":
+ raise CompileError(f"Only Gtk.ListItem is allowed as a type here")
- @validate("id")
+ @validate("template")
def type_name_upgrade(self):
if self.type_name is None:
raise UpgradeWarning(
@@ -103,9 +66,8 @@ class ExtListItemFactory(AstNode):
@property
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
-
- @docs("id")
- def ref_docs(self):
- return get_docs_section("Syntax ExtListItemFactory")
diff --git a/blueprintcompiler/language/gtk_menu.py b/blueprintcompiler/language/gtk_menu.py
index c7ef5f2..5698e12 100644
--- a/blueprintcompiler/language/gtk_menu.py
+++ b/blueprintcompiler/language/gtk_menu.py
@@ -35,23 +35,6 @@ class Menu(AstNode):
def id(self) -> str:
return self.tokens["id"]
- @property
- def signature(self) -> str:
- if self.id:
- return f"Gio.Menu {self.id}"
- else:
- return "Gio.Menu"
-
- @property
- def document_symbol(self) -> DocumentSymbol:
- return DocumentSymbol(
- self.tokens["tag"],
- SymbolKind.Object,
- self.range,
- self.group.tokens[self.tokens["tag"]].range,
- self.id,
- )
-
@property
def tag(self) -> str:
return self.tokens["tag"]
@@ -70,25 +53,6 @@ class Menu(AstNode):
if self.id in RESERVED_IDS:
raise CompileWarning(f"{self.id} may be a confusing object ID")
- @docs("menu")
- def ref_docs_menu(self):
- return get_docs_section("Syntax Menu")
-
- @docs("section")
- def ref_docs_section(self):
- return get_docs_section("Syntax Menu")
-
- @docs("submenu")
- def ref_docs_submenu(self):
- return get_docs_section("Syntax Menu")
-
- @docs("item")
- def ref_docs_item(self):
- if self.tokens["shorthand"]:
- return get_docs_section("Syntax MenuItemShorthand")
- else:
- return get_docs_section("Syntax Menu")
-
class MenuAttribute(AstNode):
tag_name = "attribute"
@@ -101,20 +65,6 @@ class MenuAttribute(AstNode):
def value(self) -> StringValue:
return self.children[StringValue][0]
- @property
- def document_symbol(self) -> DocumentSymbol:
- return DocumentSymbol(
- self.name,
- SymbolKind.Field,
- self.range,
- (
- self.group.tokens["name"].range
- if self.group.tokens["name"]
- else self.range
- ),
- self.value.range.text,
- )
-
@context(ValueTypeCtx)
def value_type(self) -> ValueTypeCtx:
return ValueTypeCtx(None)
@@ -141,7 +91,7 @@ menu_attribute = Group(
menu_section = Group(
Menu,
[
- Keyword("section"),
+ "section",
UseLiteral("tag", "section"),
Optional(UseIdent("id")),
Match("{").expected(),
@@ -152,7 +102,7 @@ menu_section = Group(
menu_submenu = Group(
Menu,
[
- Keyword("submenu"),
+ "submenu",
UseLiteral("tag", "submenu"),
Optional(UseIdent("id")),
Match("{").expected(),
@@ -163,7 +113,7 @@ menu_submenu = Group(
menu_item = Group(
Menu,
[
- Keyword("item"),
+ "item",
UseLiteral("tag", "item"),
Match("{").expected(),
Until(menu_attribute, "}"),
@@ -173,9 +123,8 @@ menu_item = Group(
menu_item_shorthand = Group(
Menu,
[
- Keyword("item"),
+ "item",
UseLiteral("tag", "item"),
- UseLiteral("shorthand", True),
"(",
Group(
MenuAttribute,
@@ -243,7 +192,7 @@ from .ui import UI
applies_in=[UI],
matches=new_statement_patterns,
)
-def menu_completer(lsp, ast_node, match_variables):
+def menu_completer(ast_node, match_variables):
yield Completion("menu", CompletionItemKind.Snippet, snippet="menu {\n $0\n}")
@@ -251,7 +200,7 @@ def menu_completer(lsp, ast_node, match_variables):
applies_in=[Menu],
matches=new_statement_patterns,
)
-def menu_content_completer(lsp, ast_node, match_variables):
+def menu_content_completer(ast_node, match_variables):
yield Completion(
"submenu", CompletionItemKind.Snippet, snippet="submenu {\n $0\n}"
)
@@ -286,7 +235,7 @@ def decompile_submenu(ctx, gir, id=None):
ctx.print("submenu {")
-@decompiler("item", parent_tag="menu")
+@decompiler("item")
def decompile_item(ctx, gir, id=None):
if id:
ctx.print(f"item {id} {{")
diff --git a/blueprintcompiler/language/gtk_scale.py b/blueprintcompiler/language/gtk_scale.py
index 1fd5ac3..a81e03d 100644
--- a/blueprintcompiler/language/gtk_scale.py
+++ b/blueprintcompiler/language/gtk_scale.py
@@ -17,8 +17,8 @@
#
# SPDX-License-Identifier: LGPL-3.0-or-later
+from .gobject_object import validate_parent_type, ObjectContent
from .common import *
-from .gobject_object import ObjectContent, validate_parent_type
from .values import StringValue
@@ -58,24 +58,6 @@ class ExtScaleMark(AstNode):
else:
return None
- @property
- def document_symbol(self) -> DocumentSymbol:
- return DocumentSymbol(
- str(self.value),
- SymbolKind.Field,
- self.range,
- self.group.tokens["mark"].range,
- self.label.string if self.label else None,
- )
-
- def get_semantic_tokens(self) -> T.Iterator[SemanticToken]:
- if range := self.ranges["position"]:
- yield SemanticToken(
- range.start,
- range.end,
- SemanticTokenType.EnumMember,
- )
-
@docs("position")
def position_docs(self) -> T.Optional[str]:
if member := self.root.gir.get_type("PositionType", "Gtk").members.get(
@@ -94,10 +76,6 @@ class ExtScaleMark(AstNode):
did_you_mean=(self.position, positions.keys()),
)
- @docs("mark")
- def ref_docs(self):
- return get_docs_section("Syntax ExtScaleMarks")
-
class ExtScaleMarks(AstNode):
grammar = [
@@ -110,15 +88,6 @@ class ExtScaleMarks(AstNode):
def marks(self) -> T.List[ExtScaleMark]:
return self.children
- @property
- def document_symbol(self) -> DocumentSymbol:
- return DocumentSymbol(
- "marks",
- SymbolKind.Array,
- self.range,
- self.group.tokens["marks"].range,
- )
-
@validate("marks")
def container_is_size_group(self):
validate_parent_type(self, "Gtk", "Scale", "scale marks")
@@ -127,24 +96,20 @@ class ExtScaleMarks(AstNode):
def unique_in_parent(self):
self.validate_unique_in_parent("Duplicate 'marks' block")
- @docs("marks")
- def ref_docs(self):
- return get_docs_section("Syntax ExtScaleMarks")
-
@completer(
applies_in=[ObjectContent],
applies_in_subclass=("Gtk", "Scale"),
matches=new_statement_patterns,
)
-def complete_marks(lsp, ast_node, match_variables):
+def complete_marks(ast_node, match_variables):
yield Completion("marks", CompletionItemKind.Keyword, snippet="marks [\n\t$0\n]")
@completer(
applies_in=[ExtScaleMarks],
)
-def complete_mark(lsp, ast_node, match_variables):
+def complete_mark(ast_node, match_variables):
yield Completion("mark", CompletionItemKind.Keyword, snippet="mark ($0),")
diff --git a/blueprintcompiler/language/gtk_size_group.py b/blueprintcompiler/language/gtk_size_group.py
index 54d85e5..5ba4325 100644
--- a/blueprintcompiler/language/gtk_size_group.py
+++ b/blueprintcompiler/language/gtk_size_group.py
@@ -18,9 +18,9 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
+from .gobject_object import ObjectContent, validate_parent_type
from .common import *
from .contexts import ScopeCtx
-from .gobject_object import ObjectContent, validate_parent_type
class Widget(AstNode):
@@ -30,21 +30,6 @@ class Widget(AstNode):
def name(self) -> str:
return self.tokens["name"]
- @property
- def document_symbol(self) -> DocumentSymbol:
- return DocumentSymbol(
- self.name,
- SymbolKind.Field,
- self.range,
- self.group.tokens["name"].range,
- )
-
- def get_reference(self, _idx: int) -> T.Optional[LocationLink]:
- if obj := self.context[ScopeCtx].objects.get(self.name):
- return LocationLink(self.range, obj.range, obj.ranges["id"])
- else:
- return None
-
@validate("name")
def obj_widget(self):
object = self.context[ScopeCtx].objects.get(self.tokens["name"])
@@ -77,15 +62,6 @@ class ExtSizeGroupWidgets(AstNode):
"]",
]
- @property
- def document_symbol(self) -> DocumentSymbol:
- return DocumentSymbol(
- "widgets",
- SymbolKind.Array,
- self.range,
- self.group.tokens["widgets"].range,
- )
-
@validate("widgets")
def container_is_size_group(self):
validate_parent_type(self, "Gtk", "SizeGroup", "size group properties")
@@ -94,25 +70,11 @@ class ExtSizeGroupWidgets(AstNode):
def unique_in_parent(self):
self.validate_unique_in_parent("Duplicate widgets block")
- @docs("widgets")
- def ref_docs(self):
- return get_docs_section("Syntax ExtSizeGroupWidgets")
-
@completer(
applies_in=[ObjectContent],
applies_in_subclass=("Gtk", "SizeGroup"),
matches=new_statement_patterns,
)
-def size_group_completer(lsp, ast_node, match_variables):
+def size_group_completer(ast_node, match_variables):
yield Completion("widgets", CompletionItemKind.Snippet, snippet="widgets [$0]")
-
-
-@decompiler("widgets")
-def size_group_decompiler(ctx, gir: gir.GirContext):
- ctx.print("widgets [")
-
-
-@decompiler("widget")
-def widget_decompiler(ctx, gir: gir.GirContext, name: str):
- ctx.print(name + ",")
diff --git a/blueprintcompiler/language/gtk_string_list.py b/blueprintcompiler/language/gtk_string_list.py
index a146f35..78d9c67 100644
--- a/blueprintcompiler/language/gtk_string_list.py
+++ b/blueprintcompiler/language/gtk_string_list.py
@@ -18,9 +18,9 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
-from .common import *
from .gobject_object import ObjectContent, validate_parent_type
from .values import StringValue
+from .common import *
class Item(AstNode):
@@ -30,15 +30,6 @@ class Item(AstNode):
def child(self) -> StringValue:
return self.children[StringValue][0]
- @property
- def document_symbol(self) -> DocumentSymbol:
- return DocumentSymbol(
- self.child.range.text,
- SymbolKind.String,
- self.range,
- self.range,
- )
-
class ExtStringListStrings(AstNode):
grammar = [
@@ -48,16 +39,7 @@ class ExtStringListStrings(AstNode):
"]",
]
- @property
- def document_symbol(self) -> DocumentSymbol:
- return DocumentSymbol(
- "strings",
- SymbolKind.Array,
- self.range,
- self.group.tokens["strings"].range,
- )
-
- @validate("strings")
+ @validate("items")
def container_is_string_list(self):
validate_parent_type(self, "Gtk", "StringList", "StringList items")
@@ -65,37 +47,11 @@ class ExtStringListStrings(AstNode):
def unique_in_parent(self):
self.validate_unique_in_parent("Duplicate strings block")
- @docs("strings")
- def ref_docs(self):
- return get_docs_section("Syntax ExtStringListStrings")
-
@completer(
applies_in=[ObjectContent],
applies_in_subclass=("Gtk", "StringList"),
matches=new_statement_patterns,
)
-def strings_completer(lsp, ast_node, match_variables):
+def strings_completer(ast_node, match_variables):
yield Completion("strings", CompletionItemKind.Snippet, snippet="strings [$0]")
-
-
-@decompiler("items", parent_type="Gtk.StringList")
-def decompile_strings(ctx: DecompileCtx, gir: gir.GirContext):
- ctx.print("strings [")
-
-
-@decompiler("item", cdata=True, parent_type="Gtk.StringList")
-def decompile_item(
- ctx: DecompileCtx,
- gir: gir.GirContext,
- translatable="false",
- comments=None,
- context=None,
- cdata=None,
-):
- comments, translatable = decompile_translatable(
- cdata, translatable, context, comments
- )
- if comments is not None:
- ctx.print(comments)
- ctx.print(translatable + ",")
diff --git a/blueprintcompiler/language/gtk_styles.py b/blueprintcompiler/language/gtk_styles.py
index 8617522..6591aa5 100644
--- a/blueprintcompiler/language/gtk_styles.py
+++ b/blueprintcompiler/language/gtk_styles.py
@@ -18,8 +18,8 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
-from .common import *
from .gobject_object import ObjectContent, validate_parent_type
+from .common import *
class StyleClass(AstNode):
@@ -29,15 +29,6 @@ class StyleClass(AstNode):
def name(self) -> str:
return self.tokens["name"]
- @property
- def document_symbol(self) -> DocumentSymbol:
- return DocumentSymbol(
- self.name,
- SymbolKind.String,
- self.range,
- self.range,
- )
-
@validate("name")
def unique_in_parent(self):
self.validate_unique_in_parent(
@@ -53,15 +44,6 @@ class ExtStyles(AstNode):
"]",
]
- @property
- def document_symbol(self) -> DocumentSymbol:
- return DocumentSymbol(
- "styles",
- SymbolKind.Array,
- self.range,
- self.group.tokens["styles"].range,
- )
-
@validate("styles")
def container_is_widget(self):
validate_parent_type(self, "Gtk", "Widget", "style classes")
@@ -70,17 +52,13 @@ class ExtStyles(AstNode):
def unique_in_parent(self):
self.validate_unique_in_parent("Duplicate styles block")
- @docs("styles")
- def ref_docs(self):
- return get_docs_section("Syntax ExtStyles")
-
@completer(
applies_in=[ObjectContent],
applies_in_subclass=("Gtk", "Widget"),
matches=new_statement_patterns,
)
-def style_completer(lsp, ast_node, match_variables):
+def style_completer(ast_node, match_variables):
yield Completion("styles", CompletionItemKind.Keyword, snippet='styles ["$0"]')
diff --git a/blueprintcompiler/language/gtkbuilder_child.py b/blueprintcompiler/language/gtkbuilder_child.py
index bee551c..4de13f2 100644
--- a/blueprintcompiler/language/gtkbuilder_child.py
+++ b/blueprintcompiler/language/gtkbuilder_child.py
@@ -20,9 +20,9 @@
from functools import cached_property
-from .common import *
from .gobject_object import Object
-from .response_id import ExtResponse, decompile_response_type
+from .response_id import ExtResponse
+from .common import *
ALLOWED_PARENTS: T.List[T.Tuple[str, str]] = [
("Gtk", "Buildable"),
@@ -53,10 +53,6 @@ class ChildExtension(AstNode):
def child(self) -> ExtResponse:
return self.children[0]
- @docs()
- def ref_docs(self):
- return get_docs_section("Syntax ChildExtension")
-
class ChildAnnotation(AstNode):
grammar = ["[", AnyOf(ChildInternal, ChildExtension, ChildType), "]"]
@@ -92,7 +88,7 @@ class Child(AstNode):
hints = [
"only Gio.ListStore or Gtk.Buildable implementors can have children"
]
- if hasattr(gir_class, "properties") and "child" in gir_class.properties:
+ if "child" in gir_class.properties:
hints.append(
"did you mean to assign this object to the 'child' property?"
)
@@ -131,15 +127,10 @@ class Child(AstNode):
)
-@decompiler("child", element=True)
-def decompile_child(ctx, gir, element):
- if type := element["type"]:
- if type == "action":
- if decompiled := decompile_response_type(ctx.parent_node, element):
- ctx.print(decompiled)
- return
-
+@decompiler("child")
+def decompile_child(ctx, gir, type=None, internal_child=None):
+ if type is not None:
ctx.print(f"[{type}]")
- elif internal_child := element["internal-child"]:
+ elif internal_child is not None:
ctx.print(f"[internal-child {internal_child}]")
return gir
diff --git a/blueprintcompiler/language/gtkbuilder_template.py b/blueprintcompiler/language/gtkbuilder_template.py
index 96383eb..149152a 100644
--- a/blueprintcompiler/language/gtkbuilder_template.py
+++ b/blueprintcompiler/language/gtkbuilder_template.py
@@ -21,9 +21,9 @@ import typing as T
from blueprintcompiler.language.common import GirType
-from ..gir import TemplateType
-from .common import *
from .gobject_object import Object, ObjectContent
+from .common import *
+from ..gir import TemplateType
from .types import ClassName, TemplateClassName
@@ -44,22 +44,6 @@ class Template(Object):
def id(self) -> str:
return "template"
- @property
- def signature(self) -> str:
- if self.parent_type and self.parent_type.gir_type:
- return f"template {self.class_name.as_string} : {self.parent_type.gir_type.full_name}"
- else:
- return f"template {self.class_name.as_string}"
-
- @property
- def document_symbol(self) -> DocumentSymbol:
- return DocumentSymbol(
- self.signature,
- SymbolKind.Object,
- self.range,
- self.group.tokens["id"].range,
- )
-
@property
def gir_class(self) -> GirType:
if isinstance(self.class_name.gir_type, ExternType):
@@ -88,10 +72,6 @@ class Template(Object):
f"Only one template may be defined per file, but this file contains {len(self.parent.children[Template])}",
)
- @docs("id")
- def ref_docs(self):
- return get_docs_section("Syntax Template")
-
@decompiler("template")
def decompile_template(ctx: DecompileCtx, gir, klass, parent=None):
@@ -101,9 +81,8 @@ def decompile_template(ctx: DecompileCtx, gir, klass, parent=None):
else:
return "$" + cname
- if parent is None:
- ctx.print(f"template {class_name(klass)} {{")
- else:
- ctx.print(f"template {class_name(klass)} : {class_name(parent)} {{")
+ ctx.print(f"template {class_name(klass)} : {class_name(parent)} {{")
+
+ ctx.template_class = klass
return ctx.type_by_cname(klass) or ctx.type_by_cname(parent)
diff --git a/blueprintcompiler/language/imports.py b/blueprintcompiler/language/imports.py
index 2d4bcf6..e34901c 100644
--- a/blueprintcompiler/language/imports.py
+++ b/blueprintcompiler/language/imports.py
@@ -59,12 +59,14 @@ class GtkDirective(AstNode):
@property
def gir_namespace(self):
- # For better error handling, just assume it's 4.0
- return gir.get_namespace("Gtk", "4.0")
-
- @docs()
- def ref_docs(self):
- return get_docs_section("Syntax GtkDecl")
+ # 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
+ return gir.get_namespace("Gtk", "4.0")
class Import(AstNode):
@@ -84,26 +86,11 @@ class Import(AstNode):
@validate("namespace", "version")
def namespace_exists(self):
- gir.get_namespace(self.namespace, self.version)
-
- @validate()
- def unused(self):
- if self.namespace not in self.root.used_imports:
- raise UnusedWarning(
- f"Unused import: {self.namespace}",
- self.range,
- actions=[
- CodeAction("Remove import", "", self.range.with_trailing_newline)
- ],
- )
+ gir.get_namespace(self.tokens["namespace"], self.tokens["version"])
@property
def gir_namespace(self):
try:
- return gir.get_namespace(self.namespace, self.version)
+ return gir.get_namespace(self.tokens["namespace"], self.tokens["version"])
except CompileError:
return None
-
- @docs()
- def ref_docs(self):
- return get_docs_section("Syntax Using")
diff --git a/blueprintcompiler/language/property_binding.py b/blueprintcompiler/language/property_binding.py
new file mode 100644
index 0000000..686d1e8
--- /dev/null
+++ b/blueprintcompiler/language/property_binding.py
@@ -0,0 +1,145 @@
+# property_binding.py
+#
+# Copyright 2023 James Westman
+#
+# 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 .
+#
+# SPDX-License-Identifier: LGPL-3.0-or-later
+
+from .common import *
+from .contexts import ScopeCtx
+from .gobject_object import Object
+
+
+class PropertyBindingFlag(AstNode):
+ grammar = [
+ AnyOf(
+ UseExact("flag", "inverted"),
+ UseExact("flag", "bidirectional"),
+ UseExact("flag", "no-sync-create"),
+ UseExact("flag", "sync-create"),
+ )
+ ]
+
+ @property
+ def flag(self) -> str:
+ return self.tokens["flag"]
+
+ @validate()
+ def sync_create(self):
+ if self.flag == "sync-create":
+ raise UpgradeWarning(
+ "'sync-create' is now the default. Use 'no-sync-create' if this is not wanted.",
+ actions=[CodeAction("remove 'sync-create'", "")],
+ )
+
+ @validate()
+ def unique(self):
+ self.validate_unique_in_parent(
+ f"Duplicate flag '{self.flag}'", lambda x: x.flag == self.flag
+ )
+
+
+class PropertyBinding(AstNode):
+ grammar = AnyOf(
+ [
+ Keyword("bind-property"),
+ UseIdent("source"),
+ ".",
+ UseIdent("property"),
+ ZeroOrMore(PropertyBindingFlag),
+ ],
+ [
+ Keyword("bind"),
+ UseIdent("source"),
+ ".",
+ UseIdent("property"),
+ PropertyBindingFlag,
+ ZeroOrMore(PropertyBindingFlag),
+ ],
+ )
+
+ @property
+ def source(self) -> str:
+ return self.tokens["source"]
+
+ @property
+ def source_obj(self) -> T.Optional[Object]:
+ if self.root.is_legacy_template(self.source):
+ return self.root.template
+ return self.context[ScopeCtx].objects.get(self.source)
+
+ @property
+ def property_name(self) -> str:
+ return self.tokens["property"]
+
+ @property
+ def flags(self) -> T.List[PropertyBindingFlag]:
+ return self.children[PropertyBindingFlag]
+
+ @property
+ def inverted(self) -> bool:
+ return any([f.flag == "inverted" for f in self.flags])
+
+ @property
+ def bidirectional(self) -> bool:
+ return any([f.flag == "bidirectional" for f in self.flags])
+
+ @property
+ def no_sync_create(self) -> bool:
+ return any([f.flag == "no-sync-create" for f in self.flags])
+
+ @validate("source")
+ def source_object_exists(self) -> None:
+ if self.source_obj is None:
+ raise CompileError(
+ f"Could not find object with ID {self.source}",
+ did_you_mean=(self.source, self.context[ScopeCtx].objects.keys()),
+ )
+
+ @validate("property")
+ def property_exists(self) -> None:
+ if self.source_obj is None:
+ return
+
+ gir_class = self.source_obj.gir_class
+
+ if gir_class is None or gir_class.incomplete:
+ # Objects that we have no gir data on should not be validated
+ # This happens for classes defined by the app itself
+ return
+
+ if (
+ isinstance(gir_class, gir.Class)
+ and gir_class.properties.get(self.property_name) is None
+ ):
+ raise CompileError(
+ f"{gir_class.full_name} does not have a property called {self.property_name}"
+ )
+
+ @validate("bind")
+ def old_bind(self):
+ if self.tokens["bind"]:
+ raise UpgradeWarning(
+ "Use 'bind-property', introduced in blueprint 0.8.0, to use binding flags",
+ actions=[CodeAction("Use 'bind-property'", "bind-property")],
+ )
+
+ @validate("source")
+ def legacy_template(self):
+ if self.root.is_legacy_template(self.source):
+ raise UpgradeWarning(
+ "Use 'template' instead of the class name (introduced in 0.8.0)",
+ actions=[CodeAction("Use 'template'", "template")],
+ )
diff --git a/blueprintcompiler/language/response_id.py b/blueprintcompiler/language/response_id.py
index 939f71f..7de197c 100644
--- a/blueprintcompiler/language/response_id.py
+++ b/blueprintcompiler/language/response_id.py
@@ -123,42 +123,3 @@ class ExtResponse(AstNode):
object = self.parent_by_type(Child).object
return object.id
-
- @docs()
- def ref_docs(self):
- return get_docs_section("Syntax ExtResponse")
-
- @docs("response_id")
- def response_id_docs(self):
- if enum := self.root.gir.get_type("ResponseType", "Gtk"):
- if member := enum.members.get(self.response_id, None):
- return member.doc
-
-
-def decompile_response_type(parent_element, child_element):
- obj_id = None
- for obj in child_element.children:
- if obj.tag == "object":
- obj_id = obj["id"]
- break
-
- if obj_id is None:
- return None
-
- for child in parent_element.children:
- if child.tag == "action-widgets":
- for action_widget in child.children:
- if action_widget.cdata == obj_id:
- response_id = action_widget["response"]
- is_default = (
- " default" if decompile.truthy(action_widget["default"]) else ""
- )
- return f"[action response={response_id}{is_default}]"
-
- return None
-
-
-@decompiler("action-widgets", skip_children=True)
-def decompile_action_widgets(ctx, gir):
- # This is handled in the decompiler and decompile_response_type above
- pass
diff --git a/blueprintcompiler/language/types.py b/blueprintcompiler/language/types.py
index fe45c4d..dbce44f 100644
--- a/blueprintcompiler/language/types.py
+++ b/blueprintcompiler/language/types.py
@@ -18,8 +18,8 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
-from ..gir import Class, ExternType, Interface
from .common import *
+from ..gir import Class, ExternType, Interface
class TypeName(AstNode):
@@ -55,33 +55,12 @@ class TypeName(AstNode):
@validate("namespace")
def gir_ns_exists(self):
if not self.tokens["extern"]:
- try:
- self.root.gir.validate_ns(self.tokens["namespace"])
- except CompileError as e:
- ns = self.tokens["namespace"]
- e.actions = [
- self.root.import_code_action(n, version)
- for n, version in gir.get_available_namespaces()
- if n == ns
- ]
- raise e
-
- @validate()
- def deprecated(self) -> None:
- if self.gir_type and self.gir_type.deprecated:
- hints = []
- if self.gir_type.deprecated_doc:
- hints.append(self.gir_type.deprecated_doc)
- raise DeprecatedWarning(
- f"{self.gir_type.full_name} is deprecated",
- hints=hints,
- )
+ self.root.gir.validate_ns(self.tokens["namespace"])
@property
- def gir_ns(self) -> T.Optional[gir.Namespace]:
+ def gir_ns(self):
if not self.tokens["extern"]:
return self.root.gir.namespaces.get(self.tokens["namespace"] or "Gtk")
- return None
@property
def gir_type(self) -> gir.GirType:
diff --git a/blueprintcompiler/language/ui.py b/blueprintcompiler/language/ui.py
index d55a22a..033e2ca 100644
--- a/blueprintcompiler/language/ui.py
+++ b/blueprintcompiler/language/ui.py
@@ -17,17 +17,13 @@
#
# SPDX-License-Identifier: LGPL-3.0-or-later
-from functools import cached_property
-
from .. import gir
+from .imports import GtkDirective, Import
+from .gtkbuilder_template import Template
+from .gobject_object import Object
+from .gtk_menu import menu, Menu
from .common import *
from .contexts import ScopeCtx
-from .gobject_object import Object
-from .gtk_menu import Menu, menu
-from .gtkbuilder_template import Template
-from .imports import GtkDirective, Import
-from .translation_domain import TranslationDomain
-from .types import TypeName
class UI(AstNode):
@@ -36,7 +32,6 @@ class UI(AstNode):
grammar = [
GtkDirective,
ZeroOrMore(Import),
- Optional(TranslationDomain),
Until(
AnyOf(
Template,
@@ -47,7 +42,7 @@ class UI(AstNode):
),
]
- @cached_property
+ @property
def gir(self) -> gir.GirContext:
gir_ctx = gir.GirContext()
self._gir_errors = []
@@ -65,7 +60,8 @@ class UI(AstNode):
else:
gir_ctx.not_found_namespaces.add(i.namespace)
except CompileError as e:
- e.range = i.range
+ e.start = i.group.tokens["namespace"].start
+ e.end = i.group.tokens["version"].end
self._gir_errors.append(e)
return gir_ctx
@@ -78,14 +74,6 @@ class UI(AstNode):
def gtk_decl(self) -> GtkDirective:
return self.children[GtkDirective][0]
- @property
- def translation_domain(self) -> T.Optional[TranslationDomain]:
- domains = self.children[TranslationDomain]
- if len(domains):
- return domains[0]
- else:
- return None
-
@property
def contents(self) -> T.List[T.Union[Object, Template, Menu]]:
return [
@@ -110,34 +98,6 @@ class UI(AstNode):
and self.template.class_name.glib_type_name == id
)
- def import_code_action(self, ns: str, version: str) -> CodeAction:
- if len(self.children[Import]):
- pos = self.children[Import][-1].range.end
- else:
- pos = self.children[GtkDirective][0].range.end
-
- return CodeAction(
- f"Import {ns} {version}",
- f"\nusing {ns} {version};",
- Range(pos, pos, self.group.text),
- )
-
- @cached_property
- def used_imports(self) -> T.Optional[T.Set[str]]:
- def _iter_recursive(node: AstNode):
- yield node
- for child in node.children:
- if isinstance(child, AstNode):
- yield from _iter_recursive(child)
-
- result = set()
- for node in _iter_recursive(self):
- if isinstance(node, TypeName):
- ns = node.gir_ns
- if ns is not None:
- result.add(ns.name)
- return result
-
@context(ScopeCtx)
def scope_ctx(self) -> ScopeCtx:
return ScopeCtx(node=self)
diff --git a/blueprintcompiler/language/values.py b/blueprintcompiler/language/values.py
index 833a4a3..d0f3be5 100644
--- a/blueprintcompiler/language/values.py
+++ b/blueprintcompiler/language/values.py
@@ -19,14 +19,10 @@
import typing as T
-from blueprintcompiler.gir import ArrayType
-from blueprintcompiler.lsp_utils import SemanticToken
-
from .common import *
-from .contexts import ExprValueCtx, ScopeCtx, ValueTypeCtx
-from .expression import Expression
-from .gobject_object import Object
from .types import TypeName
+from .gobject_object import Object
+from .contexts import ScopeCtx, ValueTypeCtx
class Translated(AstNode):
@@ -58,23 +54,6 @@ class Translated(AstNode):
f"Cannot convert translated string to {expected_type.full_name}"
)
- @validate("context")
- def context_double_quoted(self):
- if self.translate_context is None:
- return
-
- if not str(self.group.tokens["context"]).startswith('"'):
- raise CompileWarning("gettext may not recognize single-quoted strings")
-
- @validate("string")
- def string_double_quoted(self):
- if not str(self.group.tokens["string"]).startswith('"'):
- raise CompileWarning("gettext may not recognize single-quoted strings")
-
- @docs()
- def ref_docs(self):
- return get_docs_section("Syntax Translated")
-
class TypeLiteral(AstNode):
grammar = [
@@ -120,10 +99,6 @@ class TypeLiteral(AstNode):
],
)
- @docs()
- def ref_docs(self):
- return get_docs_section("Syntax TypeLiteral")
-
class QuotedLiteral(AstNode):
grammar = UseQuoted("value")
@@ -225,22 +200,15 @@ class Flag(AstNode):
return self.tokens["value"]
@property
- def value(self) -> T.Optional[str]:
+ def value(self) -> T.Optional[int]:
type = self.context[ValueTypeCtx].value_type
if not isinstance(type, Enumeration):
return None
elif member := type.members.get(self.name):
- return member.nick
+ return member.value
else:
return None
- def get_semantic_tokens(self) -> T.Iterator[SemanticToken]:
- yield SemanticToken(
- self.group.tokens["value"].start,
- self.group.tokens["value"].end,
- SemanticTokenType.EnumMember,
- )
-
@docs()
def docs(self):
type = self.context[ValueTypeCtx].value_type
@@ -281,10 +249,6 @@ class Flags(AstNode):
if expected_type is not None and not isinstance(expected_type, gir.Bitfield):
raise CompileError(f"{expected_type.full_name} is not a bitfield type")
- @docs()
- def ref_docs(self):
- return get_docs_section("Syntax Flags")
-
class IdentLiteral(AstNode):
grammar = UseIdent("value")
@@ -327,18 +291,13 @@ class IdentLiteral(AstNode):
actions=[CodeAction("Use 'template'", "template")],
)
- elif expected_type is not None or self.context[ValueTypeCtx].must_infer_type:
+ elif expected_type is not None:
object = self.context[ScopeCtx].objects.get(self.ident)
if object is None:
if self.ident == "null":
if not self.context[ValueTypeCtx].allow_null:
raise CompileError("null is not permitted here")
- elif self.ident == "item":
- if not self.context[ExprValueCtx]:
- raise CompileError(
- '"item" can only be used in an expression literal'
- )
- elif self.ident not in ["true", "false"]:
+ else:
raise CompileError(
f"Could not find object with ID {self.ident}",
did_you_mean=(
@@ -346,31 +305,21 @@ class IdentLiteral(AstNode):
self.context[ScopeCtx].objects.keys(),
),
)
- elif (
- expected_type is not None
- and object.gir_class is not None
- and not object.gir_class.assignable_to(expected_type)
- ):
+ elif object.gir_class and not object.gir_class.assignable_to(expected_type):
raise CompileError(
f"Cannot assign {object.gir_class.full_name} to {expected_type.full_name}"
)
@docs()
def docs(self) -> T.Optional[str]:
- expected_type = self.context[ValueTypeCtx].value_type
- if isinstance(expected_type, gir.BoolType):
- return None
- elif isinstance(expected_type, gir.Enumeration):
- if member := expected_type.members.get(self.ident):
+ type = self.context[ValueTypeCtx].value_type
+ if isinstance(type, gir.Enumeration):
+ if member := type.members.get(self.ident):
return member.doc
else:
- return expected_type.doc
- elif self.ident == "null" and self.context[ValueTypeCtx].allow_null:
- return None
- elif object := self.context[ScopeCtx].objects.get(self.ident):
- return f"```\n{object.signature}\n```"
- elif self.root.is_legacy_template(self.ident):
- return f"```\n{self.root.template.signature}\n```"
+ return type.doc
+ elif isinstance(type, gir.GirNode):
+ return type.doc
else:
return None
@@ -380,16 +329,6 @@ class IdentLiteral(AstNode):
token = self.group.tokens["value"]
yield SemanticToken(token.start, token.end, SemanticTokenType.EnumMember)
- def get_reference(self, _idx: int) -> T.Optional[LocationLink]:
- ref = self.context[ScopeCtx].objects.get(self.ident)
- if ref is None and self.root.is_legacy_template(self.ident):
- ref = self.root.template
-
- if ref:
- return LocationLink(self.range, ref.range, ref.ranges["id"])
- else:
- return None
-
class Literal(AstNode):
grammar = AnyOf(
@@ -426,35 +365,6 @@ class ObjectValue(AstNode):
)
-class ExprValue(AstNode):
- grammar = [Keyword("expr"), Expression]
-
- @property
- def expression(self) -> Expression:
- return self.children[Expression][0]
-
- @validate("expr")
- def validate_for_type(self) -> None:
- expected_type = self.parent.context[ValueTypeCtx].value_type
- expr_type = self.root.gir.get_type("Expression", "Gtk")
- if expected_type is not None and not expected_type.assignable_to(expr_type):
- raise CompileError(
- f"Cannot convert Gtk.Expression to {expected_type.full_name}"
- )
-
- @docs("expr")
- def ref_docs(self):
- return get_docs_section("Syntax ExprValue")
-
- @context(ExprValueCtx)
- def expr_literal(self):
- return ExprValueCtx()
-
- @context(ValueTypeCtx)
- def value_type(self):
- return ValueTypeCtx(None, must_infer_type=True)
-
-
class Value(AstNode):
grammar = AnyOf(Translated, Flags, Literal)
@@ -465,68 +375,6 @@ class Value(AstNode):
return self.children[0]
-class ArrayValue(AstNode):
- grammar = ["[", Delimited(Value, ","), "]"]
-
- @validate()
- def validate_for_type(self) -> None:
- expected_type = self.gir_type
- if expected_type is not None and not isinstance(expected_type, gir.ArrayType):
- raise CompileError(f"Cannot assign array to {expected_type.full_name}")
-
- if expected_type is not None and not isinstance(
- expected_type.inner, StringType
- ):
- raise CompileError("Only string arrays are supported")
-
- @validate()
- def validate_invalid_newline(self) -> None:
- expected_type = self.gir_type
- if isinstance(expected_type, gir.ArrayType) and isinstance(
- expected_type.inner, StringType
- ):
- errors = []
- for value in self.values:
- if isinstance(value.child, Literal) and isinstance(
- value.child.value, QuotedLiteral
- ):
- quoted_literal = value.child.value
- literal_value = quoted_literal.value
- # literal_value can be None if there's an invalid escape sequence
- if literal_value is not None and "\n" in literal_value:
- errors.append(
- CompileError(
- "String literals inside arrays can't contain newlines",
- range=quoted_literal.range,
- )
- )
- elif isinstance(value.child, Translated):
- errors.append(
- CompileError(
- "Arrays can't contain translated strings",
- range=value.child.range,
- )
- )
-
- if len(errors) > 0:
- raise MultipleErrors(errors)
-
- @property
- def values(self) -> T.List[Value]:
- return self.children
-
- @property
- def gir_type(self):
- return self.parent.context[ValueTypeCtx].value_type
-
- @context(ValueTypeCtx)
- def child_value(self):
- if self.gir_type is None or not isinstance(self.gir_type, ArrayType):
- return ValueTypeCtx(None)
- else:
- return ValueTypeCtx(self.gir_type.inner)
-
-
class StringValue(AstNode):
grammar = AnyOf(Translated, QuotedLiteral)
diff --git a/blueprintcompiler/lsp.py b/blueprintcompiler/lsp.py
index c4076b4..b44d631 100644
--- a/blueprintcompiler/lsp.py
+++ b/blueprintcompiler/lsp.py
@@ -18,19 +18,14 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
-import json
-import sys
-import traceback
import typing as T
-from difflib import SequenceMatcher
+import json, sys, traceback
-from . import decompiler, formatter, parser, tokenizer, utils, xml_reader
-from .ast_utils import AstNode
from .completions import complete
-from .errors import CompileError, MultipleErrors
+from .errors import PrintableError, CompileError, MultipleErrors
from .lsp_utils import *
from .outputs.xml import XmlOutput
-from .tokenizer import Token
+from . import tokenizer, parser, utils, xml_reader, decompiler
def printerr(*args, **kwargs):
@@ -46,16 +41,16 @@ def command(json_method: str):
class OpenFile:
- def __init__(self, uri: str, text: str, version: int) -> None:
+ def __init__(self, uri: str, text: str, version: int):
self.uri = uri
self.text = text
self.version = version
- self.ast: T.Optional[AstNode] = None
- self.tokens: T.Optional[list[Token]] = None
+ self.ast = None
+ self.tokens = None
self._update()
- def apply_changes(self, changes) -> None:
+ def apply_changes(self, changes):
for change in changes:
if "range" not in change:
self.text = change["text"]
@@ -73,8 +68,8 @@ class OpenFile:
self.text = self.text[:start] + change["text"] + self.text[end:]
self._update()
- def _update(self) -> None:
- self.diagnostics: list[CompileError] = []
+ def _update(self):
+ self.diagnostics = []
try:
self.tokens = tokenizer.tokenize(self.text)
self.ast, errors, warnings = parser.parse(self.tokens)
@@ -102,10 +97,10 @@ class OpenFile:
]
# convert line, column numbers to deltas
- for a, b in zip(token_lists[-2::-1], token_lists[:0:-1]):
- b[0] -= a[0]
- if b[0] == 0:
- b[1] -= a[1]
+ for i, token_list in enumerate(token_lists[1:]):
+ token_list[0] -= token_lists[i][0]
+ if token_list[0] == 0:
+ token_list[1] -= token_lists[i][1]
# flatten the list
return [x for y in token_lists for x in y]
@@ -116,9 +111,7 @@ class LanguageServer:
def __init__(self):
self.client_capabilities = {}
- self.client_supports_completion_choice = False
self._open_files: T.Dict[str, OpenFile] = {}
- self._exited = False
def run(self):
# Read tags from gir files. During normal compilation these are
@@ -126,7 +119,7 @@ class LanguageServer:
xml_reader.PARSE_GIR.add("doc")
try:
- while not self._exited:
+ while True:
line = ""
content_len = -1
while content_len == -1 or (line != "\n" and line != "\r\n"):
@@ -150,7 +143,7 @@ class LanguageServer:
def _send(self, data):
data["jsonrpc"] = "2.0"
- line = json.dumps(data, separators=(",", ":"))
+ line = json.dumps(data, separators=(",", ":")) + "\r\n"
printerr("output: " + line)
sys.stdout.write(
f"Content-Length: {len(line.encode())}\r\nContent-Type: application/vscode-jsonrpc; charset=utf-8\r\n\r\n{line}"
@@ -190,9 +183,6 @@ class LanguageServer:
from . import main
self.client_capabilities = params.get("capabilities", {})
- self.client_supports_completion_choice = params.get("clientInfo", {}).get(
- "name"
- ) in ["Visual Studio Code", "VSCodium"]
self._send_response(
id,
{
@@ -204,16 +194,12 @@ class LanguageServer:
"semanticTokensProvider": {
"legend": {
"tokenTypes": ["enumMember"],
- "tokenModifiers": [],
},
"full": True,
},
"completionProvider": {},
"codeActionProvider": {},
"hoverProvider": True,
- "documentSymbolProvider": True,
- "definitionProvider": True,
- "documentFormattingProvider": True,
},
"serverInfo": {
"name": "Blueprint",
@@ -222,14 +208,6 @@ class LanguageServer:
},
)
- @command("shutdown")
- def shutdown(self, id, params):
- self._send_response(id, None)
-
- @command("exit")
- def exit(self, id, params):
- self._exited = True
-
@command("textDocument/didOpen")
def didOpen(self, id, params):
doc = params.get("textDocument")
@@ -286,43 +264,11 @@ class LanguageServer:
idx = utils.pos_to_idx(
params["position"]["line"], params["position"]["character"], open_file.text
)
- completions = complete(self, open_file.ast, open_file.tokens, idx)
+ completions = complete(open_file.ast, open_file.tokens, idx)
self._send_response(
id, [completion.to_json(True) for completion in completions]
)
- @command("textDocument/formatting")
- def formatting(self, id, params):
- open_file = self._open_files[params["textDocument"]["uri"]]
-
- if open_file.text is None:
- self._send_error(id, ErrorCode.RequestFailed, "Document is not open")
- return
-
- try:
- formatted_blp = formatter.format(
- open_file.text,
- params["options"]["tabSize"],
- params["options"]["insertSpaces"],
- )
- except PrintableError:
- self._send_error(id, ErrorCode.RequestFailed, "Could not format document")
- return
-
- lst = []
- for tag, i1, i2, j1, j2 in SequenceMatcher(
- None, open_file.text, formatted_blp
- ).get_opcodes():
- if tag in ("replace", "insert", "delete"):
- lst.append(
- TextEdit(
- Range(i1, i2, open_file.text),
- "" if tag == "delete" else formatted_blp[j1:j2],
- ).to_json()
- )
-
- self._send_response(id, lst)
-
@command("textDocument/x-blueprint-compile")
def compile(self, id, params):
open_file = self._open_files[params["textDocument"]["uri"]]
@@ -334,7 +280,7 @@ class LanguageServer:
xml = None
try:
output = XmlOutput()
- xml = output.emit(open_file.ast, indent=2, generated_notice=False)
+ xml = output.emit(open_file.ast)
except:
printerr(traceback.format_exc())
self._send_error(id, ErrorCode.RequestFailed, "Could not compile document")
@@ -345,19 +291,16 @@ class LanguageServer:
def decompile(self, id, params):
text = params.get("text")
blp = None
- if text.strip() == "":
- blp = ""
- printerr("Decompiled to empty blueprint because input was empty")
- else:
- try:
- blp = decompiler.decompile_string(text)
- except decompiler.UnsupportedError as e:
- self._send_error(id, ErrorCode.RequestFailed, e.message)
- return
- except:
- printerr(traceback.format_exc())
- self._send_error(id, ErrorCode.RequestFailed, "Invalid input")
- return
+
+ try:
+ blp = decompiler.decompile_string(text)
+ except decompiler.UnsupportedError as e:
+ self._send_error(id, ErrorCode.RequestFailed, e.message)
+ return
+ except:
+ printerr(traceback.format_exc())
+ self._send_error(id, ErrorCode.RequestFailed, "Invalid input")
+ return
self._send_response(id, {"blp": blp})
@@ -376,17 +319,14 @@ class LanguageServer:
def code_actions(self, id, params):
open_file = self._open_files[params["textDocument"]["uri"]]
- range = Range(
- utils.pos_to_idx(
- params["range"]["start"]["line"],
- params["range"]["start"]["character"],
- open_file.text,
- ),
- utils.pos_to_idx(
- params["range"]["end"]["line"],
- params["range"]["end"]["character"],
- open_file.text,
- ),
+ range_start = utils.pos_to_idx(
+ params["range"]["start"]["line"],
+ params["range"]["start"]["character"],
+ open_file.text,
+ )
+ range_end = utils.pos_to_idx(
+ params["range"]["end"]["line"],
+ params["range"]["end"]["character"],
open_file.text,
)
@@ -394,15 +334,15 @@ class LanguageServer:
{
"title": action.title,
"kind": "quickfix",
- "diagnostics": [self._create_diagnostic(open_file.uri, diagnostic)],
+ "diagnostics": [
+ self._create_diagnostic(open_file.text, open_file.uri, diagnostic)
+ ],
"edit": {
"changes": {
open_file.uri: [
{
- "range": (
- action.edit_range.to_json()
- if action.edit_range
- else diagnostic.range.to_json()
+ "range": utils.idxs_to_range(
+ diagnostic.start, diagnostic.end, open_file.text
),
"newText": action.replace_with,
}
@@ -411,88 +351,46 @@ class LanguageServer:
},
}
for diagnostic in open_file.diagnostics
- if range.overlaps(diagnostic.range)
+ if not (diagnostic.end < range_start or diagnostic.start > range_end)
for action in diagnostic.actions
]
self._send_response(id, actions)
- @command("textDocument/documentSymbol")
- def document_symbols(self, id, params):
- open_file = self._open_files[params["textDocument"]["uri"]]
- symbols = open_file.ast.get_document_symbols()
-
- def to_json(symbol: DocumentSymbol):
- result = {
- "name": symbol.name,
- "kind": symbol.kind,
- "range": symbol.range.to_json(),
- "selectionRange": symbol.selection_range.to_json(),
- "children": [to_json(child) for child in symbol.children],
- }
- if symbol.detail is not None:
- result["detail"] = symbol.detail
- return result
-
- self._send_response(id, [to_json(symbol) for symbol in symbols])
-
- @command("textDocument/definition")
- def definition(self, id, params):
- open_file = self._open_files[params["textDocument"]["uri"]]
- idx = utils.pos_to_idx(
- params["position"]["line"], params["position"]["character"], open_file.text
- )
- definition = open_file.ast.get_reference(idx)
- if definition is None:
- self._send_response(id, None)
- else:
- self._send_response(
- id,
- definition.to_json(open_file.uri),
- )
-
def _send_file_updates(self, open_file: OpenFile):
self._send_notification(
"textDocument/publishDiagnostics",
{
"uri": open_file.uri,
"diagnostics": [
- self._create_diagnostic(open_file.uri, err)
+ self._create_diagnostic(open_file.text, open_file.uri, err)
for err in open_file.diagnostics
],
},
)
- def _create_diagnostic(self, uri: str, err: CompileError):
+ def _create_diagnostic(self, text: str, uri: str, err: CompileError):
message = err.message
- assert err.range is not None
+ assert err.start is not None and err.end is not None
for hint in err.hints:
message += "\nhint: " + hint
result = {
- "range": err.range.to_json(),
+ "range": utils.idxs_to_range(err.start, err.end, text),
"message": message,
- "severity": (
- DiagnosticSeverity.Warning
- if isinstance(err, CompileWarning)
- else DiagnosticSeverity.Error
- ),
+ "severity": DiagnosticSeverity.Warning
+ if isinstance(err, CompileWarning)
+ else DiagnosticSeverity.Error,
}
- if isinstance(err, DeprecatedWarning):
- result["tags"] = [DiagnosticTag.Deprecated]
-
- if isinstance(err, UnusedWarning):
- result["tags"] = [DiagnosticTag.Unnecessary]
-
if len(err.references) > 0:
result["relatedInformation"] = [
{
"location": {
"uri": uri,
- "range": ref.range.to_json(),
+ "range": utils.idxs_to_range(ref.start, ref.end, text),
},
"message": ref.message,
}
diff --git a/blueprintcompiler/lsp_utils.py b/blueprintcompiler/lsp_utils.py
index b938181..219cade 100644
--- a/blueprintcompiler/lsp_utils.py
+++ b/blueprintcompiler/lsp_utils.py
@@ -18,14 +18,11 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
+from dataclasses import dataclass
import enum
-import json
-import os
import typing as T
-from dataclasses import dataclass, field
from .errors import *
-from .tokenizer import Range
from .utils import *
@@ -82,11 +79,9 @@ class Completion:
kind: CompletionItemKind
signature: T.Optional[str] = None
deprecated: bool = False
- sort_text: T.Optional[str] = None
docs: T.Optional[str] = None
text: T.Optional[str] = None
snippet: T.Optional[str] = None
- detail: T.Optional[str] = None
def to_json(self, snippets: bool):
insert_text = self.text or self.label
@@ -99,21 +94,16 @@ class Completion:
"label": self.label,
"kind": self.kind,
"tags": [CompletionItemTag.Deprecated] if self.deprecated else None,
- # https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#completionItemLabelDetails
- "labelDetails": ({"detail": self.signature} if self.signature else None),
- "documentation": (
- {
- "kind": "markdown",
- "value": self.docs,
- }
- if self.docs
- else None
- ),
+ "detail": self.signature,
+ "documentation": {
+ "kind": "markdown",
+ "value": self.docs,
+ }
+ if self.docs
+ else None,
"deprecated": self.deprecated,
- "sortText": self.sort_text,
"insertText": insert_text,
"insertTextFormat": insert_text_format,
- "detail": self.detail if self.detail else None,
}
return {k: v for k, v in result.items() if v is not None}
@@ -129,100 +119,8 @@ class DiagnosticSeverity(enum.IntEnum):
Hint = 4
-class DiagnosticTag(enum.IntEnum):
- Unnecessary = 1
- Deprecated = 2
-
-
@dataclass
class SemanticToken:
start: int
end: int
type: SemanticTokenType
-
-
-class SymbolKind(enum.IntEnum):
- File = 1
- Module = 2
- Namespace = 3
- Package = 4
- Class = 5
- Method = 6
- Property = 7
- Field = 8
- Constructor = 9
- Enum = 10
- Interface = 11
- Function = 12
- Variable = 13
- Constant = 14
- String = 15
- Number = 16
- Boolean = 17
- Array = 18
- Object = 19
- Key = 20
- Null = 21
- EnumMember = 22
- Struct = 23
- Event = 24
- Operator = 25
- TypeParameter = 26
-
-
-@dataclass
-class DocumentSymbol:
- name: str
- kind: SymbolKind
- range: Range
- selection_range: Range
- detail: T.Optional[str] = None
- children: T.List["DocumentSymbol"] = field(default_factory=list)
-
-
-@dataclass
-class LocationLink:
- origin_selection_range: Range
- target_range: Range
- target_selection_range: Range
-
- def to_json(self, target_uri: str):
- return {
- "originSelectionRange": self.origin_selection_range.to_json(),
- "targetUri": target_uri,
- "targetRange": self.target_range.to_json(),
- "targetSelectionRange": self.target_selection_range.to_json(),
- }
-
-
-@dataclass
-class TextEdit:
- range: Range
- newText: str
-
- def to_json(self):
- return {"range": self.range.to_json(), "newText": self.newText}
-
-
-_docs_sections: T.Optional[dict[str, T.Any]] = None
-
-
-def get_docs_section(section_name: str) -> T.Optional[str]:
- global _docs_sections
-
- if _docs_sections is None:
- try:
- with open(
- os.path.join(os.path.dirname(__file__), "reference_docs.json")
- ) as f:
- _docs_sections = json.load(f)
- except FileNotFoundError:
- _docs_sections = {}
-
- if section := _docs_sections.get(section_name):
- content = section["content"]
- link = section["link"]
- content += f"\n\n---\n\n[Online documentation]({link})"
- return content
- else:
- return None
diff --git a/blueprintcompiler/main.py b/blueprintcompiler/main.py
index 1c3a1c6..6ac7a11 100644
--- a/blueprintcompiler/main.py
+++ b/blueprintcompiler/main.py
@@ -18,19 +18,15 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
-import argparse
-import difflib
-import os
-import sys
import typing as T
+import argparse, json, os, sys
-from . import formatter, interactive_port, parser, tokenizer
-from .decompiler import decompile_string
-from .errors import CompileError, CompilerBugError, PrintableError, report_bug
+from .errors import PrintableError, report_bug, MultipleErrors, CompilerBugError
from .gir import add_typelib_search_path
from .lsp import LanguageServer
-from .outputs import XmlOutput
+from . import parser, tokenizer, decompiler, interactive_port
from .utils import Colors
+from .outputs import XmlOutput
VERSION = "uninstalled"
LIBDIR = None
@@ -67,52 +63,6 @@ class BlueprintApp:
type=argparse.FileType("r"),
)
- format = self.add_subcommand(
- "format", "Format given blueprint files", self.cmd_format
- )
- format.add_argument(
- "-f",
- "--fix",
- help="Apply the edits to the files",
- default=False,
- action="store_true",
- )
- format.add_argument(
- "-t",
- "--tabs",
- help="Use tabs instead of spaces",
- default=False,
- action="store_true",
- )
- format.add_argument(
- "-s",
- "--spaces-num",
- help="How many spaces should be used per indent",
- default=2,
- type=int,
- )
- format.add_argument(
- "-n",
- "--no-diff",
- help="Do not print a full diff of the changes",
- default=False,
- action="store_true",
- )
- format.add_argument(
- "inputs",
- nargs="+",
- metavar="filenames",
- )
-
- decompile = self.add_subcommand(
- "decompile", "Convert .ui XML files to blueprint", self.cmd_decompile
- )
- decompile.add_argument("--output", dest="output", default="-")
- decompile.add_argument("--typelib-path", nargs="?", action="append")
- decompile.add_argument(
- "input", metavar="filename", default=sys.stdin, type=argparse.FileType("r")
- )
-
port = self.add_subcommand("port", "Interactive porting tool", self.cmd_port)
lsp = self.add_subcommand(
@@ -171,11 +121,9 @@ class BlueprintApp:
for file in opts.inputs:
data = file.read()
- file_abs = os.path.abspath(file.name)
- input_dir_abs = os.path.abspath(opts.input_dir)
try:
- if not os.path.commonpath([file_abs, input_dir_abs]):
+ if not os.path.commonpath([file.name, opts.input_dir]):
print(
f"{Colors.RED}{Colors.BOLD}error: input file '{file.name}' is not in input directory '{opts.input_dir}'{Colors.CLEAR}"
)
@@ -199,135 +147,6 @@ class BlueprintApp:
e.pretty_print(file.name, data)
sys.exit(1)
- def cmd_format(self, opts):
- input_files = []
- missing_files = []
- panic = False
- formatted_files = 0
- skipped_files = 0
-
- for path in opts.inputs:
- if os.path.isfile(path):
- input_files.append(path)
- elif os.path.isdir(path):
- for root, subfolders, files in os.walk(path):
- for file in files:
- if file.endswith(".blp"):
- input_files.append(os.path.join(root, file))
- else:
- missing_files.append(path)
-
- for file in input_files:
- with open(file, "r+") as file:
- data = file.read()
- errored = False
-
- try:
- self._compile(data)
- except:
- errored = True
-
- formatted_str = formatter.format(data, opts.spaces_num, not opts.tabs)
-
- if data != formatted_str:
- happened = "Would format"
-
- if opts.fix and not errored:
- file.seek(0)
- file.truncate()
- file.write(formatted_str)
- happened = "Formatted"
-
- if not opts.no_diff:
- diff_lines = []
- a_lines = data.splitlines(keepends=True)
- b_lines = formatted_str.splitlines(keepends=True)
-
- for line in difflib.unified_diff(
- a_lines, b_lines, fromfile=file.name, tofile=file.name, n=5
- ):
- # Work around https://bugs.python.org/issue2142
- # See:
- # https://www.gnu.org/software/diffutils/manual/html_node/Incomplete-Lines.html
- if line[-1] == "\n":
- diff_lines.append(line)
- else:
- diff_lines.append(line + "\n")
- diff_lines.append("\\ No newline at end of file\n")
-
- print("".join(diff_lines))
-
- to_print = Colors.BOLD
- if errored:
- to_print += f"{Colors.RED}Skipped {file.name}: Will not overwrite file with compile errors"
- panic = True
- skipped_files += 1
- else:
- to_print += f"{happened} {file.name}"
- formatted_files += 1
-
- print(to_print)
- print(Colors.CLEAR)
-
- missing_num = len(missing_files)
- summary = ""
-
- if missing_num > 0:
- print(
- f"{Colors.BOLD}{Colors.RED}Could not find files:{Colors.CLEAR}{Colors.BOLD}"
- )
- for path in missing_files:
- print(f" {path}")
- print(Colors.CLEAR)
- panic = True
-
- if len(input_files) == 0:
- print(f"{Colors.RED}No Blueprint files found")
- sys.exit(1)
-
- def would_be(verb):
- return verb if opts.fix else f"would be {verb}"
-
- def how_many(count, bold=True):
- string = f"{Colors.BLUE}{count} {'files' if count != 1 else 'file'}{Colors.CLEAR}"
- return Colors.BOLD + string + Colors.BOLD if bold else Colors.CLEAR + string
-
- if formatted_files > 0:
- summary += f"{how_many(formatted_files)} {would_be('formatted')}, "
- panic = panic or not opts.fix
-
- left_files = len(input_files) - formatted_files - skipped_files
- summary += f"{how_many(left_files, False)} {would_be('left unchanged')}"
-
- if skipped_files > 0:
- summary += f", {how_many(skipped_files)} {would_be('skipped')}"
-
- if missing_num > 0:
- summary += f", {how_many(missing_num)} not found"
-
- print(summary + Colors.CLEAR)
-
- if panic:
- sys.exit(1)
-
- def cmd_decompile(self, opts):
- if opts.typelib_path != None:
- for typelib_path in opts.typelib_path:
- add_typelib_search_path(typelib_path)
-
- data = opts.input.read()
- try:
- decompiled = decompile_string(data)
-
- if opts.output == "-":
- print(decompiled)
- else:
- with open(opts.output, "w") as file:
- file.write(decompiled)
- except PrintableError as e:
- e.pretty_print(opts.input.name, data, stream=sys.stderr)
- sys.exit(1)
-
def cmd_lsp(self, opts):
langserv = LanguageServer()
langserv.run()
@@ -335,7 +154,7 @@ class BlueprintApp:
def cmd_port(self, opts):
interactive_port.run(opts)
- def _compile(self, data: str) -> T.Tuple[str, T.List[CompileError]]:
+ def _compile(self, data: str) -> T.Tuple[str, T.List[PrintableError]]:
tokens = tokenizer.tokenize(data)
ast, errors, warnings = parser.parse(tokens)
diff --git a/blueprintcompiler/outputs/xml/__init__.py b/blueprintcompiler/outputs/xml/__init__.py
index 15850f7..7d450d1 100644
--- a/blueprintcompiler/outputs/xml/__init__.py
+++ b/blueprintcompiler/outputs/xml/__init__.py
@@ -1,21 +1,18 @@
import typing as T
-from ...language import *
from .. import OutputFormat
+from ...language import *
from .xml_emitter import XmlEmitter
class XmlOutput(OutputFormat):
- def emit(self, ui: UI, indent=2, generated_notice=True) -> str:
- xml = XmlEmitter(indent, generated_notice)
+ def emit(self, ui: UI) -> str:
+ xml = XmlEmitter()
self._emit_ui(ui, xml)
return xml.result
def _emit_ui(self, ui: UI, xml: XmlEmitter):
- if domain := ui.translation_domain:
- xml.start_tag("interface", domain=domain.domain)
- else:
- xml.start_tag("interface")
+ xml.start_tag("interface")
self._emit_gtk_directive(ui.gtk_decl, xml)
@@ -119,40 +116,32 @@ class XmlOutput(OutputFormat):
if simple := value.simple_binding:
props["bind-source"] = self._object_id(value, simple.source)
props["bind-property"] = simple.property_name
- flags = []
- if not simple.no_sync_create:
- flags.append("sync-create")
- if simple.inverted:
- flags.append("invert-boolean")
- if simple.bidirectional:
- flags.append("bidirectional")
- props["bind-flags"] = "|".join(flags) or None
-
+ props["bind-flags"] = "sync-create"
xml.put_self_closing("property", **props)
else:
xml.start_tag("binding", **props)
self._emit_expression(value.expression, xml)
xml.end_tag()
- elif isinstance(value, ExprValue):
- xml.start_tag("property", **props)
- self._emit_expression(value.expression, xml)
- xml.end_tag()
+ elif isinstance(value, PropertyBinding):
+ bind_flags = []
+ if not value.no_sync_create:
+ bind_flags.append("sync-create")
+ if value.inverted:
+ bind_flags.append("invert-boolean")
+ if value.bidirectional:
+ bind_flags.append("bidirectional")
+
+ props["bind-source"] = self._object_id(value, value.source)
+ props["bind-property"] = value.property_name
+ props["bind-flags"] = "|".join(bind_flags) or None
+ xml.put_self_closing("property", **props)
elif isinstance(value, ObjectValue):
xml.start_tag("property", **props)
self._emit_object(value.object, xml)
xml.end_tag()
- elif isinstance(value, ArrayValue):
- xml.start_tag("property", **props)
- values = list(value.values)
- for value in values[:-1]:
- self._emit_value(value, xml)
- xml.put_text("\n")
- self._emit_value(values[-1], xml)
- xml.end_tag()
-
else:
raise CompilerBugError()
@@ -164,7 +153,7 @@ class XmlOutput(OutputFormat):
elif isinstance(translated, QuotedLiteral):
return {}
else:
- return {"translatable": "yes", "context": translated.translate_context}
+ return {"translatable": "true", "context": translated.translate_context}
def _emit_signal(self, signal: Signal, xml: XmlEmitter):
name = signal.name
@@ -174,8 +163,7 @@ class XmlOutput(OutputFormat):
"signal",
name=name,
handler=signal.handler,
- swapped=signal.is_swapped,
- after=signal.is_after or None,
+ swapped=signal.is_swapped or None,
object=(
self._object_id(signal, signal.object_id) if signal.object_id else None
),
@@ -211,10 +199,7 @@ class XmlOutput(OutputFormat):
elif isinstance(value, TypeLiteral):
xml.put_text(value.type_name.glib_type_name)
else:
- if isinstance(value.value, float) and value.value == int(value.value):
- xml.put_text(int(value.value))
- else:
- xml.put_text(value.value)
+ xml.put_text(value.value)
def _emit_value(self, value: Value, xml: XmlEmitter):
if isinstance(value.child, Literal):
@@ -223,6 +208,12 @@ class XmlOutput(OutputFormat):
xml.put_text(
"|".join([str(flag.value or flag.name) for flag in value.child.flags])
)
+ elif isinstance(value.child, Translated):
+ raise CompilerBugError("translated values must be handled in the parent")
+ elif isinstance(value.child, TypeLiteral):
+ xml.put_text(value.child.type_name.glib_type_name)
+ elif isinstance(value.child, ObjectValue):
+ self._emit_object(value.child.object, xml)
else:
raise CompilerBugError()
@@ -244,9 +235,6 @@ class XmlOutput(OutputFormat):
raise CompilerBugError()
def _emit_literal_expr(self, expr: LiteralExpr, xml: XmlEmitter):
- if expr.is_this:
- return
-
if expr.is_object:
xml.start_tag("constant")
else:
@@ -294,11 +282,8 @@ class XmlOutput(OutputFormat):
def _emit_extensions(self, extension, xml: XmlEmitter):
if isinstance(extension, ExtAccessibility):
xml.start_tag("accessibility")
- for property in extension.properties:
- for val in property.values:
- self._emit_attribute(
- property.tag_name, "name", property.name, val, xml
- )
+ for prop in extension.properties:
+ self._emit_attribute(prop.tag_name, "name", prop.name, prop.value, xml)
xml.end_tag()
elif isinstance(extension, AdwBreakpointCondition):
@@ -308,9 +293,6 @@ class XmlOutput(OutputFormat):
elif isinstance(extension, AdwBreakpointSetters):
for setter in extension.setters:
- if setter.value is None:
- continue
-
attrs = {}
if isinstance(setter.value.child, Translated):
@@ -355,7 +337,7 @@ class XmlOutput(OutputFormat):
self._emit_attribute("property", "name", prop.name, prop.value, xml)
xml.end_tag()
- elif isinstance(extension, ExtAdwResponseDialog):
+ elif isinstance(extension, ExtAdwMessageDialog):
xml.start_tag("responses")
for response in extension.responses:
xml.start_tag(
@@ -371,13 +353,12 @@ class XmlOutput(OutputFormat):
elif isinstance(extension, ExtScaleMarks):
xml.start_tag("marks")
- for mark in extension.marks:
- label = mark.label.child if mark.label is not None else None
+ for mark in extension.children:
xml.start_tag(
"mark",
value=mark.value,
position=mark.position,
- **self._translated_string_attrs(label),
+ **self._translated_string_attrs(mark.label and mark.label.child),
)
if mark.label is not None:
xml.put_text(mark.label.string)
@@ -394,9 +375,9 @@ class XmlOutput(OutputFormat):
xml.end_tag()
elif isinstance(extension, ExtListItemFactory):
- child_xml = XmlEmitter(generated_notice=False)
+ child_xml = XmlEmitter()
child_xml.start_tag("interface")
- child_xml.start_tag("template", **{"class": extension.gir_class})
+ child_xml.start_tag("template", **{"class": "GtkListItem"})
self._emit_object_or_template(extension, child_xml)
child_xml.end_tag()
child_xml.end_tag()
diff --git a/blueprintcompiler/outputs/xml/xml_emitter.py b/blueprintcompiler/outputs/xml/xml_emitter.py
index d34eff4..3dd09a0 100644
--- a/blueprintcompiler/outputs/xml/xml_emitter.py
+++ b/blueprintcompiler/outputs/xml/xml_emitter.py
@@ -18,6 +18,7 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
import typing as T
+
from xml.sax import saxutils
from blueprintcompiler.gir import GirType
@@ -25,24 +26,13 @@ from blueprintcompiler.language.types import ClassName
class XmlEmitter:
- def __init__(self, indent=2, generated_notice=True):
+ def __init__(self, indent=2):
self.indent = indent
self.result = ''
- if generated_notice:
- self.result += (
- "\n"
- ""
- )
self._tag_stack = []
self._needs_newline = False
- def start_tag(
- self, tag, **attrs: T.Union[str, GirType, ClassName, bool, None, float]
- ):
+ def start_tag(self, tag, **attrs: T.Union[str, GirType, ClassName, bool, None]):
self._indent()
self.result += f"<{tag}"
for key, val in attrs.items():
@@ -73,7 +63,6 @@ class XmlEmitter:
self._needs_newline = False
def put_cdata(self, text: str):
- text = text.replace("]]>", "]]]]>")
self.result += f""
self._needs_newline = False
diff --git a/blueprintcompiler/parse_tree.py b/blueprintcompiler/parse_tree.py
index e590539..ff080ea 100644
--- a/blueprintcompiler/parse_tree.py
+++ b/blueprintcompiler/parse_tree.py
@@ -17,21 +17,23 @@
#
# SPDX-License-Identifier: LGPL-3.0-or-later
-"""Utilities for parsing an AST from a token stream."""
+""" Utilities for parsing an AST from a token stream. """
import typing as T
-from enum import Enum
-from . import utils
+from collections import defaultdict
+from enum import Enum
from .ast_utils import AstNode
+
from .errors import (
- CompileError,
+ assert_true,
CompilerBugError,
+ CompileError,
CompileWarning,
UnexpectedTokenError,
- assert_true,
)
-from .tokenizer import Range, Token, TokenType
+from .tokenizer import Token, TokenType
+
SKIP_TOKENS = [TokenType.COMMENT, TokenType.WHITESPACE]
@@ -63,16 +65,14 @@ class ParseGroup:
be converted to AST nodes by passing the children and key=value pairs to
the AST node constructor."""
- def __init__(self, ast_type: T.Type[AstNode], start: int, text: str):
+ def __init__(self, ast_type: T.Type[AstNode], start: int):
self.ast_type = ast_type
self.children: T.List[ParseGroup] = []
self.keys: T.Dict[str, T.Any] = {}
self.tokens: T.Dict[str, T.Optional[Token]] = {}
- self.ranges: T.Dict[str, Range] = {}
self.start = start
self.end: T.Optional[int] = None
self.incomplete = False
- self.text = text
def add_child(self, child: "ParseGroup"):
self.children.append(child)
@@ -82,12 +82,6 @@ class ParseGroup:
self.keys[key] = val
self.tokens[key] = token
- if token:
- self.set_range(key, token.range)
-
- def set_range(self, key: str, range: Range):
- assert_true(key not in self.ranges)
- self.ranges[key] = range
def to_ast(self):
"""Creates an AST node from the match group."""
@@ -95,18 +89,25 @@ class ParseGroup:
try:
return self.ast_type(self, children, self.keys, incomplete=self.incomplete)
- except TypeError: # pragma: no cover
+ except TypeError as e:
raise CompilerBugError(
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:
"""Contains the state of the parser."""
- def __init__(self, tokens: T.List[Token], text: str, index=0):
+ def __init__(self, tokens: T.List[Token], index=0):
self.tokens = tokens
- self.text = text
self.binding_power = 0
self.index = index
@@ -114,7 +115,6 @@ class ParseContext:
self.group: T.Optional[ParseGroup] = None
self.group_keys: T.Dict[str, T.Tuple[T.Any, T.Optional[Token]]] = {}
self.group_children: T.List[ParseGroup] = []
- self.group_ranges: T.Dict[str, Range] = {}
self.last_group: T.Optional[ParseGroup] = None
self.group_incomplete = False
@@ -126,7 +126,7 @@ class ParseContext:
context will be used to parse one node. If parsing is successful, the
new context will be applied to "self". If parsing fails, the new
context will be discarded."""
- ctx = ParseContext(self.tokens, self.text, self.index)
+ ctx = ParseContext(self.tokens, self.index)
ctx.errors = self.errors
ctx.warnings = self.warnings
ctx.binding_power = self.binding_power
@@ -142,8 +142,6 @@ class ParseContext:
other.group.set_val(key, val, token)
for child in other.group_children:
other.group.add_child(child)
- for key, range in other.group_ranges.items():
- other.group.set_range(key, range)
other.group.end = other.tokens[other.index - 1].end
other.group.incomplete = other.group_incomplete
self.group_children.append(other.group)
@@ -152,7 +150,6 @@ class ParseContext:
# its matched values
self.group_keys = {**self.group_keys, **other.group_keys}
self.group_children += other.group_children
- self.group_ranges = {**self.group_ranges, **other.group_ranges}
self.group_incomplete |= other.group_incomplete
self.index = other.index
@@ -166,19 +163,13 @@ class ParseContext:
def start_group(self, ast_type: T.Type[AstNode]):
"""Sets this context to have its own match group."""
assert_true(self.group is None)
- self.group = ParseGroup(ast_type, self.tokens[self.index].start, self.text)
+ self.group = ParseGroup(ast_type, self.tokens[self.index].start)
def set_group_val(self, key: str, value: T.Any, token: T.Optional[Token]):
"""Sets a matched key=value pair on the current match group."""
assert_true(key not in self.group_keys)
self.group_keys[key] = (value, token)
- def set_mark(self, key: str):
- """Sets a zero-length range on the current match group at the current position."""
- self.group_ranges[key] = Range(
- self.tokens[self.index].start, self.tokens[self.index].start, self.text
- )
-
def set_group_incomplete(self):
"""Marks the current match group as incomplete (it could not be fully
parsed, but the parser recovered)."""
@@ -217,11 +208,11 @@ class ParseContext:
if (
len(self.errors)
and isinstance((err := self.errors[-1]), UnexpectedTokenError)
- and err.range.end == start
+ and err.end == start
):
- err.range.end = end
+ err.end = end
else:
- self.errors.append(UnexpectedTokenError(Range(start, end, self.text)))
+ self.errors.append(UnexpectedTokenError(start, end))
def is_eof(self) -> bool:
return self.index >= len(self.tokens) or self.peek_token().type == TokenType.EOF
@@ -257,6 +248,10 @@ class ParseNode:
"""Convenience method for err()."""
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):
"""ParseNode that emits a compile error if it fails to parse."""
@@ -270,14 +265,34 @@ class Err(ParseNode):
start_idx = ctx.start
while ctx.tokens[start_idx].type in SKIP_TOKENS:
start_idx += 1
- start_token = ctx.tokens[start_idx]
- raise CompileError(
- self.message, Range(start_token.start, start_token.start, ctx.text)
- )
+ start_token = ctx.tokens[start_idx]
+ end_token = ctx.tokens[ctx.index]
+ raise CompileError(self.message, start_token.start, end_token.end)
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):
"""ParseNode that emits a compile error if it parses successfully."""
@@ -293,9 +308,7 @@ class Fail(ParseNode):
start_token = ctx.tokens[start_idx]
end_token = ctx.tokens[ctx.index]
- raise CompileError(
- self.message, Range.join(start_token.range, end_token.range)
- )
+ raise CompileError(self.message, start_token.start, end_token.end)
return True
@@ -344,7 +357,7 @@ class Statement(ParseNode):
token = ctx.peek_token()
if str(token) != ";":
- ctx.errors.append(CompileError("Expected `;`", token.range))
+ ctx.errors.append(CompileError("Expected `;`", token.start, token.end))
else:
ctx.next_token()
return True
@@ -509,6 +522,8 @@ class UseNumber(ParseNode):
return False
number = token.get_number()
+ if number % 1.0 == 0:
+ number = int(number)
ctx.set_group_val(self.key, number, token)
return True
@@ -541,19 +556,14 @@ class UseQuoted(ParseNode):
if token.type != TokenType.QUOTED:
return False
- unescaped = None
-
- try:
- unescaped = utils.unescape_quote(str(token))
- except utils.UnescapeError as e:
- start = ctx.tokens[ctx.index - 1].start
- range = Range(start + e.start, start + e.end, ctx.text)
- ctx.errors.append(
- CompileError(f"Invalid escape sequence '{range.text}'", range)
- )
-
- ctx.set_group_val(self.key, unescaped, token)
-
+ string = (
+ str(token)[1:-1]
+ .replace("\\n", "\n")
+ .replace('\\"', '"')
+ .replace("\\\\", "\\")
+ .replace("\\'", "'")
+ )
+ ctx.set_group_val(self.key, string, token)
return True
@@ -598,15 +608,6 @@ class Keyword(ParseNode):
return str(token) == self.kw
-class Mark(ParseNode):
- def __init__(self, key: str):
- self.key = key
-
- def _parse(self, ctx: ParseContext):
- ctx.set_mark(self.key)
- return True
-
-
def to_parse_node(value) -> ParseNode:
if isinstance(value, str):
return Match(value)
diff --git a/blueprintcompiler/parser.py b/blueprintcompiler/parser.py
index 1f87647..edef840 100644
--- a/blueprintcompiler/parser.py
+++ b/blueprintcompiler/parser.py
@@ -19,23 +19,20 @@
from .errors import MultipleErrors, PrintableError
-from .language import OBJECT_CONTENT_HOOKS, UI, Template
from .parse_tree import *
from .tokenizer import TokenType
+from .language import OBJECT_CONTENT_HOOKS, Template, UI
def parse(
tokens: T.List[Token],
-) -> T.Tuple[T.Optional[UI], T.Optional[MultipleErrors], T.List[CompileError]]:
+) -> T.Tuple[T.Optional[UI], T.Optional[MultipleErrors], T.List[PrintableError]]:
"""Parses a list of tokens into an abstract syntax tree."""
try:
- original_text = tokens[0].string if len(tokens) else ""
- ctx = ParseContext(tokens, original_text)
+ ctx = ParseContext(tokens)
AnyOf(UI).parse(ctx)
-
- assert ctx.last_group is not None
- ast_node = ctx.last_group.to_ast()
+ ast_node = ctx.last_group.to_ast() if ctx.last_group else None
errors = [*ctx.errors, *ast_node.errors]
warnings = [*ctx.warnings, *ast_node.warnings]
diff --git a/blueprintcompiler/tokenizer.py b/blueprintcompiler/tokenizer.py
index 85bce95..f68f5a7 100644
--- a/blueprintcompiler/tokenizer.py
+++ b/blueprintcompiler/tokenizer.py
@@ -18,12 +18,11 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
-import re
import typing as T
-from dataclasses import dataclass
+import re
from enum import Enum
-from . import utils
+from .errors import CompileError, CompilerBugError
class TokenType(Enum):
@@ -39,8 +38,8 @@ class TokenType(Enum):
_tokens = [
(TokenType.IDENT, r"[A-Za-z_][\d\w\-_]*"),
- (TokenType.QUOTED, r'"(\\(.|\n)|[^\\"\n])*"'),
- (TokenType.QUOTED, r"'(\\(.|\n)|[^\\'\n])*'"),
+ (TokenType.QUOTED, r'"(\\"|[^"\n])*"'),
+ (TokenType.QUOTED, r"'(\\'|[^'\n])*'"),
(TokenType.NUMBER, r"0x[A-Za-z0-9_]+"),
(TokenType.NUMBER, r"[\d_]+(\.[\d_]+)?"),
(TokenType.NUMBER, r"\.[\d_]+"),
@@ -63,13 +62,7 @@ class Token:
def __str__(self) -> str:
return self.string[self.start : self.end]
- @property
- def range(self) -> "Range":
- return Range(self.start, self.end, self.string)
-
def get_number(self) -> T.Union[int, float]:
- from .errors import CompileError, CompilerBugError
-
if self.type != TokenType.NUMBER:
raise CompilerBugError()
@@ -77,17 +70,15 @@ class Token:
try:
if string.startswith("0x"):
return int(string, 16)
- elif "." in string:
- return float(string)
else:
- return int(string)
+ return float(string)
except:
- raise CompileError(f"{str(self)} is not a valid number literal", self.range)
+ raise CompileError(
+ f"{str(self)} is not a valid number literal", self.start, self.end
+ )
def _tokenize(ui_ml: str):
- from .errors import CompileError
-
i = 0
while i < len(ui_ml):
matched = False
@@ -102,8 +93,7 @@ def _tokenize(ui_ml: str):
if not matched:
raise CompileError(
- "Could not determine what kind of syntax is meant here",
- Range(i, i, ui_ml),
+ "Could not determine what kind of syntax is meant here", i, i
)
yield Token(TokenType.EOF, i, i, ui_ml)
@@ -111,45 +101,3 @@ def _tokenize(ui_ml: str):
def tokenize(data: str) -> T.List[Token]:
return list(_tokenize(data))
-
-
-@dataclass
-class Range:
- start: int
- end: int
- original_text: str
-
- @property
- def length(self) -> int:
- return self.end - self.start
-
- @property
- def text(self) -> str:
- return self.original_text[self.start : self.end]
-
- @property
- def with_trailing_newline(self) -> "Range":
- if len(self.original_text) > self.end and self.original_text[self.end] == "\n":
- return Range(self.start, self.end + 1, self.original_text)
- else:
- return self
-
- @staticmethod
- def join(a: T.Optional["Range"], b: T.Optional["Range"]) -> T.Optional["Range"]:
- if a is None:
- return b
- if b is None:
- return a
- return Range(min(a.start, b.start), max(a.end, b.end), a.original_text)
-
- def __contains__(self, other: T.Union[int, "Range"]) -> bool:
- if isinstance(other, int):
- return self.start <= other <= self.end
- else:
- return self.start <= other.start and self.end >= other.end
-
- def to_json(self):
- return utils.idxs_to_range(self.start, self.end, self.original_text)
-
- def overlaps(self, other: "Range") -> bool:
- return not (self.end < other.start or self.start > other.end)
diff --git a/blueprintcompiler/typelib.py b/blueprintcompiler/typelib.py
index be22eb1..6babc10 100644
--- a/blueprintcompiler/typelib.py
+++ b/blueprintcompiler/typelib.py
@@ -17,15 +17,15 @@
#
# SPDX-License-Identifier: LGPL-3.0-or-later
-import math
-import mmap
-import os
import sys
import typing as T
+import math
from ctypes import *
+import mmap, os
from .errors import CompilerBugError
+
BLOB_TYPE_STRUCT = 3
BLOB_TYPE_BOXED = 4
BLOB_TYPE_ENUM = 5
@@ -61,14 +61,7 @@ class Field:
def __init__(self, offset: int, type: str, shift=0, mask=None):
self._offset = offset
self._type = type
- if not mask or sys.byteorder == "little":
- self._shift = shift
- elif self._type == "u8" or self._type == "i8":
- self._shift = 8 - (shift + mask)
- elif self._type == "u16" or self._type == "i16":
- self._shift = 16 - (shift + mask)
- else:
- self._shift = 32 - (shift + mask)
+ self._shift = shift
self._mask = (1 << mask) - 1 if mask else None
self._name = f"{offset}__{type}__{shift}__{mask}"
@@ -125,7 +118,6 @@ class Typelib:
HEADER_FUNCTION_BLOB_SIZE = Field(0x3E, "u16")
HEADER_CALLBACK_BLOB_SIZE = Field(0x40, "u16")
HEADER_SIGNAL_BLOB_SIZE = Field(0x42, "u16")
- HEADER_ARG_BLOB_SIZE = Field(0x46, "u16")
HEADER_PROPERTY_BLOB_SIZE = Field(0x48, "u16")
HEADER_FIELD_BLOB_SIZE = Field(0x4A, "u16")
HEADER_VALUE_BLOB_SIZE = Field(0x4C, "u16")
@@ -140,16 +132,9 @@ class Typelib:
DIR_ENTRY_OFFSET = Field(0x8, "pointer")
DIR_ENTRY_NAMESPACE = Field(0x8, "string")
- ARG_NAME = Field(0x0, "string")
- ARG_TYPE = Field(0xC, "u32")
-
- SIGNATURE_RETURN_TYPE = Field(0x0, "u32")
- SIGNATURE_N_ARGUMENTS = Field(0x6, "u16")
- SIGNATURE_ARGUMENTS = Field(0x8, "offset")
-
ATTR_OFFSET = Field(0x0, "u32")
- ATTR_NAME = Field(0x4, "string")
- ATTR_VALUE = Field(0x8, "string")
+ ATTR_NAME = Field(0x0, "string")
+ ATTR_VALUE = Field(0x0, "string")
TYPE_BLOB_TAG = Field(0x0, "u8", 3, 5)
TYPE_BLOB_INTERFACE = Field(0x2, "dir_entry")
@@ -157,15 +142,11 @@ class Typelib:
BLOB_NAME = Field(0x4, "string")
- STRUCT_DEPRECATED = Field(0x2, "u16", 0, 1)
-
- ENUM_DEPRECATED = Field(0x2, "u16", 0, 1)
ENUM_GTYPE_NAME = Field(0x8, "string")
ENUM_N_VALUES = Field(0x10, "u16")
ENUM_N_METHODS = Field(0x12, "u16")
ENUM_VALUES = Field(0x18, "offset")
- INTERFACE_DEPRECATED = Field(0x2, "u16", 0, 1)
INTERFACE_GTYPE_NAME = Field(0x8, "string")
INTERFACE_N_PREREQUISITES = Field(0x12, "u16")
INTERFACE_N_PROPERTIES = Field(0x14, "u16")
@@ -181,7 +162,7 @@ class Typelib:
OBJ_FINAL = Field(0x02, "u16", 3, 1)
OBJ_GTYPE_NAME = Field(0x08, "string")
OBJ_PARENT = Field(0x10, "dir_entry")
- OBJ_GTYPE_STRUCT = Field(0x12, "string")
+ OBJ_GTYPE_STRUCT = Field(0x14, "string")
OBJ_N_INTERFACES = Field(0x14, "u16")
OBJ_N_FIELDS = Field(0x16, "u16")
OBJ_N_PROPERTIES = Field(0x18, "u16")
@@ -199,11 +180,6 @@ class Typelib:
PROP_CONSTRUCT_ONLY = Field(0x4, "u32", 4, 1)
PROP_TYPE = Field(0xC, "u32")
- SIGNAL_DEPRECATED = Field(0x0, "u16", 0, 1)
- SIGNAL_DETAILED = Field(0x0, "u16", 5, 1)
- SIGNAL_NAME = Field(0x4, "string")
- SIGNAL_SIGNATURE = Field(0xC, "pointer")
-
VALUE_NAME = Field(0x4, "string")
VALUE_VALUE = Field(0x8, "i32")
@@ -261,14 +237,14 @@ class Typelib:
if loc == 0:
return None
- end = self._typelib_file.find(b"\0", loc)
+ end = loc
+ while self._typelib_file[end] != 0:
+ end += 1
return self._typelib_file[loc:end].decode("utf-8")
def _int(self, size, signed) -> int:
return int.from_bytes(
- self._typelib_file[self._offset : self._offset + size],
- sys.byteorder,
- signed=signed,
+ self._typelib_file[self._offset : self._offset + size], sys.byteorder
)
diff --git a/blueprintcompiler/utils.py b/blueprintcompiler/utils.py
index ea8102e..4c4b44a 100644
--- a/blueprintcompiler/utils.py
+++ b/blueprintcompiler/utils.py
@@ -18,7 +18,6 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
import typing as T
-from dataclasses import dataclass
class Colors:
@@ -99,58 +98,3 @@ def idxs_to_range(start: int, end: int, text: str):
"character": end_c,
},
}
-
-
-@dataclass
-class UnescapeError(Exception):
- start: int
- end: int
-
-
-def escape_quote(string: str) -> str:
- return (
- '"'
- + (
- string.replace("\\", "\\\\")
- .replace('"', '\\"')
- .replace("\n", "\\n")
- .replace("\t", "\\t")
- )
- + '"'
- )
-
-
-def unescape_quote(string: str) -> str:
- string = string[1:-1]
-
- REPLACEMENTS = {
- "\n": "\n",
- "\\": "\\",
- "n": "\n",
- "t": "\t",
- '"': '"',
- "'": "'",
- }
-
- result = ""
- i = 0
- while i < len(string):
- c = string[i]
- if c == "\\":
- i += 1
-
- if i >= len(string):
- from .errors import CompilerBugError
-
- raise CompilerBugError()
-
- if r := REPLACEMENTS.get(string[i]):
- result += r
- else:
- raise UnescapeError(i, i + 2)
- else:
- result += c
-
- i += 1
-
- return result
diff --git a/blueprintcompiler/xml_reader.py b/blueprintcompiler/xml_reader.py
index a3c0e3e..b2d579b 100644
--- a/blueprintcompiler/xml_reader.py
+++ b/blueprintcompiler/xml_reader.py
@@ -18,11 +18,12 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
-import typing as T
from collections import defaultdict
from functools import cached_property
+import typing as T
from xml import sax
+
# To speed up parsing, we ignore all tags except these
PARSE_GIR = set(
[
diff --git a/build-aux/Dockerfile b/build-aux/Dockerfile
index 617015f..b060765 100644
--- a/build-aux/Dockerfile
+++ b/build-aux/Dockerfile
@@ -2,9 +2,9 @@ FROM fedora:latest
RUN dnf install -y meson gcc g++ python3-pip gobject-introspection-devel \
python3-devel python3-gobject git diffutils xorg-x11-server-Xvfb \
- appstream-devel dbus-x11 "dnf-command(builddep)" glslc
+ appstream-devel "dnf-command(builddep)"
RUN dnf build-dep -y gtk4 libadwaita
-RUN pip3 install furo mypy sphinx coverage black isort
+RUN pip3 install furo mypy sphinx coverage black
COPY install_deps.sh .
RUN ./install_deps.sh
diff --git a/build-aux/install_deps.sh b/build-aux/install_deps.sh
index 342778d..381a57a 100755
--- a/build-aux/install_deps.sh
+++ b/build-aux/install_deps.sh
@@ -7,8 +7,8 @@ git clone --depth=1 https://gitlab.gnome.org/GNOME/gtk.git
cd gtk
meson setup builddir \
--prefix=/usr \
- -Ddocumentation=true \
- -Dbuild-demos=false \
+ -Dgtk_doc=true \
+ -Ddemos=false \
-Dbuild-examples=false \
-Dbuild-tests=false \
-Dbuild-testsuite=false
diff --git a/docs/collect-sections.py b/docs/collect-sections.py
deleted file mode 100755
index a2dd004..0000000
--- a/docs/collect-sections.py
+++ /dev/null
@@ -1,139 +0,0 @@
-#!/usr/bin/env python3
-
-import json
-import os
-import re
-import sys
-from dataclasses import dataclass
-from pathlib import Path
-
-__all__ = ["get_docs_section"]
-
-DOCS_ROOT = "https://gnome.pages.gitlab.gnome.org/blueprint-compiler"
-
-
-sections: dict[str, "Section"] = {}
-
-
-@dataclass
-class Section:
- link: str
- lines: str
-
- def to_json(self):
- return {
- "content": rst_to_md(self.lines),
- "link": self.link,
- }
-
-
-def load_reference_docs():
- for filename in Path(os.path.dirname(__file__), "reference").glob("*.rst"):
- with open(filename) as f:
- section_name = None
- lines = []
-
- def close_section():
- if section_name:
- html_file = re.sub(r"\.rst$", ".html", filename.name)
- anchor = re.sub(r"[^a-z0-9]+", "-", section_name.lower())
- link = f"{DOCS_ROOT}/reference/{html_file}#{anchor}"
- sections[section_name] = Section(link, lines)
-
- for line in f:
- if m := re.match(r"\.\.\s+_(.*):", line):
- close_section()
- section_name = m.group(1)
- lines = []
- else:
- lines.append(line)
-
- close_section()
-
-
-# This isn't a comprehensive rST to markdown converter, it just needs to handle the
-# small subset of rST used in the reference docs.
-def rst_to_md(lines: list[str]) -> str:
- result = ""
-
- def rst_to_md_inline(line):
- line = re.sub(r"``(.*?)``", r"`\1`", line)
- line = re.sub(
- r":ref:`(.*?)<(.*?)>`",
- lambda m: f"[{m.group(1)}]({sections[m.group(2)].link})",
- line,
- )
- line = re.sub(r"`([^`]*?) <([^`>]*?)>`_", r"[\1](\2)", line)
- return line
-
- i = 0
- n = len(lines)
- heading_levels = {}
-
- def print_block(lang: str = "", code: bool = True, strip_links: bool = False):
- nonlocal result, i
- block = ""
- while i < n:
- line = lines[i].rstrip()
- if line.startswith(" "):
- line = line[3:]
- elif line != "":
- break
-
- if strip_links:
- line = re.sub(r":ref:`(.*?)<(.*?)>`", r"\1", line)
-
- if not code:
- line = rst_to_md_inline(line)
-
- block += line + "\n"
- i += 1
-
- if code:
- result += f"```{lang}\n{block.strip()}\n```\n\n"
- else:
- result += block
-
- while i < n:
- line = lines[i].rstrip()
- i += 1
- if line == ".. rst-class:: grammar-block":
- print_block("text", strip_links=True)
- elif line == ".. code-block:: blueprint":
- print_block("blueprint")
- elif line == ".. note::":
- result += "#### Note\n"
- print_block(code=False)
- elif m := re.match(r"\.\. image:: (.*)", line):
- result += f"})\n"
- elif i < n and re.match(r"^((-+)|(~+)|(\++))$", lines[i]):
- level_char = lines[i][0]
- if level_char not in heading_levels:
- heading_levels[level_char] = max(heading_levels.values(), default=1) + 1
- result += (
- "#" * heading_levels[level_char] + " " + rst_to_md_inline(line) + "\n"
- )
- i += 1
- else:
- result += rst_to_md_inline(line) + "\n"
-
- return result
-
-
-if __name__ == "__main__":
- if len(sys.argv) != 2:
- print("Usage: collect_sections.py ")
- sys.exit(1)
-
- outfile = sys.argv[1]
-
- load_reference_docs()
-
- # print the sections to a json file
- with open(outfile, "w") as f:
- json.dump(
- {name: section.to_json() for name, section in sections.items()},
- f,
- indent=2,
- sort_keys=True,
- )
diff --git a/docs/conf.py b/docs/conf.py
index a397da7..b5676c2 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -17,9 +17,9 @@
# -- Project information -----------------------------------------------------
-project = "Blueprint"
-copyright = "2021-2023, James Westman"
-author = "James Westman"
+project = 'Blueprint'
+copyright = '2021-2023, James Westman'
+author = 'James Westman'
# -- General configuration ---------------------------------------------------
@@ -27,15 +27,16 @@ author = "James Westman"
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
-extensions = []
+extensions = [
+]
# Add any paths that contain templates here, relative to this directory.
-templates_path = ["_templates"]
+templates_path = ['_templates']
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
-exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
+exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# -- Options for HTML output -------------------------------------------------
@@ -43,11 +44,11 @@ exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
-html_theme = "furo"
+html_theme = 'furo'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ["_static"]
+html_static_path = ['_static']
-html_css_files = ["styles.css"]
+html_css_files = ['styles.css']
diff --git a/docs/flatpak.rst b/docs/flatpak.rst
index 8081c8d..f10a944 100644
--- a/docs/flatpak.rst
+++ b/docs/flatpak.rst
@@ -12,12 +12,11 @@ a module in your flatpak manifest:
{
"name": "blueprint-compiler",
"buildsystem": "meson",
- "cleanup": ["*"],
"sources": [
{
"type": "git",
- "url": "https://gitlab.gnome.org/GNOME/blueprint-compiler",
- "tag": "v0.16.0"
+ "url": "https://gitlab.gnome.org/jwestman/blueprint-compiler",
+ "tag": "v0.8.1"
}
]
}
diff --git a/docs/index.rst b/docs/index.rst
index 6cd130f..e277add 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -22,11 +22,11 @@ Blueprint is a markup language and compiler for GTK 4 user interfaces.
packaging
-.. code-block:: blueprint
+.. code-block::
using Gtk 4.0;
- template $MyAppWindow: ApplicationWindow {
+ template MyAppWindow : ApplicationWindow {
default-width: 600;
default-height: 300;
title: _("Hello, Blueprint!");
@@ -35,7 +35,7 @@ Blueprint is a markup language and compiler for GTK 4 user interfaces.
HeaderBar {}
Label {
- label: bind template.main_text;
+ label: bind MyAppWindow.main_text;
}
}
@@ -59,7 +59,7 @@ Features
Links
-----
-- `Source code `_
+- `Source code `_
- `Workbench `_ lets you try, preview and export Blueprint
- `GNOME Builder `_ provides builtin support
- `Vim syntax highlighting plugin by thetek42 `_
@@ -81,68 +81,33 @@ Built with Blueprint
- `AdwSteamGtk `_
- `Blurble `_
- `Bottles `_
-- `Cartridges `_
-- `Cassette `_
-- `Cavalier `_
-- `Chance `_
- `Commit `_
-- `Confy `_
-- `Cozy `_
-- `Daikhan `_
-- `Damask `_
-- `Denaro `_
-- `Design `_
-- `Dev Toolbox `_
- `Dialect `_
-- `Diccionario de la Lengua `_
-- `Doggo `_
-- `Dosage `_
-- `Dynamic Wallpaper `_
- `Extension Manager `_
-- `Eyedropper `_
- `favagtk `_
- `Feeds `_
- `File Shredder `_
-- `Flare `_
-- `Flowtime `_
-- `Fretboard `_
-- `Frog `_
- `Geopard `_
- `Giara `_
- `Girens `_
- `Gradience `_
-- `Graphs `_
- `Health `_
- `HydraPaper `_
- `Identity `_
-- `Jogger `_
- `Junction `_
-- `Komikku `_
-- `Letterpress `_
- `Login Manager Settings `_
- `Maniatic Launcher `_
-- `Master Key `_
-- `Misson Center `_
- `NewCaw `_
- `Paper `_
-- `Paper Plane `_
-- `Parabolic `_
- `Passes `_
-- `Pipeline `_
- `Playhouse `_
- `Plitki `_
- `Raider `_
- `Retro `_
- `Solanum `_
-- `Sudoku Solver `_
- `Swatch `_
-- `Switcheroo `_
-- `Tagger `_
- `Tangram `_
- `Text Pieces `_
-- `Upscaler `_
- `Video Trimmer `_
-- `Webfont Kit Generator `_
- `WhatIP `_
-- `Who Wants To Be a Millionaire `_
-- `Workbench `_
\ No newline at end of file
+- `Workbench `_
diff --git a/docs/meson.build b/docs/meson.build
index d9ad736..95e545d 100644
--- a/docs/meson.build
+++ b/docs/meson.build
@@ -9,11 +9,3 @@ custom_target('docs',
)
endif
-
-custom_target('reference_docs.json',
- output: 'reference_docs.json',
- command: [meson.current_source_dir() / 'collect-sections.py', '@OUTPUT@'],
- build_always_stale: true,
- install: true,
- install_dir: py.get_install_dir() / 'blueprintcompiler',
-)
\ No newline at end of file
diff --git a/docs/packaging.rst b/docs/packaging.rst
index 78bb2c2..4f248e6 100644
--- a/docs/packaging.rst
+++ b/docs/packaging.rst
@@ -13,7 +13,7 @@ GObject Introspection
Blueprint files can import GObject Introspection namespaces like this:
-.. code-block:: blueprint
+.. code-block::
using Gtk 4.0;
using Adw 1;
diff --git a/docs/reference/diagnostics.rst b/docs/reference/diagnostics.rst
index c8774d6..b8fee83 100644
--- a/docs/reference/diagnostics.rst
+++ b/docs/reference/diagnostics.rst
@@ -21,7 +21,7 @@ The tokenizer encountered an unexpected sequence of characters that aren't part
child_not_accepted
------------------
-The parent class does not have child objects (it does not implement `Gtk.Buildable `_ and is not a subclass of `Gio.ListStore `_). Some classes use properties instead of children to add widgets. Check the parent class's documentation.
+The parent class does not have child widgets (it does not implement `Gtk.Buildable `_ and is not a subclass of `Gio.ListStore `_). Some classes use properties instead of children to add widgets. Check the parent class's documentation.
.. _Diagnostic conversion_error:
@@ -166,14 +166,14 @@ version_conflict
----------------
This error occurs when two versions of a namespace are imported (possibly transitively) in the same file. For example, this will cause a version conflict:
-.. code-block:: blueprint
+.. code-block:: blueprintui
using Gtk 4.0;
using Gtk 3.0;
But so will this:
-.. code-block:: blueprint
+.. code-block:: blueprintui
using Gtk 4.0;
using Handy 1;
diff --git a/docs/reference/document_root.rst b/docs/reference/document_root.rst
index ee97d6a..af5357d 100644
--- a/docs/reference/document_root.rst
+++ b/docs/reference/document_root.rst
@@ -10,14 +10,14 @@ Document Root
.. rst-class:: grammar-block
- Root = :ref:`GtkDecl` (:ref:`Using`)* (:ref:`TranslationDomain`)? ( :ref:`Template` | :ref:`Menu` | :ref:`Object` )* EOF
+ Root = :ref:`GtkDecl` (:ref:`Using`)* ( :ref:`Template` | :ref:`Menu` | :ref:`Object` )* EOF
-A blueprint document consists of a :ref:`GTK declaration`, one or more :ref:`imports`, and a list of :ref:`objects` and/or a :ref:`template`.
+A blueprint document consists of a :ref:`GTK declaration`, one sor more :ref:`imports`, and a list of :ref:`objects` and/or a :ref:`template`.
Example
~~~~~~~
-.. code-block:: blueprint
+.. code-block:: blueprintui
// Gtk Declaration
using Gtk 4.0;
@@ -43,7 +43,7 @@ Every blueprint file begins with the line ``using Gtk 4.0;``, which declares the
Example
~~~~~~~
-.. code-block:: blueprint
+.. code-block:: blueprintui
using Gtk 4.0;
@@ -68,21 +68,7 @@ The compiler requires typelib files for these libraries to be installed. They ar
Example
~~~~~~~
-.. code-block:: blueprint
+.. code-block:: blueprintui
// Import libadwaita
using Adw 1;
-
-
-.. _Syntax TranslationDomain:
-
-Translation Domain
-------------------
-
-.. rst-class:: grammar-block
-
- TranslationDomain = 'translation-domain' `> ';'
-
-The translation domain is used to look up translations for translatable strings in the blueprint file. If no translation domain is specified, strings will be looked up in the program's global domain.
-
-See `Gtk.Builder:translation-domain `_ for more information.
diff --git a/docs/reference/expressions.rst b/docs/reference/expressions.rst
index 3d523d1..042aec7 100644
--- a/docs/reference/expressions.rst
+++ b/docs/reference/expressions.rst
@@ -6,12 +6,12 @@ Expressions make your user interface code *reactive*. This means when your
application's data changes, the user interface reacts to the change
automatically.
-.. code-block:: blueprint
+.. code-block:: blueprintui
- label: bind template.account.username;
- /* ^ ^ ^
- | creates lookup expressions that are re-evaluated when
- | the account's username *or* the account itself changes
+ label: bind MyAppWindow.account.username;
+ /* ^ ^ ^
+ | creates lookup expressions that are re-evaluated when
+ | the account's username *or* the account itself changes
|
binds the `label` property to the expression's output
*/
@@ -42,22 +42,22 @@ Expressions are composed of property lookups and/or closures. Property lookups a
.. _Syntax LookupExpression:
-Lookups
--------
+Lookup Expressions
+------------------
.. rst-class:: grammar-block
LookupExpression = '.' `>
-Lookup expressions perform a GObject property lookup on the preceding expression. They are recalculated whenever the property changes, using the `notify signal `_.
+Lookup expressions perform a GObject property lookup on the preceding expression. They are recalculated whenever the property changes, using the `notify signal `_
The type of a property expression is the type of the property it refers to.
.. _Syntax ClosureExpression:
-Closures
---------
+Closure Expressions
+-------------------
.. rst-class:: grammar-block
@@ -65,48 +65,23 @@ Closures
Closure expressions allow you to perform additional calculations that aren't supported in blueprint by writing those calculations as application code. These application-defined functions are created in the same way as :ref:`signal handlers`.
-Expressions are only reevaluated when their inputs change. Because blueprint doesn't manage a closure's application code, it can't tell what changes might affect the result. Therefore, closures must be *pure*, or deterministic. They may only calculate the result based on their immediate inputs, not properties of their inputs or outside variables.
+Expressions are only reevaluated when their inputs change. Because blueprint doesn't manage a closure's application code, it can't tell what changes might affect the result. Therefore, closures must be *pure*, or deterministic. They may only calculate the result based on their immediate inputs, properties of their inputs or outside variables.
Blueprint doesn't know the closure's return type, so closure expressions must be cast to the correct return type using a :ref:`cast expression`.
.. _Syntax CastExpression:
-Casts
------
+Cast Expressions
+----------------
.. rst-class:: grammar-block
CastExpression = 'as' '<' :ref:`TypeName` '>'
-Cast expressions allow Blueprint to know the type of an expression when it can't otherwise determine it. This is necessary for closures and for properties of application-defined types.
+Cast expressions allow Blueprint to know the type of an expression when it can't otherwise determine it.
-Example
-~~~~~~~
-
-.. code-block:: blueprint
+.. code-block:: blueprintui
// Cast the result of the closure so blueprint knows it's a string
- label: bind $format_bytes(template.file-size) as
-
-.. _Syntax ExprValue:
-
-Expression Values
------------------
-
-.. rst-class:: grammar-block
-
- ExprValue = 'expr' :ref:`Expression`
-
-Some APIs take *an expression itself*--not its result--as a property value. For example, `Gtk.BoolFilter `_ has an ``expression`` property of type `Gtk.Expression `_. This expression is evaluated for every item in a list model to determine whether the item should be filtered.
-
-To define an expression for such a property, use ``expr`` instead of ``bind``. Inside the expression, you can use the ``item`` keyword to refer to the item being evaluated. You must cast the item to the correct type using the ``as`` keyword, and you can only use ``item`` in a property lookup--you may not pass it to a closure.
-
-Example
-~~~~~~~
-
-.. code-block:: blueprint
-
- BoolFilter {
- expression: expr item as <$UserAccount>.active;
- }
+ label: bind $my_closure() as
\ No newline at end of file
diff --git a/docs/reference/extensions.rst b/docs/reference/extensions.rst
index 2fd5dbb..f7b42b1 100644
--- a/docs/reference/extensions.rst
+++ b/docs/reference/extensions.rst
@@ -10,14 +10,12 @@ Properties are the main way to set values on objects, but they are limited by th
Extensions are a feature of ``Gtk.Buildable``--see `Gtk.Buildable.custom_tag_start() `_ for internal details.
- Because they aren't part of the type system, they aren't present in typelib files like properties and signals are. Therefore, if a library adds a new extension, syntax for it must be added to Blueprint manually. If there's a commonly used extension that isn't supported by Blueprint, please `file an issue `_.
+ Because they aren't part of the type system, they aren't present in typelib files like properties and signals are. Therefore, if a library adds a new extension, syntax for it must be added to Blueprint manually. If there's a commonly used extension that isn't supported by Blueprint, please `file an issue `_.
.. rst-class:: grammar-block
Extension = :ref:`ExtAccessibility`
- | :ref:`ExtAdwAlertDialog`
| :ref:`ExtAdwMessageDialog`
- | :ref:`ExtAdwBreakpoint`
| :ref:`ExtComboBoxItems`
| :ref:`ExtFileFilterMimeTypes`
| :ref:`ExtFileFilterPatterns`
@@ -37,15 +35,12 @@ Accessibility Properties
.. rst-class:: grammar-block
ExtAccessibility = 'accessibility' '{' ExtAccessibilityProp* '}'
- ExtAccessibilityProp = `> ':' (:ref:`Value ` | ('[' (:ref: Value ),* ']') ) ';'
+ ExtAccessibilityProp = `> ':' :ref:`Value ` ';'
Valid in any `Gtk.Widget `_.
The ``accessibility`` block defines values relevant to accessibility software. The property names and acceptable values are described in the `Gtk.AccessibleRelation `_, `Gtk.AccessibleState `_, and `Gtk.AccessibleProperty `_ enums.
-.. note::
-
- Relations which allow for a list of values, for example `labelled-by`, must be given as a single relation with a list of values instead of duplicating the relation like done in Gtk.Builder.
.. _Syntax ExtAdwBreakpoint:
@@ -67,35 +62,6 @@ Defines the condition for a breakpoint and the properties that will be set at th
The `Adw.Breakpoint:condition `_ property has type `Adw.BreakpointCondition `_, which GtkBuilder doesn't know how to parse from a string. Therefore, the ``condition`` syntax is used instead.
-.. _Syntax ExtAdwAlertDialog:
-
-Adw.AlertDialog Responses
-----------------------------
-
-.. rst-class:: grammar-block
-
- ExtAdwAlertDialog = 'responses' '[' (ExtAdwAlertDialogResponse),* ']'
- ExtAdwAlertDialogResponse = `> ':' :ref:`StringValue` ExtAdwAlertDialogFlag*
- ExtAdwAlertDialogFlag = 'destructive' | 'suggested' | 'disabled'
-
-Valid in `Adw.AlertDialog `_.
-
-The ``responses`` block defines the buttons that will be added to the dialog. The ``destructive`` or ``suggested`` flag sets the appearance of the button, and the ``disabled`` flag can be used to disable the button.
-
-.. code-block:: blueprint
-
- using Adw 1;
-
- Adw.AlertDialog {
- responses [
- cancel: _("Cancel"),
- delete: _("Delete") destructive,
- save: "Save" suggested,
- wipeHardDrive: "Wipe Hard Drive" destructive disabled,
- ]
- }
-
-
.. _Syntax ExtAdwMessageDialog:
Adw.MessageDialog Responses
@@ -111,7 +77,7 @@ Valid in `Adw.MessageDialog `_
The ``items`` block defines the items that will be added to the combo box. The optional ID can be used to refer to the item rather than its label.
-.. code-block:: blueprint
+.. code-block:: blueprintui
ComboBoxText {
items [
@@ -166,7 +132,7 @@ Valid in `Gtk.FileFilter `_.
The ``mime-types``, ``patterns``, and ``suffixes`` blocks define the items that will be added to the file filter. The ``mime-types`` block accepts mime types (including wildcards for subtypes, such as ``image/*``). The ``patterns`` block accepts glob patterns, and the ``suffixes`` block accepts file extensions.
-.. code-block:: blueprint
+.. code-block:: blueprintui
FileFilter {
mime-types [ "text/plain", "image/*" ]
@@ -189,7 +155,7 @@ Valid in `Gtk.Widget `_.
The ``layout`` block describes how the widget should be positioned within its parent. The available properties depend on the parent widget's layout manager.
-.. code-block:: blueprint
+.. code-block:: blueprintui
Grid {
Button {
@@ -227,9 +193,9 @@ Valid in `Gtk.BuilderListItemFactory `_, `Gtk.ColumnViewRow `_, or `Gtk.ColumnViewCell `_. The template object can be referenced with the ``template`` keyword.
+The template type must be `Gtk.ListItem `_. The template object can be referenced with the ``template`` keyword.
-.. code-block:: blueprint
+.. code-block:: blueprintui
ListView {
factory: BuilderListItemFactory {
@@ -277,7 +243,7 @@ Valid in `Gtk.SizeGroup `_.
The ``widgets`` block defines the widgets that will be added to the size group.
-.. code-block:: blueprint
+.. code-block:: blueprintui
Box {
Button button1 {}
@@ -303,7 +269,7 @@ Valid in `Gtk.StringList `_.
The ``strings`` block defines the strings in the string list.
-.. code-block:: blueprint
+.. code-block:: blueprintui
StringList {
strings ["violin", "guitar", _("harp")]
@@ -318,13 +284,13 @@ CSS Styles
.. rst-class:: grammar-block
ExtStyles = 'styles' '[' ExtStylesProp* ']'
- ExtStylesProp = `>
+ ExtStylesClass = `>
Valid in any `Gtk.Widget `_.
The ``styles`` block defines CSS classes that will be added to the widget.
-.. code-block:: blueprint
+.. code-block:: blueprintui
Button {
styles ["suggested-action"]
@@ -360,7 +326,7 @@ The ``action response`` extension sets the ``action`` child type for the child a
No more than one child of a dialog or infobar may have the ``default`` flag.
-.. code-block:: blueprint
+.. code-block:: blueprintui
Dialog {
[action response=ok default]
diff --git a/docs/reference/index.rst b/docs/reference/index.rst
index d49feb9..7670ba5 100644
--- a/docs/reference/index.rst
+++ b/docs/reference/index.rst
@@ -31,7 +31,7 @@ Tokens
IDENT
~~~~~
-An identifier starts with an ASCII underscore ``_`` or letter ``[A-Za-z]`` and consists of ASCII underscores, letters, digits ``[0-9]``, and dashes ``-``. Dashes are included for historical reasons, since GObject properties and signals are traditionally kebab-case.
+An identifier starts with an ASCII underscore ``_`` or letter ``[A-Za-z]`` and consists of ASCII underscores, letters, digits ``[0-9]``, and dashes ``-``. Dashes are included for historical reasons, since GObject properties are traditionally kebab-case.
.. _Syntax NUMBER:
diff --git a/docs/reference/menus.rst b/docs/reference/menus.rst
index 2d7bfea..525458b 100644
--- a/docs/reference/menus.rst
+++ b/docs/reference/menus.rst
@@ -21,7 +21,7 @@ Menus, such as the application menu, are defined using the ``menu`` keyword. Men
Example
~~~~~~~
-.. code-block:: blueprint
+.. code-block:: blueprintui
menu my_menu {
submenu {
@@ -53,7 +53,7 @@ The most common menu attributes are ``label``, ``action``, and ``icon``. Because
Example
~~~~~~~
-.. code-block:: blueprint
+.. code-block:: blueprintui
menu {
item ("label")
diff --git a/docs/reference/objects.rst b/docs/reference/objects.rst
index 6f76da6..8acbb32 100644
--- a/docs/reference/objects.rst
+++ b/docs/reference/objects.rst
@@ -24,7 +24,7 @@ Optionally, objects may have an ID to provide a handle for other parts of the bl
Example
~~~~~~~
-.. code-block:: blueprint
+.. code-block:: blueprintui
Label label1 {
label: "Hello, world!";
@@ -58,7 +58,7 @@ Properties
.. rst-class:: grammar-block
- Property = `> ':' ( :ref:`Binding` | :ref:`ExprValue` | :ref:`ObjectValue` | :ref:`Value` ) ';'
+ Property = `> ':' ( :ref:`PropertyBinding` | :ref:`Binding` | :ref:`ObjectValue` | :ref:`Value` ) ';'
Properties specify the details of each object, like a label's text, an image's icon name, or the margins on a container.
@@ -69,7 +69,7 @@ A property's value can be another object, either inline or referenced by ID.
Example
~~~~~~~
-.. code-block:: blueprint
+.. code-block:: blueprintui
Label {
label: "text";
@@ -91,7 +91,7 @@ Signal Handlers
.. rst-class:: grammar-block
Signal = `> ('::' `>)? '=>' '$' `> '(' `>? ')' (SignalFlag)* ';'
- SignalFlag = 'after' | 'swapped' | 'not-swapped'
+ SignalFlag = 'after' | 'swapped'
Signals are one way to respond to user input (another is `actions `_, which use the `action-name property `_).
@@ -99,17 +99,16 @@ Signals provide a handle for your code to listen to events in the UI. The handle
Optionally, you can provide an object ID to use when connecting the signal.
-The ``swapped`` flag is used to swap the order of the object and userdata arguments in C applications. If an object argument is specified, then this is the default behavior, so the ``not-swapped`` flag can be used to prevent the swap.
-
Example
~~~~~~~
-.. code-block:: blueprint
+.. code-block:: blueprintui
Button {
clicked => $on_button_clicked();
}
+
.. _Syntax Child:
Children
@@ -142,7 +141,7 @@ Examples
Add children to a container
+++++++++++++++++++++++++++
-.. code-block:: blueprint
+.. code-block:: blueprintui
Button {
Image {}
@@ -151,7 +150,7 @@ Add children to a container
Child types
+++++++++++
-.. code-block:: blueprint
+.. code-block:: blueprintui
HeaderBar {
[start]
@@ -166,7 +165,7 @@ Child types
Child extensions
++++++++++++++++
-.. code-block:: blueprint
+.. code-block:: blueprintui
Dialog {
// Here, a child extension annotation defines the button's response.
@@ -177,7 +176,7 @@ Child extensions
Internal children
+++++++++++++++++
-.. code-block:: blueprint
+.. code-block:: blueprintui
Dialog {
[internal-child content_area]
diff --git a/docs/reference/templates.rst b/docs/reference/templates.rst
index 74e4225..fa4c264 100644
--- a/docs/reference/templates.rst
+++ b/docs/reference/templates.rst
@@ -15,7 +15,7 @@ Widget subclassing is one of the primary techniques for structuring an applicati
You could implement this with the following blueprint:
-.. code-block:: blueprint
+.. code-block:: blueprintui
using Gtk 4.0;
@@ -39,7 +39,7 @@ We can solve these problems by giving each widget its own blueprint file, which
For this to work, we need to specify in the blueprint which object is the one being instantiated. We do this with a template block:
-.. code-block:: blueprint
+.. code-block:: blueprintui
using Gtk 4.0;
@@ -56,7 +56,7 @@ This blueprint can only be used by the ``MapsHeaderBar`` constructor. Instantiat
This ``MapsHeaderBar`` class, along with its blueprint template, can then be referenced in another blueprint:
-.. code-block:: blueprint
+.. code-block:: blueprintui
using Gtk 4.0;
@@ -66,31 +66,17 @@ This ``MapsHeaderBar`` class, along with its blueprint template, can then be ref
}
}
-Type & Parent Parameters
-~~~~~~~~~~~~~~~~~~~~~~~~
+ID & Parent Parameters
+~~~~~~~~~~~~~~~~~~~~~~
The type name that directly follows the ``template`` keyword is the type of the template class. In most cases, this will be an extern type starting with ``$`` and matching the class name in the application code. Templates for use in a `Gtk.BuilderListItemFactory `_ use ``ListItem`` as the type name instead.
The parent type is optional, and may only be present if the template type is extern. It enables limited type checking for the properties and signals of the template object.
-Referencing a Template
-----------------------
-
-To reference the template object in a binding or expression, use the ``template`` keyword:
-
-.. code-block:: blueprint
-
- template $MyTemplate {
- prop1: "Hello, world!";
- prop2: bind template.prop1;
- }
-
-
Language Implementations
------------------------
-- **C** ``gtk_widget_class_set_template ()``: https://docs.gtk.org/gtk4/class.Widget.html#building-composite-widgets-from-template-xml
-- **gtk-rs** ``#[template]``: https://gtk-rs.org/gtk4-rs/stable/latest/book/composite_templates.html
-- **GJS** ``GObject.registerClass()``: https://gjs.guide/guides/gtk/3/14-templates.html
-- **PyGObject** ``@Gtk.Template``: https://pygobject.gnome.org/guide/gtk_template.html
+- ``gtk_widget_class_set_template ()`` in C: https://docs.gtk.org/gtk4/class.Widget.html#building-composite-widgets-from-template-xml
+- ``#[template]`` in gtk-rs: https://gtk-rs.org/gtk4-rs/stable/latest/book/composite_templates.html
+- ``GObject.registerClass()`` in GJS: https://gjs.guide/guides/gtk/3/14-templates.html
\ No newline at end of file
diff --git a/docs/reference/values.rst b/docs/reference/values.rst
index fd414a8..9c0a09f 100644
--- a/docs/reference/values.rst
+++ b/docs/reference/values.rst
@@ -25,7 +25,8 @@ Literals
NumberLiteral = ( '-' | '+' )? `>
IdentLiteral = `>
-Literals are used to specify values for properties. They can be strings, numbers, references to objects, ``null``, types, boolean values, or enum members.
+Literals are used to specify values for properties. They can be strings, numbers, references to objects, types, boolean values, or enum members.
+
.. _Syntax TypeLiteral:
@@ -44,7 +45,7 @@ The type of a ``typeof<>`` literal is `GType ;
@@ -65,7 +66,7 @@ Flags are used to specify a set of options. One or more of the available flag va
Example
~~~~~~~
-.. code-block:: blueprint
+.. code-block:: blueprintui
Adw.TabView {
shortcuts: control_tab | control_shift_tab;
@@ -84,7 +85,7 @@ Translated Strings
Use ``_("...")`` to mark strings as translatable. You can put a comment for translators on the line above if needed.
-.. code-block:: blueprint
+.. code-block:: blueprintui
Gtk.Label label {
/* Translators: This is the main text of the welcome screen */
@@ -93,7 +94,7 @@ Use ``_("...")`` to mark strings as translatable. You can put a comment for tran
Use ``C_("context", "...")`` to add a *message context* to a string to disambiguate it, in case the same string appears in different places. Remember, two strings might be the same in one language but different in another depending on context.
-.. code-block:: blueprint
+.. code-block:: blueprintui
Gtk.Label label {
/* Translators: This is a section in the preferences window */
@@ -101,49 +102,46 @@ Use ``C_("context", "...")`` to add a *message context* to a string to disambigu
}
-.. _Syntax Binding:
+.. _Syntax PropertyBinding:
-Bindings
---------
+Property Bindings
+-----------------
.. rst-class:: grammar-block
- Binding = 'bind' :ref:`Expression` (BindingFlag)*
- BindingFlag = 'inverted' | 'bidirectional' | 'no-sync-create'
+ PropertyBinding = 'bind-property' `> '.' `> (PropertyBindingFlag)*
+ PropertyBindingFlag = 'inverted' | 'bidirectional' | 'no-sync-create'
-Bindings keep a property updated as other properties change. They can be used to keep the UI in sync with application data, or to connect two parts of the UI.
-
-The simplest bindings connect to a property of another object in the blueprint. When that other property changes, the bound property updates as well. More advanced bindings can do multi-step property lookups and can even call application code to compute values. See :ref:`the expressions page`.
-
-Simple Bindings
-~~~~~~~~~~~~~~~
-
-A binding that consists of a source object and a single lookup is called a "simple binding". These are implemented using `GObject property bindings `_ and support a few flags:
-
-- ``inverted``: For boolean properties, the target is set to the inverse of the source property.
-- ``bidirectional``: The binding is two-way, so changes to the target property will also update the source property.
-- ``no-sync-create``: Normally, when a binding is created, the target property is immediately updated with the current value of the source property. This flag disables that behavior, and the bound property will be updated the next time the source property changes.
-
-Complex Bindings
-~~~~~~~~~~~~~~~~
-
-Bindings with more complex expressions are implemented with `Gtk.Expression `_. These bindings do not support flags.
+Bindings keep a property updated as another property changes. They can be used to keep the UI in sync with application data, or to connect two parts of the UI.
Example
~~~~~~~
-.. code-block:: blueprint
+.. code-block:: blueprintui
- /* Use bindings to show a label when a switch
+ /* Use property bindings to show a label when a switch
* is active, without any application code */
- Switch show_label {}
+ Switch advanced_feature {}
- Label {
- visible: bind show_label.active;
- label: _("I'm a label that's only visible when the switch is enabled!");
+ Label warning {
+ visible: bind-property advanced_feature.active;
+ label: _("This is an advanced feature. Use with caution!");
}
+
+.. _Syntax Binding:
+
+Expression Bindings
+-------------------
+
+.. rst-class:: grammar-block
+
+ Binding = 'bind' :ref:`Expression`
+
+Expression bindings serve the same purpose as property bindings, but are more powerful. They can call application code to compute the value of a property, and they can do multi-step property lookups. See :ref:`the expressions page`.
+
+
.. _Syntax ObjectValue:
Object Values
@@ -168,14 +166,3 @@ String Values
StringValue = :ref:`Translated` | :ref:`QuotedLiteral`
Menus, as well as some :ref:`extensions`, have properties that can only be string literals or translated strings.
-
-.. _Syntax ArrayValue:
-
-Array Values
--------------
-
-.. rst-class:: grammar-block
-
- ArrayValue = '[' (:ref:`StringValue`),* ']'
-
-For now, it only supports :ref:`Strings`. This is because Gtk.Builder only supports string arrays.
diff --git a/docs/setup.rst b/docs/setup.rst
index 914c753..839f8f6 100644
--- a/docs/setup.rst
+++ b/docs/setup.rst
@@ -8,7 +8,7 @@ Setting up Blueprint on a new or existing project
Using the porting tool
~~~~~~~~~~~~~~~~~~~~~~
-Clone `blueprint-compiler `_
+Clone `blueprint-compiler `_
from source. You can install it using ``meson _build`` and ``ninja -C _build install``,
or you can leave it uninstalled.
@@ -29,7 +29,7 @@ blueprint-compiler works as a meson subproject.
[wrap-git]
directory = blueprint-compiler
- url = https://gitlab.gnome.org/GNOME/blueprint-compiler.git
+ url = https://gitlab.gnome.org/jwestman/blueprint-compiler.git
revision = main
depth = 1
diff --git a/docs/translations.rst b/docs/translations.rst
index 7af2099..b143d36 100644
--- a/docs/translations.rst
+++ b/docs/translations.rst
@@ -5,7 +5,7 @@ Translations
Blueprint files can be translated with xgettext. To mark a string as translated,
use the following syntax:
-.. code-block:: blueprint
+.. code-block::
_("translated string")
@@ -24,8 +24,6 @@ If you're using Meson's `i18n module ;
- as: 1;
- signal => $on_signal() after; // Inline comment
- type_value: typeof<$MyTemplate>;
-}
-
-Dialog {
- [action response=ok]
- $MyButton {}
-}
-
-menu menu {
- item ("test")
-
- item {
- label: "test";
- }
-
- item ("test")
-}
-
-Adw.MessageDialog {
- responses [
- save: "Save" suggested disabled,
- ]
-}
-
-Adw.Breakpoint {
- condition ("width < 100")
-
- setters {
- label2.label: _("Hello, world!");
- label2.visible: false;
- label2.extra-menu: null;
- }
-}
diff --git a/tests/formatting/in1.blp b/tests/formatting/in1.blp
deleted file mode 100644
index f77a3f4..0000000
--- a/tests/formatting/in1.blp
+++ /dev/null
@@ -1 +0,0 @@
-using Gtk 4.0;using Adw 1;Overlay{Label label{label:_("'Hello World!' \"\n\t\"");}[overlay]Button{notify::icon-name=>$on_icon_name_changed(label)swapped;styles["destructive"]}visible:bind $isVisible(label.visible,my-menu)as;width-request:bind label.width-request no-sync-create;}menu my-menu{item(_("Label"), "action-name", "icon-name")item{action:"win.format";}}
\ No newline at end of file
diff --git a/tests/formatting/in2.blp b/tests/formatting/in2.blp
deleted file mode 100644
index 137871a..0000000
--- a/tests/formatting/in2.blp
+++ /dev/null
@@ -1,40 +0,0 @@
-using Gtk 4.0;
-
- using Adw 1;
-
-Overlay {
-
-Label
-label
-{
-label
-:
-_
-(
-"'Hello World!' \"\n\t\""
-)
-;
-}
-[
- overlay
-] Button
-{ notify
-:: icon-name
-=> $ on_icon_name_changed ( label )
-swapped ;
-styles
-[ "destructive" ]
-}
-visible
-: bind $ isVisible ( label.visible ,
-my-menu ) as
- < bool > ; width-request : bind label . width-request no-sync-create ; }
- menu my-menu
-{ item ( _ ( "Label" ) , "action-name" , "icon-name" ) item { action : "win.format" ; } }
-
-
-
-
-
-
-
diff --git a/tests/formatting/lists_in.blp b/tests/formatting/lists_in.blp
deleted file mode 100644
index 66b37a2..0000000
--- a/tests/formatting/lists_in.blp
+++ /dev/null
@@ -1,21 +0,0 @@
-using Gtk 4.0;
-
-Box {
- styles []
-}
-
-Box {
- styles ["a"]
-}
-
-Box {
- styles ["a",]
-}
-
-Box {
- styles ["a", "b"]
-}
-
-Box {
- styles ["a", "b",]
-}
diff --git a/tests/formatting/lists_out.blp b/tests/formatting/lists_out.blp
deleted file mode 100644
index 7f1fe4a..0000000
--- a/tests/formatting/lists_out.blp
+++ /dev/null
@@ -1,31 +0,0 @@
-using Gtk 4.0;
-
-Box {
- styles []
-}
-
-Box {
- styles [
- "a",
- ]
-}
-
-Box {
- styles [
- "a",
- ]
-}
-
-Box {
- styles [
- "a",
- "b",
- ]
-}
-
-Box {
- styles [
- "a",
- "b",
- ]
-}
diff --git a/tests/formatting/out.blp b/tests/formatting/out.blp
deleted file mode 100644
index b84c25f..0000000
--- a/tests/formatting/out.blp
+++ /dev/null
@@ -1,28 +0,0 @@
-using Gtk 4.0;
-using Adw 1;
-
-Overlay {
- Label label {
- label: _("'Hello World!' \"\n\t\"");
- }
-
- [overlay]
- Button {
- notify::icon-name => $on_icon_name_changed(label) swapped;
-
- styles [
- "destructive",
- ]
- }
-
- visible: bind $isVisible(label.visible, my-menu) as ;
- width-request: bind label.width-request no-sync-create;
-}
-
-menu my-menu {
- item (_("Label"), "action-name", "icon-name")
-
- item {
- action: "win.format";
- }
-}
diff --git a/tests/formatting/string_in.blp b/tests/formatting/string_in.blp
deleted file mode 100644
index 451d879..0000000
--- a/tests/formatting/string_in.blp
+++ /dev/null
@@ -1,5 +0,0 @@
-using Gtk 4.0;
-
-Label {
- label: "\"'\'\t\n\\'";
-}
diff --git a/tests/formatting/string_out.blp b/tests/formatting/string_out.blp
deleted file mode 100644
index 451d879..0000000
--- a/tests/formatting/string_out.blp
+++ /dev/null
@@ -1,5 +0,0 @@
-using Gtk 4.0;
-
-Label {
- label: "\"'\'\t\n\\'";
-}
diff --git a/tests/fuzz.py b/tests/fuzz.py
index 81a9058..ad1c764 100644
--- a/tests/fuzz.py
+++ b/tests/fuzz.py
@@ -1,21 +1,20 @@
-import os
-import sys
-
+import os, sys
from pythonfuzz.main import PythonFuzz
from blueprintcompiler.outputs.xml import XmlOutput
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
-from blueprintcompiler import decompiler, gir, parser, tokenizer, utils
+from blueprintcompiler import tokenizer, parser, decompiler, gir
from blueprintcompiler.completions import complete
from blueprintcompiler.errors import (
+ PrintableError,
+ MultipleErrors,
CompileError,
CompilerBugError,
- MultipleErrors,
- PrintableError,
)
from blueprintcompiler.tokenizer import Token, TokenType, tokenize
+from blueprintcompiler import utils
@PythonFuzz
diff --git a/tests/meson.build b/tests/meson.build
index 8a5a634..8e51a47 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -1 +1 @@
-test('tests', py, args: ['-m', 'unittest'], workdir: meson.project_source_root())
+test('tests', py, args: ['-m', 'unittest'], workdir: meson.source_root())
diff --git a/tests/sample_errors/a11y_list_empty.blp b/tests/sample_errors/a11y_list_empty.blp
deleted file mode 100644
index 401c912..0000000
--- a/tests/sample_errors/a11y_list_empty.blp
+++ /dev/null
@@ -1,9 +0,0 @@
-using Gtk 4.0;
-
-Box {
- accessibility {
- label: _("Hello, world!");
- labelled-by: [];
- checked: true;
- }
-}
diff --git a/tests/sample_errors/a11y_list_empty.err b/tests/sample_errors/a11y_list_empty.err
deleted file mode 100644
index d2b0c86..0000000
--- a/tests/sample_errors/a11y_list_empty.err
+++ /dev/null
@@ -1 +0,0 @@
-6,5,11,'labelled-by' may not be empty
diff --git a/tests/sample_errors/a11y_non_list_property.blp b/tests/sample_errors/a11y_non_list_property.blp
deleted file mode 100644
index daa3a96..0000000
--- a/tests/sample_errors/a11y_non_list_property.blp
+++ /dev/null
@@ -1,15 +0,0 @@
-using Gtk 4.0;
-
-Box {
- accessibility {
- label: _("Hello, world!");
- active-descendant: [my_label1, my_label2, my_label3];
- checked: true;
- }
-}
-
-Label my_label1 {}
-
-Label my_label2 {}
-
-Label my_label3 {}
diff --git a/tests/sample_errors/a11y_non_list_property.err b/tests/sample_errors/a11y_non_list_property.err
deleted file mode 100644
index 038da92..0000000
--- a/tests/sample_errors/a11y_non_list_property.err
+++ /dev/null
@@ -1 +0,0 @@
-6,5,17,'active-descendant' does not allow a list of values
diff --git a/tests/sample_errors/adw_alert_dialog_duplicate_flags.blp b/tests/sample_errors/adw_alert_dialog_duplicate_flags.blp
deleted file mode 100644
index 65fae22..0000000
--- a/tests/sample_errors/adw_alert_dialog_duplicate_flags.blp
+++ /dev/null
@@ -1,9 +0,0 @@
-using Gtk 4.0;
-using Adw 1;
-
-Adw.AlertDialog {
- responses [
- cancel: _("Cancel") disabled disabled,
- ok: _("Ok") destructive suggested,
- ]
-}
\ No newline at end of file
diff --git a/tests/sample_errors/adw_alert_dialog_duplicate_flags.err b/tests/sample_errors/adw_alert_dialog_duplicate_flags.err
deleted file mode 100644
index 91f0a3d..0000000
--- a/tests/sample_errors/adw_alert_dialog_duplicate_flags.err
+++ /dev/null
@@ -1,2 +0,0 @@
-6,34,8,Duplicate 'disabled' flag
-7,29,9,'suggested' and 'destructive' are exclusive
\ No newline at end of file
diff --git a/tests/sample_errors/array_wrong_type.blp b/tests/sample_errors/array_wrong_type.blp
deleted file mode 100644
index c8cdd9b..0000000
--- a/tests/sample_errors/array_wrong_type.blp
+++ /dev/null
@@ -1,5 +0,0 @@
-using Gtk 4.0;
-
-Label {
- label: [1];
-}
diff --git a/tests/sample_errors/array_wrong_type.err b/tests/sample_errors/array_wrong_type.err
deleted file mode 100644
index 20e8e0d..0000000
--- a/tests/sample_errors/array_wrong_type.err
+++ /dev/null
@@ -1 +0,0 @@
-4,12,3,Cannot assign array to string
\ No newline at end of file
diff --git a/tests/sample_errors/array_wrong_type_value.blp b/tests/sample_errors/array_wrong_type_value.blp
deleted file mode 100644
index 9b39e00..0000000
--- a/tests/sample_errors/array_wrong_type_value.blp
+++ /dev/null
@@ -1,6 +0,0 @@
-using Gtk 4.0;
-
-AboutDialog about {
- valign: center;
- authors: [1];
-}
diff --git a/tests/sample_errors/array_wrong_type_value.err b/tests/sample_errors/array_wrong_type_value.err
deleted file mode 100644
index 7ba59a8..0000000
--- a/tests/sample_errors/array_wrong_type_value.err
+++ /dev/null
@@ -1 +0,0 @@
-5,15,1,Cannot convert number to string
\ No newline at end of file
diff --git a/tests/sample_errors/bad_escape_sequence.blp b/tests/sample_errors/bad_escape_sequence.blp
deleted file mode 100644
index 4b109c6..0000000
--- a/tests/sample_errors/bad_escape_sequence.blp
+++ /dev/null
@@ -1,5 +0,0 @@
-using Gtk 4.0;
-
-Label {
- label: '***** \f *****';
-}
diff --git a/tests/sample_errors/bad_escape_sequence.err b/tests/sample_errors/bad_escape_sequence.err
deleted file mode 100644
index e4ec183..0000000
--- a/tests/sample_errors/bad_escape_sequence.err
+++ /dev/null
@@ -1 +0,0 @@
-4,17,2,Invalid escape sequence '\f'
\ No newline at end of file
diff --git a/tests/sample_errors/binding_flags.blp b/tests/sample_errors/binding_flags.blp
deleted file mode 100644
index 7e94058..0000000
--- a/tests/sample_errors/binding_flags.blp
+++ /dev/null
@@ -1,5 +0,0 @@
-using Gtk 4.0;
-
-Label {
- label: bind $my_closure() as