Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[mycpp] Only generate field names from __init__ (or Reset)
This fixes a bug where the translation of process::Process and
process::Job had duplicate this->state fields.

- Add test exposes it in examples/classes.py
- Change OSH source code to be explicit about fields in a few places.

Note: Reset is a bit of hack for WordParser

This translates and compiles now.  Let's see how it does on the tests.
  • Loading branch information
Andy C committed Nov 29, 2021
1 parent 9c7819f commit 038ce9a
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 14 deletions.
2 changes: 2 additions & 0 deletions core/state.py
Expand Up @@ -1014,6 +1014,8 @@ def __init__(self, dollar0, argv, arena, debug_stack):
# CALL_SOURCE, and BASH_LINENO.
self.debug_stack = debug_stack

self.pwd = None # type: Optional[str]

self.current_spid = runtime.NO_SPID

self.line_num = value.Str('')
Expand Down
42 changes: 33 additions & 9 deletions mycpp/cppgen_pass.py
Expand Up @@ -283,6 +283,7 @@ def __init__(self, types: Dict[Expression, Type], const_lookup, f,
# This is all in the 'decl' phase.
self.member_vars = {} # type: Dict[str, Type]
self.current_class_name = None # for prototypes
self.current_method_name = None

self.imported_names = set() # For module::Foo() vs. self.foo

Expand Down Expand Up @@ -1418,11 +1419,19 @@ def visit_assignment_stmt(self, o: 'mypy.nodes.AssignmentStmt') -> T:
self.accept(o.rvalue)
self.write(';\n')

# Collect statements that look like self.foo = 1
if isinstance(lval.expr, NameExpr) and lval.expr.name == 'self':
#log(' lval.name %s', lval.name)
lval_type = self.types[lval]
self.member_vars[lval.name] = lval_type
if self.current_method_name in ('__init__', 'Reset'):
# Collect statements that look like self.foo = 1
# Only do this in __init__ so that a derived class mutating a field
# from the base calss doesn't cause duplicate C++ fields. (C++
# allows two fields of the same name!)
#
# HACK for WordParser: also include Reset(). We could change them
# all up front but I kinda like this.

if isinstance(lval.expr, NameExpr) and lval.expr.name == 'self':
#log(' lval.name %s', lval.name)
lval_type = self.types[lval]
self.member_vars[lval.name] = lval_type

elif isinstance(lval, IndexExpr): # a[x] = 1
# d->set(x, 1) for both List and Dict
Expand Down Expand Up @@ -2111,23 +2120,37 @@ def visit_class_def(self, o: 'mypy.nodes.ClassDef') -> T:

# Constructor is named after class
if isinstance(stmt, FuncDef):
if stmt.name() == '__init__':
method_name = stmt.name()
if method_name == '__init__':
self.decl_write_ind('%s(', o.name)
self._WriteFuncParams(stmt.type.arg_types, stmt.arguments)
self.decl_write(');\n')

# Must visit these for member vars!
# Visit for member vars
self.current_method_name = method_name
self.accept(stmt.body)
self.current_method_name = None
continue

if stmt.name() == '__enter__':
if method_name == '__enter__':
continue

if stmt.name() == '__exit__':
if method_name == '__exit__':
# Turn it into a destructor with NO ARGS
self.decl_write_ind('~%s();\n', o.name)
continue

if method_name == '__repr__':
# skip during declaration, just like visit_func_def does during definition
continue

# Any other function: Visit for member vars
self.current_method_name = method_name
self.accept(stmt)
self.current_method_name = None
continue

# Do we need this? I think everything under a class is a method?
self.accept(stmt)

self.current_class_name = None
Expand All @@ -2149,6 +2172,7 @@ def visit_class_def(self, o: 'mypy.nodes.ClassDef') -> T:

self.current_class_name = o.name

#
# Now we're visiting for definitions (not declarations).
#
block = o.defs
Expand Down
14 changes: 14 additions & 0 deletions mycpp/examples/classes.py
Expand Up @@ -39,6 +39,17 @@ def __init__(self, f):
# Note: translated into an initializer list.
ColorOutput.__init__(self, f)
print('TextOutput constructor')
self.i = 0 # field only in derived class

def MutateField(self):
# type: () -> None
self.num_chars = 42
self.i = 43

def PrintFields(self):
# type: () -> None
print("num_chars = %d" % self.num_chars) # field from base
print("i = %d" % self.i) # field from derived


class Base(object):
Expand Down Expand Up @@ -90,6 +101,9 @@ def run_tests():
out.write('bar\n')
log('Wrote %d bytes', out.num_chars)

out.MutateField()
out.PrintFields()

#b = Base()
d = Derived()
#log(b.method())
Expand Down
12 changes: 7 additions & 5 deletions osh/builtin_printf.py
Expand Up @@ -7,7 +7,7 @@
import time as time_ # avoid name conflict

from _devbuild.gen import arg_types
from _devbuild.gen.id_kind_asdl import Id, Kind
from _devbuild.gen.id_kind_asdl import Id, Kind, Id_t, Kind_t
from _devbuild.gen.runtime_asdl import (
cmd_value__Argv, value_e, value__Str, value
)
Expand Down Expand Up @@ -61,12 +61,14 @@ def __init__(self, lexer):
# type: (Lexer) -> None
self.lexer = lexer

# uninitialized values
self.cur_token = None # type: Token
self.token_type = Id.Undefined_Tok # type: Id_t
self.token_kind = Kind.Undefined # type: Kind_t

def _Next(self, lex_mode):
# type: (lex_mode_t) -> None
"""Set the next lex state, but don't actually read a token.
We need this for proper interactive parsing.
"""
"""Advance a token."""
self.cur_token = self.lexer.Read(lex_mode)
self.token_type = self.cur_token.id
self.token_kind = consts.GetKind(self.token_type)
Expand Down

0 comments on commit 038ce9a

Please sign in to comment.