diff --git a/blueprintcompiler/ast.py b/blueprintcompiler/ast.py index 0ac40f9..323455b 100644 --- a/blueprintcompiler/ast.py +++ b/blueprintcompiler/ast.py @@ -23,6 +23,7 @@ from .ast_utils import * from .errors import CompileError, CompilerBugError, MultipleErrors from . import gir from .lsp_utils import Completion, CompletionItemKind, SemanticToken, SemanticTokenType +from .parse_tree import * from .tokenizer import Token from .utils import lazy_prop from .xml_emitter import XmlEmitter @@ -99,6 +100,12 @@ class UI(AstNode): class GtkDirective(AstNode): + grammar = Statement( + Match("using").err("File must start with a \"using Gtk\" directive (e.g. `using Gtk 4.0;`)"), + Match("Gtk").err("File must start with a \"using Gtk\" directive (e.g. `using Gtk 4.0;`)"), + UseNumberText("version").expected("a version number for GTK"), + ) + @validate("version") def gtk_version(self): if self.tokens["version"] not in ["4.0"]: @@ -120,6 +127,12 @@ class GtkDirective(AstNode): class Import(AstNode): + grammar = Statement( + "using", + UseIdent("namespace").expected("a GIR namespace"), + UseNumberText("version").expected("a version number"), + ) + @validate("namespace", "version") def namespace_exists(self): gir.get_namespace(self.tokens["namespace"], self.tokens["version"]) diff --git a/blueprintcompiler/extensions/__init__.py b/blueprintcompiler/extensions/__init__.py index f5ea13b..e7d72ee 100644 --- a/blueprintcompiler/extensions/__init__.py +++ b/blueprintcompiler/extensions/__init__.py @@ -1,18 +1,18 @@ """ Contains all the syntax beyond basic objects, properties, signal, and templates. """ -from .gtk_a11y import a11y -from .gtk_combo_box_text import items +from .gtk_a11y import A11y +from .gtk_combo_box_text import Items from .gtk_file_filter import mime_types, patterns, suffixes -from .gtk_layout import layout +from .gtk_layout import Layout from .gtk_menu import menu -from .gtk_size_group import widgets -from .gtk_string_list import strings -from .gtk_styles import styles +from .gtk_size_group import Widgets +from .gtk_string_list import Strings +from .gtk_styles import Styles OBJECT_HOOKS = [menu] OBJECT_CONTENT_HOOKS = [ - a11y, styles, layout, mime_types, patterns, suffixes, widgets, items, - strings, + A11y, Styles, Layout, mime_types, patterns, suffixes, Widgets, Items, + Strings, ] diff --git a/blueprintcompiler/extensions/gtk_a11y.py b/blueprintcompiler/extensions/gtk_a11y.py index 01e5a83..cbf69fa 100644 --- a/blueprintcompiler/extensions/gtk_a11y.py +++ b/blueprintcompiler/extensions/gtk_a11y.py @@ -105,20 +105,13 @@ def _get_docs(gir, name): ).doc -class A11y(AstNode): - @validate("accessibility") - def container_is_widget(self): - self.validate_parent_type("Gtk", "Widget", "accessibility properties") - - - def emit_xml(self, xml: XmlEmitter): - xml.start_tag("accessibility") - for child in self.children: - child.emit_xml(xml) - xml.end_tag() - - class A11yProperty(BaseTypedAttribute): + grammar = Statement( + UseIdent("name"), + ":", + value.expected("a value"), + ) + @property def tag_name(self): name = self.tokens["name"] @@ -151,23 +144,23 @@ class A11yProperty(BaseTypedAttribute): return _get_docs(self.root.gir, self.tokens["name"]) -a11y_prop = Group( - A11yProperty, - Statement( - UseIdent("name"), - ":", - value.expected("a value"), - ) -) - -a11y = Group( - A11y, - [ +class A11y(AstNode): + grammar = [ Keyword("accessibility"), "{", - Until(a11y_prop, "}"), + Until(A11yProperty, "}"), ] -) + + @validate("accessibility") + def container_is_widget(self): + self.validate_parent_type("Gtk", "Widget", "accessibility properties") + + + def emit_xml(self, xml: XmlEmitter): + xml.start_tag("accessibility") + for child in self.children: + child.emit_xml(xml) + xml.end_tag() @completer( diff --git a/blueprintcompiler/extensions/gtk_combo_box_text.py b/blueprintcompiler/extensions/gtk_combo_box_text.py index 71dec36..d77bc5c 100644 --- a/blueprintcompiler/extensions/gtk_combo_box_text.py +++ b/blueprintcompiler/extensions/gtk_combo_box_text.py @@ -28,19 +28,6 @@ from ..parser_utils import * from ..xml_emitter import XmlEmitter -class Items(AstNode): - @validate("items") - def container_is_combo_box_text(self): - self.validate_parent_type("Gtk", "ComboBoxText", "combo box items") - - - def emit_xml(self, xml: XmlEmitter): - xml.start_tag("items") - for child in self.children: - child.emit_xml(xml) - xml.end_tag() - - class Item(BaseTypedAttribute): tag_name = "item" attr_name = "id" @@ -61,15 +48,25 @@ item = Group( ] ) -items = Group( - Items, - [ + +class Items(AstNode): + grammar = [ Keyword("items"), "[", Delimited(item, ","), "]", ] -) + + @validate("items") + def container_is_combo_box_text(self): + self.validate_parent_type("Gtk", "ComboBoxText", "combo box items") + + + def emit_xml(self, xml: XmlEmitter): + xml.start_tag("items") + for child in self.children: + child.emit_xml(xml) + xml.end_tag() @completer( diff --git a/blueprintcompiler/extensions/gtk_layout.py b/blueprintcompiler/extensions/gtk_layout.py index 833bc0b..8289a29 100644 --- a/blueprintcompiler/extensions/gtk_layout.py +++ b/blueprintcompiler/extensions/gtk_layout.py @@ -27,19 +27,6 @@ from ..parser_utils import * from ..xml_emitter import XmlEmitter -class Layout(AstNode): - @validate("layout") - def container_is_widget(self): - self.validate_parent_type("Gtk", "Widget", "layout properties") - - - def emit_xml(self, xml: XmlEmitter): - xml.start_tag("layout") - for child in self.children: - child.emit_xml(xml) - xml.end_tag() - - class LayoutProperty(BaseAttribute): tag_name = "property" @@ -58,14 +45,24 @@ layout_prop = Group( ) ) -layout = Group( - Layout, - Sequence( + +class Layout(AstNode): + grammar = Sequence( Keyword("layout"), "{", Until(layout_prop, "}"), ) -) + + @validate("layout") + def container_is_widget(self): + self.validate_parent_type("Gtk", "Widget", "layout properties") + + + def emit_xml(self, xml: XmlEmitter): + xml.start_tag("layout") + for child in self.children: + child.emit_xml(xml) + xml.end_tag() @completer( diff --git a/blueprintcompiler/extensions/gtk_size_group.py b/blueprintcompiler/extensions/gtk_size_group.py index 3f9a547..ea9a95c 100644 --- a/blueprintcompiler/extensions/gtk_size_group.py +++ b/blueprintcompiler/extensions/gtk_size_group.py @@ -27,19 +27,9 @@ from ..parser_utils import * from ..xml_emitter import XmlEmitter -class Widgets(AstNode): - @validate("widgets") - def container_is_size_group(self): - self.validate_parent_type("Gtk", "SizeGroup", "size group properties") - - def emit_xml(self, xml: XmlEmitter): - xml.start_tag("widgets") - for child in self.children: - child.emit_xml(xml) - xml.end_tag() - - class Widget(AstNode): + grammar = UseIdent("name") + @validate("name") def obj_widget(self): object = self.root.objects_by_id.get(self.tokens["name"]) @@ -58,21 +48,23 @@ class Widget(AstNode): xml.put_self_closing("widget", name=self.tokens["name"]) -widgets = Group( - Widgets, - [ +class Widgets(AstNode): + grammar = [ Keyword("widgets"), "[", - Delimited( - Group( - Widget, - UseIdent("name"), - ), - ",", - ), + Delimited(Widget, ","), "]", ] -) + + @validate("widgets") + def container_is_size_group(self): + self.validate_parent_type("Gtk", "SizeGroup", "size group properties") + + def emit_xml(self, xml: XmlEmitter): + xml.start_tag("widgets") + for child in self.children: + child.emit_xml(xml) + xml.end_tag() @completer( diff --git a/blueprintcompiler/extensions/gtk_string_list.py b/blueprintcompiler/extensions/gtk_string_list.py index 9647080..db19a8c 100644 --- a/blueprintcompiler/extensions/gtk_string_list.py +++ b/blueprintcompiler/extensions/gtk_string_list.py @@ -28,20 +28,9 @@ from ..parser_utils import * from ..xml_emitter import XmlEmitter -class Items(AstNode): - @validate("items") - def container_is_string_list(self): - self.validate_parent_type("Gtk", "StringList", "StringList items") - - - def emit_xml(self, xml: XmlEmitter): - xml.start_tag("items") - for child in self.children: - child.emit_xml(xml) - xml.end_tag() - - class Item(AstNode): + grammar = value + @property def value_type(self): return StringType() @@ -54,20 +43,24 @@ class Item(AstNode): xml.end_tag() -item = Group( - Item, - value, -) - -strings = Group( - Items, - [ +class Strings(AstNode): + grammar = [ Keyword("strings"), "[", - Delimited(item, ","), + Delimited(Item, ","), "]", ] -) + + @validate("items") + def container_is_string_list(self): + self.validate_parent_type("Gtk", "StringList", "StringList items") + + + def emit_xml(self, xml: XmlEmitter): + xml.start_tag("items") + for child in self.children: + child.emit_xml(xml) + xml.end_tag() @completer( diff --git a/blueprintcompiler/extensions/gtk_styles.py b/blueprintcompiler/extensions/gtk_styles.py index 48e1fd6..193f01f 100644 --- a/blueprintcompiler/extensions/gtk_styles.py +++ b/blueprintcompiler/extensions/gtk_styles.py @@ -27,7 +27,21 @@ from ..parser_utils import * from ..xml_emitter import XmlEmitter +class StyleClass(AstNode): + grammar = UseQuoted("name") + + def emit_xml(self, xml): + xml.put_self_closing("class", name=self.tokens["name"]) + + class Styles(AstNode): + grammar = [ + Keyword("styles"), + "[", + Delimited(StyleClass, ","), + "]", + ] + @validate("styles") def container_is_widget(self): self.validate_parent_type("Gtk", "Widget", "style classes") @@ -39,28 +53,6 @@ class Styles(AstNode): xml.end_tag() -class StyleClass(AstNode): - def emit_xml(self, xml): - xml.put_self_closing("class", name=self.tokens["name"]) - - -styles = Group( - Styles, - [ - Keyword("styles"), - "[", - Delimited( - Group( - StyleClass, - UseQuoted("name") - ), - ",", - ), - "]", - ] -) - - @completer( applies_in=[ast.ObjectContent], applies_in_subclass=("Gtk", "Widget"), diff --git a/blueprintcompiler/parse_tree.py b/blueprintcompiler/parse_tree.py index dcb2012..00ef0c7 100644 --- a/blueprintcompiler/parse_tree.py +++ b/blueprintcompiler/parse_tree.py @@ -24,7 +24,6 @@ import typing as T from collections import defaultdict from enum import Enum -from .ast import AstNode from .errors import assert_true, CompilerBugError, CompileError, UnexpectedTokenError from .tokenizer import Token, TokenType @@ -77,7 +76,7 @@ class ParseGroup: self.keys[key] = val self.tokens[key] = token - def to_ast(self) -> AstNode: + def to_ast(self): """ Creates an AST node from the match group. """ children = [child.to_ast() for child in self.children] @@ -522,5 +521,7 @@ def to_parse_node(value) -> ParseNode: return Match(value) elif isinstance(value, list): return Sequence(*value) + elif isinstance(value, type) and hasattr(value, "grammar"): + return Group(value, getattr(value, "grammar")) else: return value diff --git a/blueprintcompiler/parser.py b/blueprintcompiler/parser.py index 53738b3..04eda48 100644 --- a/blueprintcompiler/parser.py +++ b/blueprintcompiler/parser.py @@ -29,24 +29,6 @@ from .extensions import OBJECT_HOOKS, OBJECT_CONTENT_HOOKS def parse(tokens) -> T.Tuple[ast.UI, T.Optional[MultipleErrors]]: """ Parses a list of tokens into an abstract syntax tree. """ - gtk_directive = Group( - ast.GtkDirective, - Statement( - Match("using").err("File must start with a \"using Gtk\" directive (e.g. `using Gtk 4.0;`)"), - Match("Gtk").err("File must start with a \"using Gtk\" directive (e.g. `using Gtk 4.0;`)"), - UseNumberText("version").expected("a version number for GTK"), - ) - ) - - import_statement = Group( - ast.Import, - Statement( - "using", - UseIdent("namespace").expected("a GIR namespace"), - UseNumberText("version").expected("a version number"), - ) - ) - object = Group( ast.Object, None @@ -152,8 +134,8 @@ def parse(tokens) -> T.Tuple[ast.UI, T.Optional[MultipleErrors]]: ui = Group( ast.UI, [ - gtk_directive, - ZeroOrMore(import_statement), + ast.GtkDirective, + ZeroOrMore(ast.Import), Until(AnyOf( *OBJECT_HOOKS, template,