Skip to content

Commit

Permalink
Add initial enamldef const storage
Browse files Browse the repository at this point in the history
  • Loading branch information
frmdstryr committed Mar 23, 2024
1 parent bf0ea6f commit a537128
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 5 deletions.
10 changes: 10 additions & 0 deletions enaml/core/block_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ def visit_StorageExpr(self, node):
# Grab the index of the parent node for later use.
self.aux_index_map[node] = self.parent_index()

def visit_ConstExpr(self, node):
# Grab the index of the parent node for later use.
self.aux_index_map[node] = self.parent_index()

def visit_FuncDef(self, node):
# Grab the index of the parent node for later use.
self.aux_index_map[node] = self.parent_index()
Expand Down Expand Up @@ -180,6 +184,12 @@ def visit_StorageExpr(self, node):
if node.expr is not None:
cmn.gen_operator_binding(cg, node.expr, index, node.name)

def visit_ConstExpr(self, node):
# Generate the code for the const expression.
cg = self.code_generator
index = self.parent_index()
cmn.gen_const_expr(cg, node, index, self.local_names)

def visit_FuncDef(self, node):
# Generate the code for the function declaration.
cg = self.code_generator
Expand Down
55 changes: 55 additions & 0 deletions enaml/core/compiler_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -862,6 +862,61 @@ def gen_storage_expr(cg, node, index, local_names):
cg.pop_top()


def gen_const_expr(cg, node, index, local_names):
""" Generate the code for a const expression.
The caller should ensure that NODE_LIST is present in the fast
locals of the code object.
Parameters
----------
cg : CodeGenerator
The code generator with which to write the code.
node : ConstExpr
The enaml ast node of interest.
index : int
The index of the target node in the node list.
local_names : set
The set of fast local names available to the code object.
"""
with cg.try_squash_raise():
cg.set_lineno(node.lineno)
if PY311:
cg.push_null()
load_helper(cg, 'add_storage')
load_node(cg, index)
cg.load_const(node.name)
if node.typename:
load_typename(cg, node.typename, local_names)
else:
cg.load_const(None)
cg.load_const("const")
cg.call_function(4)
cg.pop_top()

# Generate the operator
code = sanitize_operator_code(cg.filename, 'eval', node.expr.ast)
with cg.try_squash_raise():
cg.set_lineno(node.lineno)
if PY311:
cg.push_null()
load_helper(cg, 'run_operator')
load_node(cg, index)
# For operators not in a template instance the scope_node and the node
# are one and the same hence the dup_top.
cg.dup_top()
cg.load_const(node.name)
cg.load_const("=")
cg.load_const(code)
cg.load_fast(F_GLOBALS)
cg.call_function(6)
cg.pop_top()


def _insert_decl_function(cg, funcdef):
""" Create and place a declarative function on the TOS.
Expand Down
6 changes: 4 additions & 2 deletions enaml/core/compiler_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#------------------------------------------------------------------------------
from functools import update_wrapper

from atom.api import Event, Instance, Member, add_member
from atom.api import Event, Instance, Member, ReadOnly, add_member
from atom.datastructures.api import sortedmap

from .alias import Alias
Expand Down Expand Up @@ -125,7 +125,7 @@ def add_storage(node, name, store_type, kind):
store_type : type or None
The type of values to allow on the attribute.
kind : 'attr' or 'event'
kind : 'attr', 'event', or 'const'
The kind of storage to add to the class.
"""
Expand All @@ -151,6 +151,8 @@ def add_storage(node, name, store_type, kind):
new = d_(Event(store_type), writable=False, final=False)
elif kind == 'attr':
new = d_(Instance(store_type), final=False)
elif kind == 'const':
new = d_(ReadOnly(store_type), final=False)
else:
raise RuntimeError("invalid kind '%s'" % kind)

Expand Down
1 change: 1 addition & 0 deletions enaml/core/parser/enaml.gram
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ child_def_simple_item:
| ex_binding
| alias_expr
| storage_expr
| const_expr
| 'pass' NEWLINE { ast.Pass(LOCATIONS) }

# --- Binding --------------------------------------------------------------------------
Expand Down
7 changes: 4 additions & 3 deletions enaml/core/parser/enaml_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
Load = ast.Load()
Store = ast.Store()
Del = ast.Del()


# Keywords and soft keywords are listed at the end of the parser definition.
class EnamlParser(Parser):
@memoize
Expand Down Expand Up @@ -416,7 +414,7 @@ def child_def_item(self) -> Optional[Any]:

@memoize
def child_def_simple_item(self) -> Optional[Any]:
# child_def_simple_item: binding | ex_binding | alias_expr | storage_expr | 'pass' NEWLINE
# child_def_simple_item: binding | ex_binding | alias_expr | storage_expr | const_expr | 'pass' NEWLINE
mark = self._mark()
tok = self._tokenizer.peek()
start_lineno, start_col_offset = tok.start
Expand All @@ -432,6 +430,9 @@ def child_def_simple_item(self) -> Optional[Any]:
if storage_expr := self.storage_expr():
return storage_expr
self._reset(mark)
if const_expr := self.const_expr():
return const_expr
self._reset(mark)
if (self.expect("pass")) and (self.expect("NEWLINE")):
tok = self._tokenizer.get_last_non_whitespace_token()
end_lineno, end_col_offset = tok.end
Expand Down
128 changes: 128 additions & 0 deletions tests/core/test_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,131 @@ def test_attr_type_1(enaml_qtbot, enaml_sleep):
# Should raise a type error
with pytest.raises(TypeError):
tester.d = datetime.time(7, 0)


#------------------------------------------------------------------------------
# Const Syntax
#------------------------------------------------------------------------------
def test_const_syntax_1():
source = dedent("""\
from enaml.widgets.api import *
enamldef Main(Window):
const editing = False
""")
compile_source(source, 'Main')


def test_const_syntax_2():
source = dedent("""\
from enaml.widgets.api import *
enamldef Main(Window):
const message: str = "Hello world"
""")
compile_source(source, 'Main')


def test_const_syntax_3():
# ChildDef const
source = dedent("""\
from enaml.widgets.api import *
enamldef Main(Window):
Label:
const show_extra = False
""")
compile_source(source, 'Main')


def test_bad_const_syntax_1():
source = dedent("""\
from enaml.widgets.api import *
enamldef Main(Window):
const enabled
""")
with pytest.raises(SyntaxError):
compile_source(source, 'Main')


def test_bad_const_syntax_2():
# Must have an expr
source = dedent("""\
from enaml.widgets.api import *
enamldef Main(Window):
const enabled: bool
""")
with pytest.raises(SyntaxError):
compile_source(source, 'Main')


def test_bad_const_syntax_3():
# Invalid op
source = dedent("""\
from enaml.widgets.api import *
enamldef Main(Window):
const message: str << str(self)
""")
with pytest.raises(SyntaxError):
compile_source(source, 'Main')


def test_const_expr_1(enaml_qtbot, enaml_sleep):
import datetime
source = dedent("""\
import datetime
from enaml.widgets.api import *
enamldef Main(Window):
const d: datetime.date = datetime.date.today()
Container:
Label:
text << str(d)
""")
tester = compile_source(source, 'Main')()
tester.show()
wait_for_window_displayed(enaml_qtbot, tester)

# const cannot be changed
with pytest.raises(TypeError):
tester.d = (datetime.datetime.now() - datetime.timedelta(days=1)).date()


def test_const_expr_2(enaml_qtbot, enaml_sleep):
import datetime
source = dedent("""\
import datetime
from enaml.widgets.api import *
enamldef Main(Window):
const d: datetime.date = "1970-1-1"
""")
tester = compile_source(source, 'Main')()
tester.show()
wait_for_window_displayed(enaml_qtbot, tester)

# const type expr invalid
with pytest.raises(TypeError):
tester.d


def test_const_expr_3(enaml_qtbot, enaml_sleep):
from datetime import datetime
source = dedent("""\
import datetime
from enaml.widgets.api import *
enamldef Main(Window):
const d: datetime.date = None
""")
today = datetime.now().date()
tester = compile_source(source, 'Main')(d=today)
tester.show()
wait_for_window_displayed(enaml_qtbot, tester)

# const initial value can be passed in
assert tester.d == today

0 comments on commit a537128

Please sign in to comment.