Procházet zdrojové kódy

Made IndentingWriter smarter

Joeri Exelmans před 5 roky
rodič
revize
c20f0c6633

+ 0 - 1
.gitignore

@@ -18,4 +18,3 @@ src/MANIFEST
 __pycache__/
 __pycache__/
 .mypy_cache/
 .mypy_cache/
 *.smcat
 *.smcat
-codegen/

+ 47 - 51
src/sccd/action_lang/codegen/rust.py

@@ -80,13 +80,13 @@ class ScopeHelper():
 
 
     def write_rvalue(self, name, offset, writer):
     def write_rvalue(self, name, offset, writer):
         if offset < 0:
         if offset < 0:
-            writer.wno("parent%d." % self.current().scope.nested_levels(offset))
-            writer.wno(ident_local(name))
+            writer.write("parent%d." % self.current().scope.nested_levels(offset))
+            writer.write(ident_local(name))
         elif offset < self.current().committed:
         elif offset < self.current().committed:
-            writer.wno("scope.")
-            writer.wno(ident_local(name))
+            writer.write("scope.")
+            writer.write(ident_local(name))
         else:
         else:
-            writer.wno(ident_local(name))
+            writer.write(ident_local(name))
 
 
 
 
 class ActionLangRustGenerator(Visitor):
 class ActionLangRustGenerator(Visitor):
@@ -96,7 +96,7 @@ class ActionLangRustGenerator(Visitor):
         self.functions_to_write = [] # Function and Rust identifier
         self.functions_to_write = [] # Function and Rust identifier
 
 
     def default(self, what):
     def default(self, what):
-        # self.w.wno("<%s>" % what)
+        # self.w.write("<%s>" % what)
         raise UnsupportedFeature(what)
         raise UnsupportedFeature(what)
 
 
     def debug_print_stack(self):
     def debug_print_stack(self):
@@ -117,7 +117,7 @@ class ActionLangRustGenerator(Visitor):
             ctr += 1
             ctr += 1
             scope = scope.parent
             scope = scope.parent
 
 
-        self.w.wno(", ".join(reversed(args)))
+        self.w.write(", ".join(reversed(args)))
 
 
     def write_parent_call_params(self, scope, skip: int = 0):
     def write_parent_call_params(self, scope, skip: int = 0):
         args = []
         args = []
@@ -130,7 +130,7 @@ class ActionLangRustGenerator(Visitor):
             ctr += 1
             ctr += 1
             scope = scope.parent
             scope = scope.parent
 
 
-        self.w.wno(", ".join(reversed(args)))
+        self.w.write(", ".join(reversed(args)))
 
 
     # This is not a visit method because Scopes may be encountered whenever there's a function call, but they are written as structs and constructor functions, which can only be written at the module level.
     # This is not a visit method because Scopes may be encountered whenever there's a function call, but they are written as structs and constructor functions, which can only be written at the module level.
     # When compiling Rust code, the Visitable.accept method must be called on the root of the AST, to write code wherever desired (e.g. in a main-function) followed by 'write_scope' at the module level.
     # When compiling Rust code, the Visitable.accept method must be called on the root of the AST, to write code wherever desired (e.g. in a main-function) followed by 'write_scope' at the module level.
@@ -144,15 +144,15 @@ class ActionLangRustGenerator(Visitor):
 
 
             for p in function.params_decl:
             for p in function.params_decl:
                 p.accept(self)
                 p.accept(self)
-                self.w.wno(", ")
+                self.w.write(", ")
 
 
             self.write_parent_params(scope)
             self.write_parent_params(scope)
 
 
-            self.w.wno(") -> ")
+            self.w.write(") -> ")
 
 
             self.write_return_type(function)
             self.write_return_type(function)
 
 
-            self.w.wnoln(" {")
+            self.w.writeln(" {")
             self.w.indent()
             self.w.indent()
             self.w.writeln("let scope = action_lang::Empty{};")
             self.w.writeln("let scope = action_lang::Empty{};")
 
 
@@ -178,7 +178,7 @@ class ActionLangRustGenerator(Visitor):
                 for v in scope.variables[commit.start: commit.end]:
                 for v in scope.variables[commit.start: commit.end]:
                     self.w.write("  %s: " % ident_local(v.name))
                     self.w.write("  %s: " % ident_local(v.name))
                     v.type.accept(self)
                     v.type.accept(self)
-                    self.w.wnoln(",")
+                    self.w.writeln(",")
                 self.w.writeln("}")
                 self.w.writeln("}")
                 self.w.dedent()
                 self.w.dedent()
                 self.w.writeln("}")
                 self.w.writeln("}")
@@ -189,18 +189,17 @@ class ActionLangRustGenerator(Visitor):
             s.accept(self)
             s.accept(self)
 
 
     def visit_Assignment(self, stmt):
     def visit_Assignment(self, stmt):
-        #self.w.write('') # indent
         stmt.lhs.accept(self)
         stmt.lhs.accept(self)
-        self.w.wno(" = ")
+        self.w.write(" = ")
         stmt.rhs.accept(self)
         stmt.rhs.accept(self)
-        self.w.wnoln(";")
+        self.w.writeln(";")
         if DEBUG:
         if DEBUG:
             self.w.writeln("eprintln!(\"%s\");" % termcolor.colored(stmt.render(),'blue'))
             self.w.writeln("eprintln!(\"%s\");" % termcolor.colored(stmt.render(),'blue'))
 
 
     def visit_IfStatement(self, stmt):
     def visit_IfStatement(self, stmt):
         self.w.write("if ")
         self.w.write("if ")
         stmt.cond.accept(self)
         stmt.cond.accept(self)
-        self.w.wnoln(" {")
+        self.w.writeln(" {")
         self.w.indent()
         self.w.indent()
         stmt.if_body.accept(self)
         stmt.if_body.accept(self)
         self.w.dedent()
         self.w.dedent()
@@ -221,88 +220,86 @@ class ActionLangRustGenerator(Visitor):
 
 
         self.w.write("return ")
         self.w.write("return ")
         if returns_closure_obj:
         if returns_closure_obj:
-            self.w.wno("(scope, ")
+            self.w.write("(scope, ")
         stmt.expr.accept(self)
         stmt.expr.accept(self)
         if returns_closure_obj:
         if returns_closure_obj:
-            self.w.wno(")")
-        self.w.wnoln(";")
+            self.w.write(")")
+        self.w.writeln(";")
 
 
     def visit_ExpressionStatement(self, stmt):
     def visit_ExpressionStatement(self, stmt):
         self.w.write('')
         self.w.write('')
         stmt.expr.accept(self)
         stmt.expr.accept(self)
-        self.w.wnoln(";")
+        self.w.writeln(";")
 
 
     def visit_BoolLiteral(self, expr):
     def visit_BoolLiteral(self, expr):
-        self.w.wno("true" if expr.b else "false")
+        self.w.write("true" if expr.b else "false")
 
 
     def visit_IntLiteral(self, expr):
     def visit_IntLiteral(self, expr):
-        self.w.wno(str(expr.i))
+        self.w.write(str(expr.i))
 
 
     def visit_StringLiteral(self, expr):
     def visit_StringLiteral(self, expr):
-        self.w.wno('"'+expr.string+'"')
+        self.w.write('"'+expr.string+'"')
 
 
     def visit_Array(self, expr):
     def visit_Array(self, expr):
-        self.w.wno("[")
+        self.w.write("[")
         for el in expr.elements:
         for el in expr.elements:
             el.accept(self)
             el.accept(self)
-            self.w.wno(", ")
-        self.w.wno("]")
+            self.w.write(", ")
+        self.w.write("]")
 
 
     def visit_BinaryExpression(self, expr):
     def visit_BinaryExpression(self, expr):
         if expr.operator == "**":
         if expr.operator == "**":
             raise UnsupportedFeature("exponent operator")
             raise UnsupportedFeature("exponent operator")
         else:
         else:
             # always put parentheses
             # always put parentheses
-            self.w.wno("(")
+            self.w.write("(")
             expr.lhs.accept(self)
             expr.lhs.accept(self)
-            self.w.wno(" %s " % expr.operator
+            self.w.write(" %s " % expr.operator
                 .replace('and', '&&')
                 .replace('and', '&&')
                 .replace('or', '||')
                 .replace('or', '||')
                 .replace('//', '/')) # integer division
                 .replace('//', '/')) # integer division
             expr.rhs.accept(self)
             expr.rhs.accept(self)
-            self.w.wno(")")
+            self.w.write(")")
 
 
     def visit_UnaryExpression(self, expr):
     def visit_UnaryExpression(self, expr):
-        self.w.wno(expr.operator
+        self.w.write(expr.operator
             .replace('not', '! '))
             .replace('not', '! '))
         expr.expr.accept(self)
         expr.expr.accept(self)
 
 
     def visit_Group(self, expr):
     def visit_Group(self, expr):
-        # self.w.wno(" (")
         expr.subexpr.accept(self)
         expr.subexpr.accept(self)
-        # self.w.wno(") ")
 
 
     def visit_ParamDecl(self, expr):
     def visit_ParamDecl(self, expr):
-        self.w.wno(ident_local(expr.name))
-        self.w.wno(": ")
+        self.w.write(ident_local(expr.name))
+        self.w.write(": ")
         expr.formal_type.accept(self)
         expr.formal_type.accept(self)
 
 
     def visit_FunctionDeclaration(self, expr):
     def visit_FunctionDeclaration(self, expr):
         function_identifier = "f%d_%s" % (len(self.functions_to_write), expr.scope.name)
         function_identifier = "f%d_%s" % (len(self.functions_to_write), expr.scope.name)
         self.functions_to_write.append( (expr, function_identifier) )
         self.functions_to_write.append( (expr, function_identifier) )
-        self.w.wno(function_identifier)
+        self.w.write(function_identifier)
 
 
     def visit_FunctionCall(self, expr):
     def visit_FunctionCall(self, expr):
         if isinstance(expr.function.get_type(), SCCDClosureObject):
         if isinstance(expr.function.get_type(), SCCDClosureObject):
-            self.w.wno("call_closure!(")
+            self.w.write("call_closure!(")
             expr.function.accept(self)
             expr.function.accept(self)
-            self.w.wno(", ")
+            self.w.write(", ")
         else:
         else:
-            self.w.wno("(")
+            self.w.write("(")
             expr.function.accept(self)
             expr.function.accept(self)
-            self.w.wno(")(")
+            self.w.write(")(")
 
 
         # Call parameters
         # Call parameters
         for p in expr.params:
         for p in expr.params:
             p.accept(self)
             p.accept(self)
-            self.w.wno(", ")
+            self.w.write(", ")
 
 
         if isinstance(expr.function.get_type(), SCCDClosureObject):
         if isinstance(expr.function.get_type(), SCCDClosureObject):
             self.write_parent_call_params(expr.function_being_called.scope, skip=1)
             self.write_parent_call_params(expr.function_being_called.scope, skip=1)
         else:
         else:
             self.write_parent_call_params(expr.function_being_called.scope)
             self.write_parent_call_params(expr.function_being_called.scope)
 
 
-        self.w.wno(")")
+        self.w.write(")")
 
 
 
 
     def visit_Identifier(self, lval):
     def visit_Identifier(self, lval):
@@ -313,41 +310,40 @@ class ActionLangRustGenerator(Visitor):
                 # a child scope exists at the current offset (typically because we encountered a function declaration) - so we must commit our scope
                 # a child scope exists at the current offset (typically because we encountered a function declaration) - so we must commit our scope
                 self.scope.commit(lval.offset, self.w)
                 self.scope.commit(lval.offset, self.w)
 
 
-            # self.w.wno("/* is_lvalue */")
             self.w.write('') # indent
             self.w.write('') # indent
 
 
         if lval.is_init:
         if lval.is_init:
-            self.w.wno("let mut ")
-            self.w.wno(ident_local(lval.name))
+            self.w.write("let mut ")
+            self.w.write(ident_local(lval.name))
         else:
         else:
             self.scope.write_rvalue(lval.name, lval.offset, self.w)
             self.scope.write_rvalue(lval.name, lval.offset, self.w)
 
 
     def visit_SCCDClosureObject(self, type):
     def visit_SCCDClosureObject(self, type):
-        self.w.wno("(%s, " % self.scope.type(type.scope, type.scope.size()))
+        self.w.write("(%s, " % self.scope.type(type.scope, type.scope.size()))
         type.function_type.accept(self)
         type.function_type.accept(self)
-        self.w.wno(")")
+        self.w.write(")")
 
 
     def write_return_type(self, function: FunctionDeclaration):
     def write_return_type(self, function: FunctionDeclaration):
         if function.return_type is None:
         if function.return_type is None:
-            self.w.wno("()")
+            self.w.write("()")
         else:
         else:
             function.return_type.accept(self)
             function.return_type.accept(self)
 
 
     def visit_SCCDFunction(self, type):
     def visit_SCCDFunction(self, type):
         scope = type.function.scope
         scope = type.function.scope
-        self.w.wno("fn(")
+        self.w.write("fn(")
 
 
         for p in type.param_types:
         for p in type.param_types:
             p.accept(self)
             p.accept(self)
-            self.w.wno(", ")
+            self.w.write(", ")
 
 
         self.write_parent_params(scope, with_identifiers=False)
         self.write_parent_params(scope, with_identifiers=False)
 
 
-        self.w.wno(") -> ")
+        self.w.write(") -> ")
         self.write_return_type(type.function)
         self.write_return_type(type.function)
 
 
     def visit__SCCDSimpleType(self, type):
     def visit__SCCDSimpleType(self, type):
-        self.w.wno(type.name
+        self.w.write(type.name
             .replace("int", "i32")
             .replace("int", "i32")
             .replace("float", "f64")
             .replace("float", "f64")
         )
         )

+ 13 - 4
src/sccd/statechart/codegen/code_generation.txt

@@ -102,7 +102,7 @@ Roadmap
 
 
   (DONE) Milestone 6: Action language
   (DONE) Milestone 6: Action language
 
 
-  Milestone 7: INSTATE-function -> INSTATE-macro
+  (DONE) Milestone 7: INSTATE-function -> INSTATE-macro
 
 
     - INVARIANT: Action lang must never depend on statechart lang!
     - INVARIANT: Action lang must never depend on statechart lang!
       - Evaluation of action lang expression can only read/write to the expression's scope (and parent scopes, if expression occurs in a function)
       - Evaluation of action lang expression can only read/write to the expression's scope (and parent scopes, if expression occurs in a function)
@@ -130,7 +130,6 @@ Roadmap
     - Syntactic output events
     - Syntactic output events
     - Event parameters
     - Event parameters
     - Memory Protocol semantics
     - Memory Protocol semantics
-    - 'Queuing' internal event semantics
     - Concurrency semantics
     - Concurrency semantics
 
 
 
 
@@ -149,9 +148,16 @@ Performance over time
 
 
 for test semantics/big_step_maximality/test_ortho_takemany.xml
 for test semantics/big_step_maximality/test_ortho_takemany.xml
 
 
+commit ae8a94e186ee972c0d3e0b229b77901352137c64 - Dec 23, 2020 - implement additional event lifeline semantics, pass input event by reference instead of by value
+
+binary size, no opt:                            800592 bytes
+binary size, opt-level=3:                       422200 bytes
+instruction count (perf stat) (opt-level=3):    570.824 instructions:u
+
 
 
 commit 39fc866428c595c7ed909569d998981d1d350059 - Dec 22, 2020 - various fixes, support INSTATE
 commit 39fc866428c595c7ed909569d998981d1d350059 - Dec 22, 2020 - various fixes, support INSTATE
 
 
+binary size, no opt:                            800544 bytes
 binary size, opt-level=3:                       422200 bytes
 binary size, opt-level=3:                       422200 bytes
 instruction count (perf stat) (opt-level=3):    570.776 instructions:u
 instruction count (perf stat) (opt-level=3):    570.776 instructions:u
 
 
@@ -181,5 +187,8 @@ instruction count (perf stat) (opt-level=3):    580.701 instructions:u
 
 
 Performance insights
 Performance insights
 
 
-  - instruction count improved a little over time (but could be due to non-relevant changes such as debug output)
-  - binary size slightly worse when optimizing for size (opt-level=z). this means that inlining is actually reducing the binaries' size, as we would expect (lots of dead code can be eliminated)
+  - non-optimized binary size increased as features were added, optimized binary size stayed pretty much the same. this assures that Rust is able to inline and optimize away the many unused parameters of enter and exit functions.
+
+  - the same conclusion can be drawn from binary size being slightly worse when setting a lower inline threshold (opt-level=z). this means that inlining is actually reducing the binaries' size, as we would expect (lots of unused parameters can be eliminated)
+
+  - passing input event by reference instead of by value did not change the optimized binary size. probably evidence of Rust inlining or passing by value anyway.

+ 17 - 23
src/sccd/statechart/codegen/rust.py

@@ -116,7 +116,7 @@ class StatechartRustGenerator(ActionLangRustGenerator):
         source = instate.ref.source
         source = instate.ref.source
         target = instate.ref.target
         target = instate.ref.target
 
 
-        self.w.wnoln("{ // macro expansion for @in(\"%s\")" % target.full_name)
+        self.w.writeln("{ // macro expansion for @in(\"%s\")" % target.full_name)
         self.w.indent()
         self.w.indent()
 
 
         # Non-exhaustive set of current states, given that 'source' is a current state
         # Non-exhaustive set of current states, given that 'source' is a current state
@@ -125,12 +125,12 @@ class StatechartRustGenerator(ActionLangRustGenerator):
         # Deconstruct state configuration tuple
         # Deconstruct state configuration tuple
         self.w.write("let (")
         self.w.write("let (")
         for parent in parents:
         for parent in parents:
-            self.w.wno("ref ")
-            self.w.wno(ident_var(parent))
-            self.w.wno(", ")
-        self.w.wno(") = ")
+            self.w.write("ref ")
+            self.w.write(ident_var(parent))
+            self.w.write(", ")
+        self.w.write(") = ")
         self.scope.write_rvalue("@conf", instate.offset, self.w)
         self.scope.write_rvalue("@conf", instate.offset, self.w)
-        self.w.wnoln(";")
+        self.w.writeln(";")
 
 
         for parent in parents + [source]:
         for parent in parents + [source]:
             if is_ancestor(parent=target, child=parent):
             if is_ancestor(parent=target, child=parent):
@@ -168,7 +168,7 @@ class StatechartRustGenerator(ActionLangRustGenerator):
         self.w.write("}")
         self.w.write("}")
 
 
     def visit_SCCDStateConfiguration(self, type):
     def visit_SCCDStateConfiguration(self, type):
-        self.w.wno(self.get_parallel_states_tuple_type(type.state))
+        self.w.write(self.get_parallel_states_tuple_type(type.state))
 
 
     def visit_RaiseOutputEvent(self, a):
     def visit_RaiseOutputEvent(self, a):
         # TODO: evaluate event parameters
         # TODO: evaluate event parameters
@@ -190,8 +190,8 @@ class StatechartRustGenerator(ActionLangRustGenerator):
 
 
         self.w.write()
         self.w.write()
         a.block.accept(self) # block is a function
         a.block.accept(self) # block is a function
-        self.w.wno("(%s, scope);" % self.get_parallel_states_tuple(self.state_stack[-1])) # call it!
-        self.w.wnoln()
+        self.w.write("(%s, scope);" % self.get_parallel_states_tuple(self.state_stack[-1])) # call it!
+        self.w.writeln()
 
 
     def visit_State(self, state):
     def visit_State(self, state):
         self.state_stack.append(state)
         self.state_stack.append(state)
@@ -356,13 +356,6 @@ class StatechartRustGenerator(ActionLangRustGenerator):
             self.w.writeln("  // TODO: event parameters")
             self.w.writeln("  // TODO: event parameters")
             self.w.writeln("}")
             self.w.writeln("}")
 
 
-        # if internal_queue:
-        #     self.w.writeln("enum InternalEvent {")
-        #     for event_name in internal_events:
-        #         self.w.writeln("  %s," % ident_event_type(event_name))
-        #     self.w.writeln("}")
-        # else:
-
         # Implement internal events as a set
         # Implement internal events as a set
         self.w.writeln("// Set of (raised) internal events")
         self.w.writeln("// Set of (raised) internal events")
         self.w.writeln("#[derive(Default)]")
         self.w.writeln("#[derive(Default)]")
@@ -373,6 +366,7 @@ class StatechartRustGenerator(ActionLangRustGenerator):
         self.w.writeln("}")
         self.w.writeln("}")
 
 
         if self.internal_queue:
         if self.internal_queue:
+            # Internal events are treated like input events
             self.w.writeln("type InternalLifeline = ();")
             self.w.writeln("type InternalLifeline = ();")
         else:
         else:
             if internal_same_round:
             if internal_same_round:
@@ -394,7 +388,7 @@ class StatechartRustGenerator(ActionLangRustGenerator):
                 bm |= arenas.get(d, 0)
                 bm |= arenas.get(d, 0)
             arenas[arena] = bm
             arenas[arena] = bm
         self.w.writeln("// Transition arenas (bitmap type)")
         self.w.writeln("// Transition arenas (bitmap type)")
-        # if syntactic_maximality:
+
         for size, typ in [(8, 'u8'), (16, 'u16'), (32, 'u32'), (64, 'u64'), (128, 'u128')]:
         for size, typ in [(8, 'u8'), (16, 'u16'), (32, 'u32'), (64, 'u64'), (128, 'u128')]:
             if len(arenas) + 1 <= size:
             if len(arenas) + 1 <= size:
                 self.w.writeln("type Arenas = %s;" % typ)
                 self.w.writeln("type Arenas = %s;" % typ)
@@ -411,8 +405,8 @@ class StatechartRustGenerator(ActionLangRustGenerator):
         self.w.writeln("impl<TimerId: Default> Default for Statechart<TimerId> {")
         self.w.writeln("impl<TimerId: Default> Default for Statechart<TimerId> {")
         self.w.writeln("  fn default() -> Self {")
         self.w.writeln("  fn default() -> Self {")
         self.w.writeln("    // Initialize data model")
         self.w.writeln("    // Initialize data model")
-        self.w.indent(); self.w.indent();
         self.w.writeln("    let scope = action_lang::Empty{};")
         self.w.writeln("    let scope = action_lang::Empty{};")
+        self.w.indent(); self.w.indent();
         if sc.datamodel is not None:
         if sc.datamodel is not None:
             sc.datamodel.accept(self)
             sc.datamodel.accept(self)
         datamodel_type = self.scope.commit(sc.scope.size(), self.w)
         datamodel_type = self.scope.commit(sc.scope.size(), self.w)
@@ -599,13 +593,13 @@ class StatechartRustGenerator(ActionLangRustGenerator):
                             raise UnsupportedFeature("Event parameters")
                             raise UnsupportedFeature("Event parameters")
                         self.w.write("if ")
                         self.w.write("if ")
                         t.guard.accept(self) # guard is a function...
                         t.guard.accept(self) # guard is a function...
-                        self.w.wno("(") # call it!
-                        self.w.wno(self.get_parallel_states_tuple(t.source))
-                        self.w.wno(", ")
+                        self.w.write("(") # call it!
+                        self.w.write(self.get_parallel_states_tuple(t.source))
+                        self.w.write(", ")
                         # TODO: write event parameters here
                         # TODO: write event parameters here
                         self.write_parent_call_params(t.guard.scope)
                         self.write_parent_call_params(t.guard.scope)
-                        self.w.wno(")")
-                        self.w.wnoln(" {")
+                        self.w.write(")")
+                        self.w.writeln(" {")
                         self.w.indent()
                         self.w.indent()
 
 
                     # 1. Execute transition's actions
                     # 1. Execute transition's actions

+ 4 - 0
src/sccd/test/codegen/write_crate.py

@@ -74,3 +74,7 @@ def write_crate(src, target):
         w.writeln("name = \"statechartgen\"")
         w.writeln("name = \"statechartgen\"")
         w.writeln("path = \"statechartgen.rs\"")
         w.writeln("path = \"statechartgen.rs\"")
         w.writeln()
         w.writeln()
+
+    with open(target+"/.gitignore", 'w') as file:
+        w = IndentingWriter(out=file)
+        w.writeln("/target/")

+ 8 - 11
src/sccd/util/indenting_writer.py

@@ -5,6 +5,7 @@ class IndentingWriter:
         self.spaces = spaces
         self.spaces = spaces
         self.out = out
         self.out = out
         self.state = initial
         self.state = initial
+        self.newline = True
 
 
     def indent(self):
     def indent(self):
         self.state += self.spaces
         self.state += self.spaces
@@ -13,17 +14,13 @@ class IndentingWriter:
         self.state -= self.spaces
         self.state -= self.spaces
 
 
     def writeln(self, s=""):
     def writeln(self, s=""):
-        if s == "":
-            self.out.write('\n')
-        else:
-            self.out.write(' '*self.state + s + '\n')
-
-    # "write no indent"
-    def wno(self, s):
-        self.out.write(s)
-
-    def wnoln(self, s=""):
+        if self.newline:
+            self.out.write(' '*self.state)
         self.out.write(s + '\n')
         self.out.write(s + '\n')
+        self.newline = True
 
 
     def write(self, s=""):
     def write(self, s=""):
-        self.out.write(' '*self.state + s)
+        if self.newline:
+            self.out.write(' '*self.state)
+        self.out.write(s)
+        self.newline = False