diff --git a/gtkblueprinttool/ast.py b/gtkblueprinttool/ast.py index ab59ee2..49a4c79 100644 --- a/gtkblueprinttool/ast.py +++ b/gtkblueprinttool/ast.py @@ -123,7 +123,6 @@ class Object(AstNode): def emit_xml(self, xml: XmlEmitter): - print("Emitting object XML! ", self.gir_class) xml.start_tag("object", **{ "class": self.gir_class.glib_type_name if self.gir_class else self.tokens["class_name"], "id": self.tokens["id"], @@ -151,13 +150,13 @@ class ObjectContent(AstNode): else: raise CompilerBugError() - @validate() - def only_one_style_class(self): - if len(self.children[Style]) > 1: - raise CompileError( - f"Only one style directive allowed per object, but this object contains {len(self.children[Style])}", - start=self.children[Style][1].group.start, - ) + # @validate() + # def only_one_style_class(self): + # if len(self.children[Style]) > 1: + # raise CompileError( + # f"Only one style directive allowed per object, but this object contains {len(self.children[Style])}", + # start=self.children[Style][1].group.start, + # ) def emit_xml(self, xml: XmlEmitter): for x in self.children: @@ -279,53 +278,3 @@ class Signal(AstNode): if self.tokens["detail_name"]: name += "::" + self.tokens["detail_name"] xml.put_self_closing("signal", name=name, handler=self.tokens["handler"], swapped="true" if self.tokens["swapped"] else None) - - -class Style(AstNode): - def emit_xml(self, xml: XmlEmitter): - xml.start_tag("style") - for child in self.children: - child.emit_xml(xml) - xml.end_tag() - - -class StyleClass(AstNode): - def emit_xml(self, xml): - xml.put_self_closing("class", name=self.tokens["name"]) - - -class Menu(AstNode): - def emit_xml(self, xml: XmlEmitter): - xml.start_tag(self.tokens["tag"], id=self.tokens["id"]) - for child in self.children: - child.emit_xml() - xml.end_tag() - - -class BaseAttribute(AstNode): - tag_name: str = "" - - def emit_xml(self, xml: XmlEmitter): - xml.start_tag( - self.tag_name, - name=self.tokens["name"], - translatable="yes" if self.tokens["translatable"] else None, - ) - xml.put_text(str(self.tokens["value"])) - xml.end_tag() - - -class MenuAttribute(BaseAttribute): - tag_name = "attribute" - - -class Layout(AstNode): - 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" diff --git a/gtkblueprinttool/ast_utils.py b/gtkblueprinttool/ast_utils.py index 2e1990b..3412ec6 100644 --- a/gtkblueprinttool/ast_utils.py +++ b/gtkblueprinttool/ast_utils.py @@ -197,3 +197,18 @@ def docs(*args, **kwargs): return Docs(func, *args, **kwargs) return decorator + + +class BaseAttribute(AstNode): + """ A helper class for attribute syntax of the form `name: literal_value;`""" + + tag_name: str = "" + + def emit_xml(self, xml: XmlEmitter): + xml.start_tag( + self.tag_name, + name=self.tokens["name"], + translatable="yes" if self.tokens["translatable"] else None, + ) + xml.put_text(str(self.tokens["value"])) + xml.end_tag() diff --git a/gtkblueprinttool/completions.py b/gtkblueprinttool/completions.py index 77d70a5..c2cefb4 100644 --- a/gtkblueprinttool/completions.py +++ b/gtkblueprinttool/completions.py @@ -20,6 +20,7 @@ import typing as T from . import ast +from .completions_utils import * from .lsp_utils import Completion, CompletionItemKind from .parser import SKIP_TOKENS from .tokenizer import TokenType, Token @@ -53,92 +54,31 @@ def complete(ast_node: ast.AstNode, tokens: T.List[Token], idx: int) -> T.Iterat token_idx -= 1 for completer in ast_node.completers: - yield from completer.completions(prev_tokens, ast_node) + yield from completer(prev_tokens, ast_node) -class Completer: - def __init__(self, func): - self.func = func - self.patterns: T.List = [] - self.ast_type: T.Type[ast.AstNode] = None - - def completions(self, prev_tokens: list[Token], ast_node: ast.AstNode) -> T.Iterator[Completion]: - any_match = len(self.patterns) == 0 - match_variables: T.List[str] = [] - - for pattern in self.patterns: - match_variables = [] - - if len(pattern) <= len(prev_tokens): - for i in range(0, len(pattern)): - type, value = pattern[i] - token = prev_tokens[i - len(pattern)] - if token.type != type or (value is not None and str(token) != value): - break - if value is None: - match_variables.append(str(token)) - else: - any_match = True - break - - if not any_match: - return - - if self.ast_type is not None: - while ast_node is not None and not isinstance(ast_node, self.ast_type): - ast_node = ast_node.parent - - yield from self.func(ast_node, match_variables) - - -def applies_to(*ast_types): - """ Decorator describing which AST nodes the completer should apply in. """ - def _decorator(func): - completer = Completer(func) - for c in ast_types: - c.completers.append(completer) - return completer - return _decorator - -def matches(patterns: T.List): - def _decorator(cls): - cls.patterns = patterns - return cls - return _decorator - -def ast_type(ast_type: T.Type[ast.AstNode]): - def _decorator(cls): - cls.ast_type = ast_type - return cls - return _decorator - - -new_statement_patterns = [ - [(TokenType.OPEN_BLOCK, None)], - [(TokenType.CLOSE_BLOCK, None)], - [(TokenType.STMT_END, None)], -] - - -@applies_to(ast.GtkDirective) +@completer([ast.GtkDirective]) def using_gtk(ast_node, match_variables): yield Completion("using Gtk 4.0;", CompletionItemKind.Keyword) -@matches(new_statement_patterns) -@ast_type(ast.UI) -@applies_to(ast.UI, ast.ObjectContent, ast.Template) +@completer( + applies_in=[ast.UI, ast.ObjectContent, ast.Template], + matches=new_statement_patterns +) def namespace(ast_node, match_variables): yield Completion("Gtk", CompletionItemKind.Module, text="Gtk.") - for ns in ast_node.imports: + for ns in ast_node.root.children[ast.Import]: yield Completion(ns.namespace, CompletionItemKind.Module, text=ns.namespace + ".") -@matches([ - [(TokenType.IDENT, None), (TokenType.OP, "."), (TokenType.IDENT, None)], - [(TokenType.IDENT, None), (TokenType.OP, ".")], -]) -@applies_to(ast.UI, ast.ObjectContent, ast.Template) +@completer( + applies_in=[ast.UI, ast.ObjectContent, ast.Template], + matches=[ + [(TokenType.IDENT, None), (TokenType.OP, "."), (TokenType.IDENT, None)], + [(TokenType.IDENT, None), (TokenType.OP, ".")], + ] +) def object_completer(ast_node, match_variables): ns = ast_node.root.gir.namespaces.get(match_variables[0]) if ns is not None: @@ -146,22 +86,20 @@ def object_completer(ast_node, match_variables): yield Completion(c.name, CompletionItemKind.Class, docs=c.doc) -@matches(new_statement_patterns) -@applies_to(ast.ObjectContent) +@completer( + applies_in=[ast.ObjectContent], + matches=new_statement_patterns, +) def property_completer(ast_node, match_variables): if ast_node.gir_class: for prop in ast_node.gir_class.properties: yield Completion(prop, CompletionItemKind.Property, snippet=f"{prop}: $0;") -@matches(new_statement_patterns) -@applies_to(ast.ObjectContent) -def style_completer(ast_node, match_variables): - yield Completion("style", CompletionItemKind.Keyword, snippet="style \"$0\";") - - -@matches(new_statement_patterns) -@applies_to(ast.ObjectContent) +@completer( + applies_in=[ast.ObjectContent], + matches=new_statement_patterns, +) def signal_completer(ast_node, match_variables): if ast_node.gir_class: for signal in ast_node.gir_class.signals: @@ -170,54 +108,12 @@ def signal_completer(ast_node, match_variables): yield Completion(signal, CompletionItemKind.Property, snippet=f"{signal} => ${{1:{name}_{signal.replace('-', '_')}}}()$0;") -@matches(new_statement_patterns) -@applies_to(ast.UI) +@completer( + applies_in=[ast.UI], + matches=new_statement_patterns +) def template_completer(ast_node, match_variables): yield Completion( "template", CompletionItemKind.Snippet, snippet="template ${1:ClassName} : ${2:ParentClass} {\n $0\n}" ) - - -@matches(new_statement_patterns) -@applies_to(ast.UI) -def menu_completer(ast_node, match_variables): - yield Completion( - "menu", CompletionItemKind.Snippet, - snippet="menu {\n $0\n}" - ) - - -@matches(new_statement_patterns) -@applies_to(ast.Menu) -def menu_content_completer(ast_node, match_variables): - yield Completion( - "submenu", CompletionItemKind.Snippet, - snippet="submenu {\n $0\n}" - ) - yield Completion( - "section", CompletionItemKind.Snippet, - snippet="section {\n $0\n}" - ) - yield Completion( - "item", CompletionItemKind.Snippet, - snippet="item {\n $0\n}" - ) - yield Completion( - "item (shorthand)", CompletionItemKind.Snippet, - snippet='item _("${1:Label}") "${2:action-name}" "${3:icon-name}";' - ) - - yield Completion( - "label", CompletionItemKind.Snippet, - snippet='label: $0;' - ) - yield Completion( - "action", CompletionItemKind.Snippet, - snippet='action: "$0";' - ) - yield Completion( - "icon", CompletionItemKind.Snippet, - snippet='icon: "$0";' - ) - diff --git a/gtkblueprinttool/completions_utils.py b/gtkblueprinttool/completions_utils.py new file mode 100644 index 0000000..33da321 --- /dev/null +++ b/gtkblueprinttool/completions_utils.py @@ -0,0 +1,73 @@ +# completions_utils.py +# +# Copyright 2021 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 + + +import typing as T + +from . import ast +from .tokenizer import Token, TokenType +from .lsp_utils import Completion + + +new_statement_patterns = [ + [(TokenType.OPEN_BLOCK, None)], + [(TokenType.CLOSE_BLOCK, None)], + [(TokenType.STMT_END, None)], +] + + +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=[]): + def decorator(func): + def inner(prev_tokens: T.List[Token], ast_node: ast.AstNode): + any_match = len(matches) == 0 + match_variables: T.List[str] = [] + + for pattern in matches: + match_variables = [] + + if len(pattern) <= len(prev_tokens): + for i in range(0, len(pattern)): + type, value = pattern[i] + token = prev_tokens[i - len(pattern)] + if token.type != type or (value is not None and str(token) != value): + break + if value is None: + match_variables.append(str(token)) + else: + any_match = True + break + + if not any_match: + return + + yield from func(ast_node, match_variables) + + for c in applies_in: + c.completers.append(inner) + return inner + + return decorator diff --git a/gtkblueprinttool/extensions/__init__.py b/gtkblueprinttool/extensions/__init__.py new file mode 100644 index 0000000..72b4c1b --- /dev/null +++ b/gtkblueprinttool/extensions/__init__.py @@ -0,0 +1,10 @@ +""" Contains all the syntax beyond basic objects, properties, signal, and +templates. """ + +from .gtk_menu import menu +from .gtk_styles import styles +from .gtk_layout import layout + +OBJECT_HOOKS = [menu] + +OBJECT_CONTENT_HOOKS = [styles, layout] diff --git a/gtkblueprinttool/extensions/gtk_layout.py b/gtkblueprinttool/extensions/gtk_layout.py new file mode 100644 index 0000000..72c8f2e --- /dev/null +++ b/gtkblueprinttool/extensions/gtk_layout.py @@ -0,0 +1,56 @@ +# gtk_layout.py +# +# Copyright 2021 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 ..ast_utils import AstNode, BaseAttribute +from ..completions_utils import * +from ..parse_tree import * +from ..parser_utils import * +from ..xml_emitter import XmlEmitter + + +class Layout(AstNode): + 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" + + +layout_prop = Group( + LayoutProperty, + Statement( + UseIdent("name"), + Op(":"), + value.expected("a value"), + ) +) + +layout = Group( + Layout, + Sequence( + Keyword("layout"), + OpenBlock().expected("`{`"), + Until(layout_prop, CloseBlock()), + ) +) diff --git a/gtkblueprinttool/extensions/gtk_menu.py b/gtkblueprinttool/extensions/gtk_menu.py new file mode 100644 index 0000000..205cfe5 --- /dev/null +++ b/gtkblueprinttool/extensions/gtk_menu.py @@ -0,0 +1,172 @@ +# gtk_menus.py +# +# Copyright 2021 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 ..ast_utils import AstNode, BaseAttribute +from ..completions_utils import * +from ..lsp_utils import Completion, CompletionItemKind +from ..parse_tree import * +from ..parser_utils import * +from ..xml_emitter import XmlEmitter + + +class Menu(AstNode): + def emit_xml(self, xml: XmlEmitter): + xml.start_tag(self.tokens["tag"], id=self.tokens["id"]) + for child in self.children: + child.emit_xml() + xml.end_tag() + + +class MenuAttribute(BaseAttribute): + tag_name = "attribute" + + +menu_contents = Sequence() + +menu_section = Group( + Menu, + Sequence( + Keyword("section"), + UseLiteral("tag", "section"), + Optional(UseIdent("id")), + menu_contents + ) +) + +menu_submenu = Group( + Menu, + Sequence( + Keyword("submenu"), + UseLiteral("tag", "submenu"), + Optional(UseIdent("id")), + menu_contents + ) +) + +menu_attribute = Group( + MenuAttribute, + Sequence( + UseIdent("name"), + Op(":"), + value.expected("a value"), + StmtEnd().expected("`;`"), + ) +) + +menu_item = Group( + Menu, + Sequence( + Keyword("item"), + UseLiteral("tag", "item"), + Optional(UseIdent("id")), + OpenBlock().expected("`{`"), + Until(menu_attribute, CloseBlock()), + ) +) + +menu_item_shorthand = Group( + Menu, + Sequence( + Keyword("item"), + UseLiteral("tag", "item"), + Group( + MenuAttribute, + Sequence(UseLiteral("name", "label"), value), + ), + Optional(Group( + MenuAttribute, + Sequence(UseLiteral("name", "action"), value), + )), + Optional(Group( + MenuAttribute, + Sequence(UseLiteral("name", "verb-icon-name"), value), + )), + StmtEnd().expected("`;`"), + ) +) + +menu_contents.children = [ + OpenBlock().expected("`{`"), + Until(AnyOf( + menu_section, + menu_submenu, + menu_item_shorthand, + menu_item, + menu_attribute, + ), CloseBlock()), +] + +menu = Group( + Menu, + Sequence( + Keyword("menu"), + UseLiteral("tag", "menu"), + Optional(UseIdent("id")), + menu_contents + ), +) + + +@completer( + applies_in=[ast.UI], + matches=new_statement_patterns, +) +def menu_completer(ast_node, match_variables): + yield Completion( + "menu", CompletionItemKind.Snippet, + snippet="menu {\n $0\n}" + ) + + +@completer( + applies_in=[Menu], + matches=new_statement_patterns, +) +def menu_content_completer(ast_node, match_variables): + yield Completion( + "submenu", CompletionItemKind.Snippet, + snippet="submenu {\n $0\n}" + ) + yield Completion( + "section", CompletionItemKind.Snippet, + snippet="section {\n $0\n}" + ) + yield Completion( + "item", CompletionItemKind.Snippet, + snippet="item {\n $0\n}" + ) + yield Completion( + "item (shorthand)", CompletionItemKind.Snippet, + snippet='item _("${1:Label}") "${2:action-name}" "${3:icon-name}";' + ) + + yield Completion( + "label", CompletionItemKind.Snippet, + snippet='label: $0;' + ) + yield Completion( + "action", CompletionItemKind.Snippet, + snippet='action: "$0";' + ) + yield Completion( + "icon", CompletionItemKind.Snippet, + snippet='icon: "$0";' + ) + diff --git a/gtkblueprinttool/extensions/gtk_styles.py b/gtkblueprinttool/extensions/gtk_styles.py new file mode 100644 index 0000000..4fa1385 --- /dev/null +++ b/gtkblueprinttool/extensions/gtk_styles.py @@ -0,0 +1,64 @@ +# gtk_styles.py +# +# Copyright 2021 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 .. import ast +from ..ast_utils import AstNode, BaseAttribute +from ..completions_utils import * +from ..lsp_utils import Completion, CompletionItemKind +from ..parse_tree import * +from ..parser_utils import * +from ..xml_emitter import XmlEmitter + + +class Styles(AstNode): + def emit_xml(self, xml: XmlEmitter): + xml.start_tag("style") + for child in self.children: + child.emit_xml(xml) + xml.end_tag() + + +class StyleClass(AstNode): + def emit_xml(self, xml): + xml.put_self_closing("class", name=self.tokens["name"]) + + +styles = Group( + Styles, + Statement( + Keyword("style"), + Delimited( + Group( + StyleClass, + UseQuoted("name") + ), + Comma(), + ), + ) +) + + +@completer( + applies_in=[ast.ObjectContent], + matches=new_statement_patterns, +) +def style_completer(ast_node, match_variables): + yield Completion("style", CompletionItemKind.Keyword, snippet="style \"$0\";") + diff --git a/gtkblueprinttool/parser.py b/gtkblueprinttool/parser.py index 8be20b6..4559fd6 100644 --- a/gtkblueprinttool/parser.py +++ b/gtkblueprinttool/parser.py @@ -21,7 +21,9 @@ from . import ast from .errors import MultipleErrors from .parse_tree import * +from .parser_utils import * from .tokenizer import TokenType +from .extensions import OBJECT_HOOKS, OBJECT_CONTENT_HOOKS def parse(tokens) -> T.Tuple[ast.UI, T.Optional[MultipleErrors]]: @@ -45,41 +47,6 @@ def parse(tokens) -> T.Tuple[ast.UI, T.Optional[MultipleErrors]]: ) ) - class_name = AnyOf( - Sequence( - UseIdent("namespace"), - Op("."), - UseIdent("class_name"), - ), - Sequence( - Op("."), - UseIdent("class_name"), - UseLiteral("ignore_gir", True), - ), - UseIdent("class_name"), - ) - - value = AnyOf( - Sequence( - Keyword("_"), - OpenParen(), - UseQuoted("value").expected("a quoted string"), - CloseParen().expected("`)`"), - UseLiteral("translatable", True), - ), - Sequence(Keyword("True"), UseLiteral("value", True)), - Sequence(Keyword("true"), UseLiteral("value", True)), - Sequence(Keyword("Yes"), UseLiteral("value", True)), - Sequence(Keyword("yes"), UseLiteral("value", True)), - Sequence(Keyword("False"), UseLiteral("value", False)), - Sequence(Keyword("false"), UseLiteral("value", False)), - Sequence(Keyword("No"), UseLiteral("value", False)), - Sequence(Keyword("no"), UseLiteral("value", False)), - UseIdent("value"), - UseNumber("value"), - UseQuoted("value"), - ) - object = Group( ast.Object, None @@ -91,6 +58,7 @@ def parse(tokens) -> T.Tuple[ast.UI, T.Optional[MultipleErrors]]: UseIdent("name"), Op(":"), AnyOf( + *OBJECT_HOOKS, object, value, ).expected("a value"), @@ -145,130 +113,12 @@ def parse(tokens) -> T.Tuple[ast.UI, T.Optional[MultipleErrors]]: ) ) - style = Group( - ast.Style, - Statement( - Keyword("style"), - Delimited( - Group( - ast.StyleClass, - UseQuoted("name") - ), - Comma(), - ), - ) - ) - - menu_contents = Sequence() - - menu_section = Group( - ast.Menu, - Sequence( - Keyword("section"), - UseLiteral("tag", "section"), - Optional(UseIdent("id")), - menu_contents - ) - ) - - menu_submenu = Group( - ast.Menu, - Sequence( - Keyword("submenu"), - UseLiteral("tag", "submenu"), - Optional(UseIdent("id")), - menu_contents - ) - ) - - menu_attribute = Group( - ast.MenuAttribute, - Sequence( - UseIdent("name"), - Op(":"), - value.expected("a value"), - StmtEnd().expected("`;`"), - ) - ) - - menu_item = Group( - ast.Menu, - Sequence( - Keyword("item"), - UseLiteral("tag", "item"), - Optional(UseIdent("id")), - OpenBlock().expected("`{`"), - Until(menu_attribute, CloseBlock()), - ) - ) - - menu_item_shorthand = Group( - ast.Menu, - Sequence( - Keyword("item"), - UseLiteral("tag", "item"), - Group( - ast.MenuAttribute, - Sequence(UseLiteral("name", "label"), value), - ), - Optional(Group( - ast.MenuAttribute, - Sequence(UseLiteral("name", "action"), value), - )), - Optional(Group( - ast.MenuAttribute, - Sequence(UseLiteral("name", "verb-icon-name"), value), - )), - StmtEnd().expected("`;`"), - ) - ) - - menu_contents.children = [ - OpenBlock().expected("`{`"), - Until(AnyOf( - menu_section, - menu_submenu, - menu_item_shorthand, - menu_item, - menu_attribute, - ), CloseBlock()), - ] - - menu = Group( - ast.Menu, - Sequence( - Keyword("menu"), - UseLiteral("tag", "menu"), - Optional(UseIdent("id")), - menu_contents - ), - ) - - layout_prop = Group( - ast.LayoutProperty, - Statement( - UseIdent("name"), - Op(":"), - value.expected("a value"), - ) - ) - - layout = Group( - ast.Layout, - Sequence( - Keyword("layout"), - OpenBlock().expected("`{`"), - Until(layout_prop, CloseBlock()), - ) - ) - object_content = Group( ast.ObjectContent, Sequence( OpenBlock(), Until(AnyOf( - style, - layout, + *OBJECT_CONTENT_HOOKS, binding, property, signal, @@ -301,8 +151,8 @@ def parse(tokens) -> T.Tuple[ast.UI, T.Optional[MultipleErrors]]: gtk_directive, ZeroOrMore(import_statement), Until(AnyOf( + *OBJECT_HOOKS, template, - menu, object, ), Eof()), ) diff --git a/gtkblueprinttool/parser_utils.py b/gtkblueprinttool/parser_utils.py new file mode 100644 index 0000000..eec9818 --- /dev/null +++ b/gtkblueprinttool/parser_utils.py @@ -0,0 +1,57 @@ +# parser_utils.py +# +# Copyright 2021 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 .parse_tree import * + + +class_name = AnyOf( + Sequence( + UseIdent("namespace"), + Op("."), + UseIdent("class_name"), + ), + Sequence( + Op("."), + UseIdent("class_name"), + UseLiteral("ignore_gir", True), + ), + UseIdent("class_name"), +) + +value = AnyOf( + Sequence( + Keyword("_"), + OpenParen(), + UseQuoted("value").expected("a quoted string"), + CloseParen().expected("`)`"), + UseLiteral("translatable", True), + ), + Sequence(Keyword("True"), UseLiteral("value", True)), + Sequence(Keyword("true"), UseLiteral("value", True)), + Sequence(Keyword("Yes"), UseLiteral("value", True)), + Sequence(Keyword("yes"), UseLiteral("value", True)), + Sequence(Keyword("False"), UseLiteral("value", False)), + Sequence(Keyword("false"), UseLiteral("value", False)), + Sequence(Keyword("No"), UseLiteral("value", False)), + Sequence(Keyword("no"), UseLiteral("value", False)), + UseIdent("value"), + UseNumber("value"), + UseQuoted("value"), +)