Add support for lex and yacc.
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Mon, 7 Jan 2002 05:06:56 +0000 (05:06 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Mon, 7 Jan 2002 05:06:56 +0000 (05:06 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@191 fdb21ef1-2011-0410-befe-b5e4ea1792b1

doc/man/scons.1
src/CHANGES.txt
src/engine/SCons/Builder.py
src/engine/SCons/BuilderTests.py
src/engine/SCons/Defaults.py
test/LEX.py [new file with mode: 0644]
test/LEXFLAGS.py [new file with mode: 0644]
test/YACC.py [new file with mode: 0644]
test/YACCFLAGS.py [new file with mode: 0644]

index ad8a8349a3e1c3d5448980e3ad9a7c4b3b2b24b6..7ac1bf423cf63cf4defc7e2957ccbcc4f19587d6 100644 (file)
@@ -519,6 +519,7 @@ env.Program('bar', ['bar.c', 'foo.c'])
 
 .B scons
 provides the following builders:
+
 .IP Object
 Builds an object file from one or more C/C++ source files. Source files
 must have one of the following extensions: .c, .C, .cc, .cpp, .cxx, .c++, .C++. 
@@ -552,6 +553,18 @@ env.Library(target = 'bar', source = 'bar.c foo.o')
 .PP
 .fi
 
+.IP CFile
+Builds a C source file given a lex (.l) or yacc (.y) input file.
+The hard-coded suffix .c is
+automatically added to the target
+if it is not already present. Example:
+.IP
+.nf
+env.CFile(target = 'foo.c', source = 'foo.l') # builds foo.c
+env.CFile(target = 'bar', source = 'bar.y')   # builds bar.c
+.PP
+.fi
+
 .LP
 
 C/C++ source files are automatically scanned for dependencies by 
@@ -663,8 +676,29 @@ General options passed to the static library command.
 .IP ARCOM
 The command line used to generate a static library from object files.
 
+.IP LEX
+The lexical analyzer generator.
+
+.IP LEXFLAGS
+General options passed to the lexical analyzer generator.
+
+.IP LEXCOM
+The command line used to call the lexical analyzer generator
+to generate a source file.
+
+.IP YACC
+The parser generator.
+
+.IP YACCFLAGS
+General options passed to the parser generator.
+
+.IP YACCCOM
+The command line used to call the parser generator
+to generate a source file.
+
 .IP BUILDERS
-A list of the available builders. [Object, Program, Library] by default.
+A list of the available builders.
+[CFile, Object, Program, Library] by default.
 
 .IP SCANNERS
 A list of the available implicit dependency scanners. [CScan] by default.
index e1e57ec5b16e91a8efdfb9d1edba41446161266a..b1ae4c4d2dfb5f166637da8b9db2bcb476b4f530 100644 (file)
@@ -30,6 +30,9 @@ RELEASE 0.03 -
   - Refactor BuilderBase.__call__() to separate Node creation/lookup
     from initialization of the Node's builder information.
 
+  - Add a CFile Builder object that supports turning lex (.l) and
+    yacc (.y) files into .c files.
+
   From Anthony Roach:
 
   - Add a "duplicate" keyword argument to BuildDir() that can be set
index 6651b1387180a453a382514749dc391d34f26c01..f47e3a7f5a60f1ad596c6ef888733bd318597c8c 100644 (file)
@@ -209,15 +209,15 @@ class BuilderBase:
             if scanner:
                 s.scanner_set(scanner.instance(env))
 
-    def __call__(self, env, target = None, source = None):
-       tlist, slist = self._create_nodes(env, target, source)
-
-       self._init_nodes(env, tlist, slist)
-
        if len(tlist) == 1:
            tlist = tlist[0]
        return tlist
 
+    def __call__(self, env, target = None, source = None):
+        tlist, slist = self._create_nodes(env, target, source)
+
+        return self._init_nodes(env, tlist, slist)
+
     def execute(self, **kw):
        """Execute a builder's action to create an output object.
        """
@@ -229,6 +229,11 @@ class BuilderBase:
         """
         return apply(self.action.get_contents, (), kw)
 
+    def src_suffixes(self):
+        if self.src_suffix != '':
+            return [self.src_suffix]
+        return []
+
 class MultiStepBuilder(BuilderBase):
     """This is a builder subclass that can build targets in
     multiple steps.  The src_builder parameter to the constructor
@@ -270,6 +275,9 @@ class MultiStepBuilder(BuilderBase):
         return BuilderBase.__call__(self, env, target=target,
                                     source=final_sources)
 
+    def src_suffixes(self):
+        return BuilderBase.src_suffixes(self) + self.src_builder.src_suffixes()
+
 class CompositeBuilder(BuilderBase):
     """This is a convenient Builder subclass that can build different
     files based on their suffixes.  For each target, this builder
@@ -300,30 +308,23 @@ class CompositeBuilder(BuilderBase):
              self.builder_dict[suff] = apply(Builder, (), kw)
 
     def __call__(self, env, target = None, source = None):
-        ret = BuilderBase.__call__(self, env, target=target, source=source)
+        tlist, slist = BuilderBase._create_nodes(self, env,
+                                                 target=target, source=source)
 
-        builder_dict = {}
-        suff_dict = {}
+        # XXX These [bs]dict tables are invariant for each unique
+        # CompositeBuilder + Environment pair, so we should cache them.
+        bdict = {}
+        sdict = {}
         for suffix, bld in self.builder_dict.items():
-            builder_dict[env.subst(bld.src_suffix)] = bld
-            suff_dict[suffix] = suffix
-            b = bld
-            while hasattr(b, 'src_builder'):
-                # Walk the chain of src_builders and add the
-                # src_suffix strings to the maps so that we know
-                # all of those suffixes are legal, too.
-                b = b.src_builder
-                s = env.subst(b.src_suffix)
-                builder_dict[s] = bld
-                suff_dict[s] = suffix
-
-        if type(ret) is types.ListType:
-            tlist = ret
-        else:
-            tlist = [ ret ]
+            bdict[env.subst(bld.src_suffix)] = bld
+            sdict[suffix] = suffix
+            for s in bld.src_suffixes():
+                bdict[s] = bld
+                sdict[s] = suffix
+
         for tnode in tlist:
-            suflist = map(lambda x, s=suff_dict: s[os.path.splitext(x.path)[1]],
-                          tnode.sources)
+            suflist = map(lambda x, s=sdict: s[os.path.splitext(x.path)[1]],
+                          slist)
             last_suffix=''
             for suffix in suflist:
                 if last_suffix and last_suffix != suffix:
@@ -331,10 +332,19 @@ class CompositeBuilder(BuilderBase):
                 last_suffix = suffix
             if last_suffix:
                 try:
-                    tnode.builder_set(builder_dict[last_suffix])
+                    bdict[last_suffix].__call__(env, target = tnode,
+                                                source = slist)
                 except KeyError:
                     raise UserError, "The builder for %s can not build files with suffix: %s" % (tnode.path, suffix)
-        return ret
+
+        if len(tlist) == 1:
+            tlist = tlist[0]
+        return tlist
+
+    def src_suffixes(self):
+        return reduce(lambda x, y: x + y,
+                      map(lambda b: b.src_suffixes(),
+                          self.builder_dict.values()))
 
 print_actions = 1;
 execute_actions = 1;
index b735a0931abde57c35b3947e4ecae73264f131f9..8293076518b77f97578c3bc5cda042fa399f694d 100644 (file)
@@ -389,18 +389,26 @@ class BuilderTestCase(unittest.TestCase):
        beginning if it isn't already present.
        """
        builder = SCons.Builder.Builder(src_suffix = '.c')
-       assert builder.src_suffix == '.c'
-       builder = SCons.Builder.Builder(src_suffix = 'c')
-       assert builder.src_suffix == '.c'
+        assert builder.src_suffixes() == ['.c'], builder.src_suffixes()
+
        tgt = builder(env, target = 'tgt2', source = 'src2')
        assert tgt.sources[0].path == 'src2.c', \
                "Source has unexpected name: %s" % tgt.sources[0].path
+
         tgt = builder(env, target = 'tgt3', source = 'src3a src3b')
         assert tgt.sources[0].path == 'src3a.c', \
                 "Sources[0] has unexpected name: %s" % tgt.sources[0].path
         assert tgt.sources[1].path == 'src3b.c', \
                 "Sources[1] has unexpected name: %s" % tgt.sources[1].path
 
+        b2 = SCons.Builder.Builder(src_suffix = '.2', src_builder = builder)
+        assert b2.src_suffixes() == ['.2', '.c'], b2.src_suffixes()
+
+        b3 = SCons.Builder.Builder(action = {'.3a' : '', '.3b' : ''})
+        s = b3.src_suffixes()
+        s.sort()
+        assert s == ['.3a', '.3b'], s
+
     def test_suffix(self):
        """Test Builder creation with a specified target suffix
 
index bbc9e034130768345a8a7830e23f2e859836d97c..2e11c3747210312a99dbdaed76e6428d9fb0ebba 100644 (file)
@@ -43,6 +43,12 @@ import SCons.Scanner.Prog
 import string
 import SCons.Errors
 
+CFile = SCons.Builder.Builder(name = 'CFile',
+                              action = { '.l'    : '$LEXCOM',
+                                         '.y'    : '$YACCCOM',
+                                       },
+                              suffix = '.c')
+
 Object = SCons.Builder.Builder(name = 'Object',
                                action = { '.c'   : '$CCCOM',
                                           '.C'   : '$CXXCOM',
@@ -53,7 +59,8 @@ Object = SCons.Builder.Builder(name = 'Object',
                                           '.C++' : '$CXXCOM',
                                         },
                                prefix = '$OBJPREFIX',
-                               suffix = '$OBJSUFFIX')
+                               suffix = '$OBJSUFFIX',
+                               src_builder = CFile)
 
 Program = SCons.Builder.Builder(name = 'Program',
                                 action = '$LINKCOM',
@@ -202,7 +209,13 @@ def make_win32_env_from_paths(include, lib, path):
         'AR'         : 'lib',
         'ARFLAGS'    : '/nologo',
         'ARCOM'      : '$AR $ARFLAGS /OUT:$TARGET $SOURCES',
-        'BUILDERS'   : [Object, Program, Library],
+        'LEX'        : 'lex',
+        'LEXFLAGS'   : '',
+        'LEXCOM'     : '$LEX $LEXFLAGS -o$TARGET $SOURCES',
+        'YACC'       : 'yacc',
+        'YACCFLAGS'  : '',
+        'YACCCOM'    : '$YACC $YACCFLAGS -o $TARGET $SOURCES',
+        'BUILDERS'   : [CFile, Object, Program, Library],
         'SCANNERS'   : [CScan],
         'OBJPREFIX'  : '',
         'OBJSUFFIX'  : '.obj',
@@ -250,7 +263,13 @@ if os.name == 'posix':
         'AR'         : 'ar',
         'ARFLAGS'    : 'r',
         'ARCOM'      : '$AR $ARFLAGS $TARGET $SOURCES\nranlib $TARGET',
-        'BUILDERS'   : [Object, Program, Library],
+        'LEX'        : 'lex',
+        'LEXFLAGS'   : '',
+        'LEXCOM'     : '$LEX $LEXFLAGS -o$TARGET $SOURCES',
+        'YACC'       : 'yacc',
+        'YACCFLAGS'  : '',
+        'YACCCOM'    : '$YACC $YACCFLAGS -o $TARGET $SOURCES',
+        'BUILDERS'   : [CFile, Object, Program, Library],
         'SCANNERS'   : [CScan],
         'OBJPREFIX'  : '',
         'OBJSUFFIX'  : '.o',
@@ -275,12 +294,12 @@ elif os.name == 'nt':
     except:
         try:
             # We failed to detect DevStudio, so fall back to the
-           # DevStudio environment variables:
+            # DevStudio environment variables:
             ConstructionEnvironment = make_win32_env_from_paths(
                 os.environ["INCLUDE"], os.environ["LIB"], os.environ["PATH"])
         except KeyError:
             # The DevStudio environment variables don't exists,
-           # so fall back to a reasonable default:
+            # so fall back to a reasonable default:
             MVSdir = r'C:\Program Files\Microsoft Visual Studio'
             MVSVCdir = r'%s\VC98' % MVSdir
             ConstructionEnvironment = make_win32_env_from_paths(
diff --git a/test/LEX.py b/test/LEX.py
new file mode 100644 (file)
index 0000000..9bf37d7
--- /dev/null
@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2001 Steven Knight
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import os
+import string
+import sys
+import TestSCons
+
+python = sys.executable
+
+if sys.platform == 'win32':
+    _exe = '.exe'
+else:
+    _exe = ''
+
+test = TestSCons.TestSCons()
+
+test.write("wrapper.py",
+"""import os
+import string
+import sys
+open('%s', 'wb').write("wrapper.py\\n")
+os.system(string.join(sys.argv[1:], " "))
+""" % string.replace(test.workpath('wrapper.out'), '\\', '\\\\'))
+
+test.write('SConstruct', """
+foo = Environment()
+lex = foo.Dictionary('LEX')
+bar = Environment(LEX = r'%s wrapper.py ' + lex)
+foo.Program(target = 'foo', source = 'foo.l')
+bar.Program(target = 'bar', source = 'bar.l')
+""" % python)
+
+lex = r"""
+%%%%
+a      printf("A%sA");
+b      printf("B%sB");
+%%%%
+int
+yywrap()
+{
+    return 1;
+}
+
+main()
+{
+    yylex();
+}
+"""
+
+test.write('foo.l', lex % ('foo.l', 'foo.l'))
+
+test.write('bar.l', lex % ('bar.l', 'bar.l'))
+
+test.run(arguments = 'foo' + _exe, stderr = None)
+
+test.fail_test(os.path.exists(test.workpath('wrapper.out')))
+
+test.run(program = test.workpath('foo'), stdin = "a\n", stdout = "Afoo.lA\n")
+
+test.run(arguments = 'bar' + _exe)
+
+test.fail_test(test.read('wrapper.out') != "wrapper.py\n")
+
+test.run(program = test.workpath('bar'), stdin = "b\n", stdout = "Bbar.lB\n")
+
+test.pass_test()
diff --git a/test/LEXFLAGS.py b/test/LEXFLAGS.py
new file mode 100644 (file)
index 0000000..06facb8
--- /dev/null
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2001 Steven Knight
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import os
+import string
+import sys
+import TestSCons
+
+python = sys.executable
+
+if sys.platform == 'win32':
+    _exe = '.exe'
+else:
+    _exe = ''
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """
+foo = Environment()
+bar = Environment(LEXFLAGS = '-b')
+foo.Program(target = 'foo', source = 'foo.l')
+bar.Program(target = 'bar', source = 'bar.l')
+""")
+
+lex = r"""
+%%%%
+a      printf("A%sA");
+b      printf("B%sB");
+%%%%
+int
+yywrap()
+{
+    return 1;
+}
+
+main()
+{
+    yylex();
+}
+"""
+
+test.write('foo.l', lex % ('foo.l', 'foo.l'))
+
+test.write('bar.l', lex % ('bar.l', 'bar.l'))
+
+test.run(arguments = 'foo' + _exe, stderr = None)
+
+test.fail_test(os.path.exists(test.workpath('lex.backup')))
+
+test.run(program = test.workpath('foo'), stdin = "a\n", stdout = "Afoo.lA\n")
+
+test.run(arguments = 'bar' + _exe)
+
+test.fail_test(not os.path.exists(test.workpath('lex.backup')))
+
+test.run(program = test.workpath('bar'), stdin = "b\n", stdout = "Bbar.lB\n")
+
+test.pass_test()
diff --git a/test/YACC.py b/test/YACC.py
new file mode 100644 (file)
index 0000000..f497c69
--- /dev/null
@@ -0,0 +1,102 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2001 Steven Knight
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import os
+import string
+import sys
+import TestSCons
+
+python = sys.executable
+
+if sys.platform == 'win32':
+    _exe = '.exe'
+else:
+    _exe = ''
+
+test = TestSCons.TestSCons()
+
+test.write("wrapper.py",
+"""import os
+import string
+import sys
+open('%s', 'wb').write("wrapper.py\\n")
+os.system(string.join(sys.argv[1:], " "))
+""" % string.replace(test.workpath('wrapper.out'), '\\', '\\\\'))
+
+test.write('SConstruct', """
+foo = Environment()
+yacc = foo.Dictionary('YACC')
+bar = Environment(YACC = r'%s wrapper.py ' + yacc)
+foo.Program(target = 'foo', source = 'foo.y')
+bar.Program(target = 'bar', source = 'bar.y')
+""" % python)
+
+yacc = r"""
+%%{
+#include <stdio.h>
+
+main()
+{
+    yyparse();
+}
+
+yyerror(s)
+char *s;
+{
+    fprintf(stderr, "%%s\n", s);
+}
+
+yylex()
+{
+    int c;
+
+    c = fgetc(stdin);
+    return (c == EOF) ? 0 : c;
+}
+%%}
+%%%%
+input: letter newline { printf("%s\n"); };
+letter:  'a' | 'b';
+newline: '\n';
+"""
+
+test.write('foo.y', yacc % 'foo.y')
+
+test.write('bar.y', yacc % 'bar.y')
+
+test.run(arguments = 'foo' + _exe, stderr = None)
+
+test.fail_test(os.path.exists(test.workpath('wrapper.out')))
+
+test.run(program = test.workpath('foo'), stdin = "a\n", stdout = "foo.y\n")
+
+test.run(arguments = 'bar' + _exe)
+
+test.fail_test(test.read('wrapper.out') != "wrapper.py\n")
+
+test.run(program = test.workpath('bar'), stdin = "b\n", stdout = "bar.y\n")
+
+test.pass_test()
diff --git a/test/YACCFLAGS.py b/test/YACCFLAGS.py
new file mode 100644 (file)
index 0000000..dc127ad
--- /dev/null
@@ -0,0 +1,95 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2001 Steven Knight
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import os
+import string
+import sys
+import TestSCons
+
+python = sys.executable
+
+if sys.platform == 'win32':
+    _exe = '.exe'
+else:
+    _exe = ''
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """
+foo = Environment()
+bar = Environment(YACCFLAGS = '-v')
+foo.Program(target = 'foo', source = 'foo.y')
+bar.Program(target = 'bar', source = 'bar.y')
+""")
+
+yacc = r"""
+%%{
+#include <stdio.h>
+
+main()
+{
+    yyparse();
+}
+
+yyerror(s)
+char *s;
+{
+    fprintf(stderr, "%%s\n", s);
+}
+
+yylex()
+{
+    int c;
+
+    c = fgetc(stdin);
+    return (c == EOF) ? 0 : c;
+}
+%%}
+%%%%
+input: letter newline { printf("%s\n"); };
+letter:  'a' | 'b';
+newline: '\n';
+"""
+
+test.write('foo.y', yacc % 'foo.y')
+
+test.write('bar.y', yacc % 'bar.y')
+
+test.run(arguments = 'foo' + _exe, stderr = None)
+
+test.fail_test(os.path.exists(test.workpath('foo.output'))
+               or os.path.exists(test.workpath('y.output')))
+
+test.run(program = test.workpath('foo'), stdin = "a\n", stdout = "foo.y\n")
+
+test.run(arguments = 'bar' + _exe)
+
+test.fail_test(not os.path.exists(test.workpath('bar.output'))
+               and not os.path.exists(test.workpath('y.output')))
+
+test.run(program = test.workpath('bar'), stdin = "b\n", stdout = "bar.y\n")
+
+test.pass_test()