mirror of
https://gitlab.gnome.org/jwestman/blueprint-compiler.git
synced 2025-05-04 15:59:08 -04:00
language: Support boxed types and GType
- Add support for type checking boxed types - Remove support for converting string and number literals - Add the `typeof()` operator for GType literals
This commit is contained in:
parent
ee2b9b2950
commit
c0c40b1577
12 changed files with 140 additions and 26 deletions
|
@ -18,7 +18,7 @@ build:
|
||||||
- ninja -C _build docs/en
|
- ninja -C _build docs/en
|
||||||
- git clone https://gitlab.gnome.org/jwestman/blueprint-regression-tests.git
|
- git clone https://gitlab.gnome.org/jwestman/blueprint-regression-tests.git
|
||||||
- cd blueprint-regression-tests
|
- cd blueprint-regression-tests
|
||||||
- git checkout c61f425d5df359261f1369722e791f947bc3ede1
|
- git checkout d14b95b6c1fc0cddd4b0ad21d224b05edee2d01f
|
||||||
- ./test.sh
|
- ./test.sh
|
||||||
- cd ..
|
- cd ..
|
||||||
coverage: '/TOTAL.*\s([.\d]+)%/'
|
coverage: '/TOTAL.*\s([.\d]+)%/'
|
||||||
|
|
7
NEWS.md
7
NEWS.md
|
@ -1,3 +1,10 @@
|
||||||
|
# v0.6.0 (unreleased)
|
||||||
|
|
||||||
|
## Breaking Changes
|
||||||
|
- Quoted and numeric literals are no longer interchangeable (e.g. `"800"` is
|
||||||
|
no longer an accepted value for an integer type).
|
||||||
|
- Boxed types are now type checked.
|
||||||
|
|
||||||
# v0.4.0
|
# v0.4.0
|
||||||
|
|
||||||
## Added
|
## Added
|
||||||
|
|
|
@ -129,6 +129,11 @@ class StringType(BasicType):
|
||||||
def assignable_to(self, other) -> bool:
|
def assignable_to(self, other) -> bool:
|
||||||
return isinstance(other, StringType)
|
return isinstance(other, StringType)
|
||||||
|
|
||||||
|
class TypeType(BasicType):
|
||||||
|
name = "GType"
|
||||||
|
def assignable_to(self, other) -> bool:
|
||||||
|
return isinstance(other, TypeType)
|
||||||
|
|
||||||
_BASIC_TYPES = {
|
_BASIC_TYPES = {
|
||||||
"gboolean": BoolType,
|
"gboolean": BoolType,
|
||||||
"int": IntType,
|
"int": IntType,
|
||||||
|
@ -141,6 +146,8 @@ _BASIC_TYPES = {
|
||||||
"float": FloatType,
|
"float": FloatType,
|
||||||
"double": FloatType,
|
"double": FloatType,
|
||||||
"utf8": StringType,
|
"utf8": StringType,
|
||||||
|
"gtype": TypeType,
|
||||||
|
"type": TypeType,
|
||||||
}
|
}
|
||||||
|
|
||||||
class GirNode:
|
class GirNode:
|
||||||
|
@ -213,7 +220,7 @@ class GirNode:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type(self):
|
def type(self):
|
||||||
return self.get_containing(Namespace).lookup_type(self.type_name)
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
class Property(GirNode):
|
class Property(GirNode):
|
||||||
|
@ -464,6 +471,18 @@ class Enumeration(GirNode, GirType):
|
||||||
return type == self
|
return type == self
|
||||||
|
|
||||||
|
|
||||||
|
class Boxed(GirNode, GirType):
|
||||||
|
def __init__(self, ns, tl: typelib.Typelib):
|
||||||
|
super().__init__(ns, tl)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def signature(self):
|
||||||
|
return f"boxed {self.full_name}"
|
||||||
|
|
||||||
|
def assignable_to(self, type):
|
||||||
|
return type == self
|
||||||
|
|
||||||
|
|
||||||
class Bitfield(Enumeration):
|
class Bitfield(Enumeration):
|
||||||
def __init__(self, ns, tl: typelib.Typelib):
|
def __init__(self, ns, tl: typelib.Typelib):
|
||||||
super().__init__(ns, tl)
|
super().__init__(ns, tl)
|
||||||
|
@ -491,6 +510,8 @@ class Namespace(GirNode):
|
||||||
self.entries[entry_name] = Class(self, entry_blob)
|
self.entries[entry_name] = Class(self, entry_blob)
|
||||||
elif entry_type == typelib.BLOB_TYPE_INTERFACE:
|
elif entry_type == typelib.BLOB_TYPE_INTERFACE:
|
||||||
self.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:
|
||||||
|
self.entries[entry_name] = Boxed(self, entry_blob)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def xml(self):
|
def xml(self):
|
||||||
|
@ -595,6 +616,8 @@ class Repository(GirNode):
|
||||||
return UIntType()
|
return UIntType()
|
||||||
elif type_id == typelib.TYPE_UTF8:
|
elif type_id == typelib.TYPE_UTF8:
|
||||||
return StringType()
|
return StringType()
|
||||||
|
elif type_id == typelib.TYPE_GTYPE:
|
||||||
|
return TypeType()
|
||||||
else:
|
else:
|
||||||
raise CompilerBugError("Unknown type ID", type_id)
|
raise CompilerBugError("Unknown type ID", type_id)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -18,7 +18,7 @@ from .gtkbuilder_child import Child
|
||||||
from .gtkbuilder_template import Template
|
from .gtkbuilder_template import Template
|
||||||
from .imports import GtkDirective, Import
|
from .imports import GtkDirective, Import
|
||||||
from .ui import UI
|
from .ui import UI
|
||||||
from .values import IdentValue, TranslatedStringValue, FlagsValue, LiteralValue
|
from .values import TypeValue, IdentValue, TranslatedStringValue, FlagsValue, QuotedValue, NumberValue
|
||||||
|
|
||||||
from .common import *
|
from .common import *
|
||||||
|
|
||||||
|
@ -43,8 +43,10 @@ OBJECT_CONTENT_HOOKS.children = [
|
||||||
]
|
]
|
||||||
|
|
||||||
VALUE_HOOKS.children = [
|
VALUE_HOOKS.children = [
|
||||||
|
TypeValue,
|
||||||
TranslatedStringValue,
|
TranslatedStringValue,
|
||||||
FlagsValue,
|
FlagsValue,
|
||||||
IdentValue,
|
IdentValue,
|
||||||
LiteralValue,
|
QuotedValue,
|
||||||
|
NumberValue,
|
||||||
]
|
]
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
|
|
||||||
from .common import *
|
from .common import *
|
||||||
|
from .types import TypeName
|
||||||
|
|
||||||
|
|
||||||
class Value(AstNode):
|
class Value(AstNode):
|
||||||
|
@ -55,11 +56,68 @@ class TranslatedStringValue(Value):
|
||||||
xml.put_text(self.tokens["value"])
|
xml.put_text(self.tokens["value"])
|
||||||
|
|
||||||
|
|
||||||
class LiteralValue(Value):
|
class TypeValue(Value):
|
||||||
grammar = AnyOf(
|
grammar = [
|
||||||
UseNumber("value"),
|
"typeof",
|
||||||
UseQuoted("value"),
|
"(",
|
||||||
)
|
to_parse_node(TypeName).expected("type name"),
|
||||||
|
Match(")").expected(),
|
||||||
|
]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type_name(self):
|
||||||
|
return self.children[TypeName][0]
|
||||||
|
|
||||||
|
def emit_xml(self, xml: XmlEmitter):
|
||||||
|
xml.put_text(self.type_name.glib_type_name)
|
||||||
|
|
||||||
|
@validate()
|
||||||
|
def validate_for_type(self):
|
||||||
|
type = self.parent.value_type
|
||||||
|
if type is not None and not isinstance(type, gir.TypeType):
|
||||||
|
raise CompileError(f"Cannot convert GType to {type.full_name}")
|
||||||
|
|
||||||
|
|
||||||
|
class QuotedValue(Value):
|
||||||
|
grammar = UseQuoted("value")
|
||||||
|
|
||||||
|
def emit_xml(self, xml: XmlEmitter):
|
||||||
|
xml.put_text(self.tokens["value"])
|
||||||
|
|
||||||
|
@validate()
|
||||||
|
def validate_for_type(self):
|
||||||
|
type = self.parent.value_type
|
||||||
|
if isinstance(type, gir.IntType) or isinstance(type, gir.UIntType) or isinstance(type, gir.FloatType):
|
||||||
|
raise CompileError(f"Cannot convert string to number")
|
||||||
|
|
||||||
|
elif isinstance(type, gir.StringType):
|
||||||
|
pass
|
||||||
|
|
||||||
|
elif isinstance(type, gir.Class) or isinstance(type, gir.Interface) or isinstance(type, gir.Boxed):
|
||||||
|
parseable_types = [
|
||||||
|
"Gdk.Paintable",
|
||||||
|
"Gdk.Texture",
|
||||||
|
"Gdk.Pixbuf",
|
||||||
|
"GLib.File",
|
||||||
|
"Gtk.ShortcutTrigger",
|
||||||
|
"Gtk.ShortcutAction",
|
||||||
|
"Gdk.RGBA",
|
||||||
|
"Gdk.ContentFormats",
|
||||||
|
"Gsk.Transform",
|
||||||
|
"GLib.Variant",
|
||||||
|
]
|
||||||
|
if type.full_name not in parseable_types:
|
||||||
|
hints = []
|
||||||
|
if isinstance(type, gir.TypeType):
|
||||||
|
hints.append(f"use the typeof operator: 'typeof({self.tokens('value')})'")
|
||||||
|
raise CompileError(f"Cannot convert string to {type.full_name}", hints=hints)
|
||||||
|
|
||||||
|
elif type is not None:
|
||||||
|
raise CompileError(f"Cannot convert string to {type.full_name}")
|
||||||
|
|
||||||
|
|
||||||
|
class NumberValue(Value):
|
||||||
|
grammar = UseNumber("value")
|
||||||
|
|
||||||
def emit_xml(self, xml: XmlEmitter):
|
def emit_xml(self, xml: XmlEmitter):
|
||||||
xml.put_text(self.tokens["value"])
|
xml.put_text(self.tokens["value"])
|
||||||
|
@ -87,23 +145,8 @@ class LiteralValue(Value):
|
||||||
except:
|
except:
|
||||||
raise CompileError(f"Cannot convert {self.group.tokens['value']} to float")
|
raise CompileError(f"Cannot convert {self.group.tokens['value']} to float")
|
||||||
|
|
||||||
elif isinstance(type, gir.StringType):
|
|
||||||
pass
|
|
||||||
|
|
||||||
elif isinstance(type, gir.Class) or isinstance(type, gir.Interface):
|
|
||||||
parseable_types = [
|
|
||||||
"Gdk.Paintable",
|
|
||||||
"Gdk.Texture",
|
|
||||||
"Gdk.Pixbuf",
|
|
||||||
"GLib.File",
|
|
||||||
"Gtk.ShortcutTrigger",
|
|
||||||
"Gtk.ShortcutAction",
|
|
||||||
]
|
|
||||||
if type.full_name not in parseable_types:
|
|
||||||
raise CompileError(f"Cannot convert {self.group.tokens['value']} to {type.full_name}")
|
|
||||||
|
|
||||||
elif type is not None:
|
elif type is not None:
|
||||||
raise CompileError(f"Cannot convert {self.group.tokens['value']} to {type.full_name}")
|
raise CompileError(f"Cannot convert number to {type.full_name}")
|
||||||
|
|
||||||
|
|
||||||
class Flag(AstNode):
|
class Flag(AstNode):
|
||||||
|
|
|
@ -26,6 +26,8 @@ import mmap, os
|
||||||
from .errors import CompilerBugError
|
from .errors import CompilerBugError
|
||||||
|
|
||||||
|
|
||||||
|
BLOB_TYPE_STRUCT = 3
|
||||||
|
BLOB_TYPE_BOXED = 4
|
||||||
BLOB_TYPE_ENUM = 5
|
BLOB_TYPE_ENUM = 5
|
||||||
BLOB_TYPE_FLAGS = 6
|
BLOB_TYPE_FLAGS = 6
|
||||||
BLOB_TYPE_OBJECT = 7
|
BLOB_TYPE_OBJECT = 7
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
5,18,1,Cannot convert 1 to Gtk.Orientation
|
5,18,1,Cannot convert number to Gtk.Orientation
|
||||||
|
|
|
@ -1,5 +1,14 @@
|
||||||
using Gtk 4.0;
|
using Gtk 4.0;
|
||||||
|
using Gio 2.0;
|
||||||
|
|
||||||
Gtk.Shortcut {
|
Gtk.Shortcut {
|
||||||
trigger: "Escape";
|
trigger: "Escape";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Picture {
|
||||||
|
paintable: "/path/to/paintable";
|
||||||
|
}
|
||||||
|
|
||||||
|
ColorButton {
|
||||||
|
rgba: "rgb(0, 0, 0)";
|
||||||
|
}
|
|
@ -4,4 +4,10 @@
|
||||||
<object class="GtkShortcut">
|
<object class="GtkShortcut">
|
||||||
<property name="trigger">Escape</property>
|
<property name="trigger">Escape</property>
|
||||||
</object>
|
</object>
|
||||||
|
<object class="GtkPicture">
|
||||||
|
<property name="paintable">/path/to/paintable</property>
|
||||||
|
</object>
|
||||||
|
<object class="GtkColorButton">
|
||||||
|
<property name="rgba">rgb(0, 0, 0)</property>
|
||||||
|
</object>
|
||||||
</interface>
|
</interface>
|
||||||
|
|
11
tests/samples/typeof.blp
Normal file
11
tests/samples/typeof.blp
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
using GObject 2.0;
|
||||||
|
using Gio 2.0;
|
||||||
|
|
||||||
|
Gio.ListStore {
|
||||||
|
item-type: typeof(GObject.Object);
|
||||||
|
}
|
||||||
|
|
||||||
|
Gio.ListStore {
|
||||||
|
item-type: typeof(.MyObject);
|
||||||
|
}
|
10
tests/samples/typeof.ui
Normal file
10
tests/samples/typeof.ui
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk" version="4.0"/>
|
||||||
|
<object class="GListStore">
|
||||||
|
<property name="item-type">GObject</property>
|
||||||
|
</object>
|
||||||
|
<object class="GListStore">
|
||||||
|
<property name="item-type">MyObject</property>
|
||||||
|
</object>
|
||||||
|
</interface>
|
|
@ -155,6 +155,7 @@ class TestSamples(unittest.TestCase):
|
||||||
self.assert_sample("template")
|
self.assert_sample("template")
|
||||||
self.assert_sample("template_no_parent")
|
self.assert_sample("template_no_parent")
|
||||||
self.assert_sample("translated")
|
self.assert_sample("translated")
|
||||||
|
self.assert_sample("typeof")
|
||||||
self.assert_sample("uint")
|
self.assert_sample("uint")
|
||||||
self.assert_sample("unchecked_class")
|
self.assert_sample("unchecked_class")
|
||||||
self.assert_sample("using")
|
self.assert_sample("using")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue