From 2ca2f9a8633ff80fc4cf78d30889b21591795f8a Mon Sep 17 00:00:00 2001 From: stevenknight Date: Mon, 7 Jan 2002 05:06:56 +0000 Subject: [PATCH] Add support for lex and yacc. git-svn-id: http://scons.tigris.org/svn/scons/trunk@191 fdb21ef1-2011-0410-befe-b5e4ea1792b1 --- doc/man/scons.1 | 36 ++++++++++- src/CHANGES.txt | 3 + src/engine/SCons/Builder.py | 66 +++++++++++--------- src/engine/SCons/BuilderTests.py | 14 ++++- src/engine/SCons/Defaults.py | 29 +++++++-- test/LEX.py | 90 +++++++++++++++++++++++++++ test/LEXFLAGS.py | 81 ++++++++++++++++++++++++ test/YACC.py | 102 +++++++++++++++++++++++++++++++ test/YACCFLAGS.py | 95 ++++++++++++++++++++++++++++ 9 files changed, 479 insertions(+), 37 deletions(-) create mode 100644 test/LEX.py create mode 100644 test/LEXFLAGS.py create mode 100644 test/YACC.py create mode 100644 test/YACCFLAGS.py diff --git a/doc/man/scons.1 b/doc/man/scons.1 index ad8a8349..7ac1bf42 100644 --- a/doc/man/scons.1 +++ b/doc/man/scons.1 @@ -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. diff --git a/src/CHANGES.txt b/src/CHANGES.txt index e1e57ec5..b1ae4c4d 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -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 diff --git a/src/engine/SCons/Builder.py b/src/engine/SCons/Builder.py index 6651b138..f47e3a7f 100644 --- a/src/engine/SCons/Builder.py +++ b/src/engine/SCons/Builder.py @@ -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; diff --git a/src/engine/SCons/BuilderTests.py b/src/engine/SCons/BuilderTests.py index b735a093..82930765 100644 --- a/src/engine/SCons/BuilderTests.py +++ b/src/engine/SCons/BuilderTests.py @@ -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 diff --git a/src/engine/SCons/Defaults.py b/src/engine/SCons/Defaults.py index bbc9e034..2e11c374 100644 --- a/src/engine/SCons/Defaults.py +++ b/src/engine/SCons/Defaults.py @@ -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 index 00000000..9bf37d71 --- /dev/null +++ b/test/LEX.py @@ -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 index 00000000..06facb81 --- /dev/null +++ b/test/LEXFLAGS.py @@ -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 index 00000000..f497c692 --- /dev/null +++ b/test/YACC.py @@ -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 + +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 index 00000000..dc127ad7 --- /dev/null +++ b/test/YACCFLAGS.py @@ -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 + +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() -- 2.26.2