All of lore.kernel.org
 help / color / mirror / Atom feed
* [WIP 0/2] Adding support for shell arithmetics
@ 2013-05-07  9:22 Olof Johansson
  2013-05-07  9:22 ` [WIP 1/2] pysh: Say what kind of token isn't implemented Olof Johansson
                   ` (2 more replies)
  0 siblings, 3 replies; 5+ messages in thread
From: Olof Johansson @ 2013-05-07  9:22 UTC (permalink / raw)
  To: bitbake-devel

Hi,

A week or so I found out that shell fragments in bitbake recipes
don't support shell arithmetics (`$((1+1))`). The reason for this
is the shell lexing throwing NotImplemented exceptions. I've made
some progress in adding support for shell arithmetics, but I have
a hard time getting support for corner cases.

It was suprising to see that currently, only a subset of posix
shell scripts is supported, even though they are sent through to
be executed by the system shell. I think I understand the reason
for having to do the shell lexing; knowing what functions and
variables to export right? But I wonder if it's possible to do
the shell lexing as a best effort, and ignore any errors.

What are the risks with this approach? Is it feasibile to do so?

This patch series is a work in progress, and does support the use
cases we had issues with, however, I currently skip one unit
test, as I can't get it to work. Comments and feedback much
appreciated!

The branch is also pushed to github at:

 https://github.com/olof/bitbake/tree/topic/shlex_shell_arith

Olof Johansson (2):
  pysh: Say what kind of token isn't implemented
  initial work on supporting shell arith

 bin/bitbake-selftest       |    1 +
 lib/bb/pysh/pyshlex.py     |   48 ++++++++++++++++--
 lib/bb/tests/codeparser.py |   22 ++++++++
 lib/bb/tests/pysh.py       |  119 ++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 186 insertions(+), 4 deletions(-)
 create mode 100644 lib/bb/tests/pysh.py

-- 
1.7.10.4




^ permalink raw reply	[flat|nested] 5+ messages in thread

* [WIP 1/2] pysh: Say what kind of token isn't implemented
  2013-05-07  9:22 [WIP 0/2] Adding support for shell arithmetics Olof Johansson
@ 2013-05-07  9:22 ` Olof Johansson
  2013-05-07  9:22 ` [WIP 2/2] initial work on supporting shell arith Olof Johansson
  2013-06-14 13:55 ` [WIP 0/2] Adding support for shell arithmetics Olof Johansson
  2 siblings, 0 replies; 5+ messages in thread
From: Olof Johansson @ 2013-05-07  9:22 UTC (permalink / raw)
  To: bitbake-devel

When the shell lexer finds an unrecognized dollar token, the error
message should contain what kind of token it is having problems with.

Signed-off-by: Olof Johansson <olof.johansson@axis.com>
---
 lib/bb/pysh/pyshlex.py |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/bb/pysh/pyshlex.py b/lib/bb/pysh/pyshlex.py
index b977b5e..b301236 100644
--- a/lib/bb/pysh/pyshlex.py
+++ b/lib/bb/pysh/pyshlex.py
@@ -292,7 +292,7 @@ class WordLexer:
         elif sep=='${':
             parsefunc = self._parse_parameter
         else:
-            raise NotImplementedError()
+            raise NotImplementedError(sep)
             
         pos, closed = parsefunc(buf, result, eof)
         return pos, closed
-- 
1.7.10.4




^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [WIP 2/2] initial work on supporting shell arith
  2013-05-07  9:22 [WIP 0/2] Adding support for shell arithmetics Olof Johansson
  2013-05-07  9:22 ` [WIP 1/2] pysh: Say what kind of token isn't implemented Olof Johansson
@ 2013-05-07  9:22 ` Olof Johansson
  2013-06-14 13:55 ` [WIP 0/2] Adding support for shell arithmetics Olof Johansson
  2 siblings, 0 replies; 5+ messages in thread
From: Olof Johansson @ 2013-05-07  9:22 UTC (permalink / raw)
  To: bitbake-devel

Signed-off-by: Olof Johansson <olof.johansson@axis.com>
---
 bin/bitbake-selftest       |    1 +
 lib/bb/pysh/pyshlex.py     |   46 +++++++++++++++--
 lib/bb/tests/codeparser.py |   22 ++++++++
 lib/bb/tests/pysh.py       |  119 ++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 185 insertions(+), 3 deletions(-)
 create mode 100644 lib/bb/tests/pysh.py

diff --git a/bin/bitbake-selftest b/bin/bitbake-selftest
index 48a58fe..904a801 100755
--- a/bin/bitbake-selftest
+++ b/bin/bitbake-selftest
@@ -29,6 +29,7 @@ tests = ["bb.tests.codeparser",
          "bb.tests.cow",
          "bb.tests.data",
          "bb.tests.fetch",
+         "bb.tests.pysh",
          "bb.tests.utils"]
 
 for t in tests:
diff --git a/lib/bb/pysh/pyshlex.py b/lib/bb/pysh/pyshlex.py
index b301236..0c68e57 100644
--- a/lib/bb/pysh/pyshlex.py
+++ b/lib/bb/pysh/pyshlex.py
@@ -211,7 +211,45 @@ class WordLexer:
         else:
             #Keep everything until the separator and defer processing
             return pos, False
-            
+
+    def _find_balanced(self, buf, delim, escapes=True):
+        expr = 1
+        pos = 0
+
+        while expr > 0 and pos < len(buf):
+            # open delimiter
+            if ( delim[0] is not None and
+                 ''.join(buf[pos : pos+len(delim[0])]) == delim[0]):
+                expr += 1
+                pos += len(delim[0]) - 1
+            # closing delimiter
+            elif ''.join(buf[pos : pos+len(delim[1])]) == delim[1]:
+                expr -= 1
+                pos += len(delim[1]) - 1
+            elif escapes and buf[pos] == '\\':
+                pos += 1 # skip next, it's escaped!
+
+            pos += 1
+
+        if expr > 0:
+            return -1 # Could not find matching pair of delims
+        return pos
+
+    def _parse_arithmetic(self, buf, result, eof):
+        if not buf:
+            raise NeedMore()
+
+        pos = self._find_balanced(buf, ("$((", "))"))
+
+        if pos < 0:
+            raise NeedMore()
+
+        result[-1] += ''.join(buf[:pos-2])
+        result.append("))")
+
+        has_eof = len(buf) <= pos
+        return pos, has_eof
+
     def _parse_command(self, buf, result, eof):
         if not buf:
             raise NeedMore()
@@ -229,7 +267,7 @@ class WordLexer:
             return pos+1, True
         else:
             return pos, False
-            
+
     def _parse_parameter(self, buf, result, eof):
         if not buf:
             raise NeedMore()
@@ -289,6 +327,8 @@ class WordLexer:
         sep = result[0]    
         if sep=='$(':
             parsefunc = self._parse_command
+        elif sep=='$((':
+            parsefunc = self._parse_arithmetic
         elif sep=='${':
             parsefunc = self._parse_parameter
         else:
@@ -386,7 +426,7 @@ def make_wordtree(token, here_document=False):
         try:
             result, remaining = WordLexer(heredoc = here_document).add(remaining, True)
         except NeedMore:
-            raise ShellSyntaxError('Invalid token "%s"')
+            raise ShellSyntaxError('Invalid token "%s"' % remaining)
         tree.append(result)
         
                 
diff --git a/lib/bb/tests/codeparser.py b/lib/bb/tests/codeparser.py
index 938b04b..debb933 100644
--- a/lib/bb/tests/codeparser.py
+++ b/lib/bb/tests/codeparser.py
@@ -110,6 +110,28 @@ ${D}${libdir}/pkgconfig/*.pc
         self.parseExpression("foo=$(echo bar)")
         self.assertExecs(set(["echo"]))
 
+    def test_assign_arith(self):
+        self.parseExpression("python2.$((8-1))")
+        self.assertExecs(set(["python2.$((8-1))"]))
+
+    def test_assign_arith_only(self):
+        self.parseExpression("$((8-1))")
+        self.assertExecs(set(["$((8-1))"]))
+
+    @unittest.skip(
+        "FIXME: multiple shell arith expressions are not supported")
+    def test_assign_arith_dual(self):
+        self.parseExpression("$((1+1))to$((4-1))")
+        self.assertExecs(set(["$((1+1))to$((4-1))"]))
+
+    def test_assign_arith_nested1(self):
+        self.parseExpression("$((1+$((1))))")
+        self.assertExecs(set(["$((1+$((1))))"]))
+
+    def test_assign_arith_nested2(self):
+        self.parseExpression("$(( 1 + $(( 1 )) ))")
+        self.assertExecs(set(["$(( 1 + $(( 1 )) ))"]))
+
     def test_shell_unexpanded(self):
         self.setEmptyVars(["QT_BASE_NAME"])
         self.parseExpression('echo "${QT_BASE_NAME}"')
diff --git a/lib/bb/tests/pysh.py b/lib/bb/tests/pysh.py
new file mode 100644
index 0000000..3f64f14
--- /dev/null
+++ b/lib/bb/tests/pysh.py
@@ -0,0 +1,119 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# BitBake Tests for the pysh lexer (pysh/)
+#
+# Copyright (C) 2013 Olof Johansson <olof.johansson@axis.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+import unittest
+from bb.pysh.pyshlex import WordLexer, NeedMore
+
+orig_init = WordLexer.__init__
+
+class WordLexerParseTest(unittest.TestCase):
+    # mock out WordLexer's constructor
+    def SetUp(self):
+        WordLexer.__init__ = lambda: None
+
+    def TearDown(self):
+        WordLexer.__init__ = orig_init
+
+    # The arithmetic tests make sure that WordLexer's _parse_arithmetic
+    # method can parse out the expected shell arithmetic expression,
+    # including support for nested expressions.
+    def assertArithmeticParser(self, expr, expect=None, eof=True, length=None):
+        expr = expr[3:] # strip off $((
+        result = ["$((", ""]
+        buf = list(expr)
+
+        # Unless stated otherwise, let's assume the complete buf
+        # is an shell arithmetic expression.
+        if not expect:
+            expect = ["$((", expr[:-2], "))"]
+        if not length:
+            length = len(expr)
+
+        wl = WordLexer()
+        pos, got_eof = WordLexer._parse_arithmetic(wl, buf, result, True)
+
+        self.assertEqual(buf, list(expr))
+        self.assertEqual(result, expect)
+        self.assertEqual(pos, length)
+        self.assertEqual(got_eof, eof)
+
+    def test_parse_arithmetic_simple(self):
+        self.assertArithmeticParser("$((1+1))")
+
+    def test_parse_arithmetic_simple_trailing(self):
+        self.assertArithmeticParser(
+            "$((1+1)); uptime",
+            expect=["$((", "1+1", "))"],
+            length=5,
+            eof=False)
+
+    def test_parse_arithmetic_incomplete(self):
+        wl = WordLexer()
+        res = ["$((", ""]
+        buf = list("10+")
+
+        needs_more = False
+        try:
+            pos, got_eof = WordLexer._parse_arithmetic(wl, buf, res, True)
+        except NeedMore:
+            needs_more = True
+
+        self.assertTrue(needs_more)
+
+    def test_parse_arithmetic_empty(self):
+        wl = WordLexer()
+        res = ["$((", ""]
+        buf = list("")
+
+        needs_more = False
+        try:
+            pos, got_eof = WordLexer._parse_arithmetic(wl, buf, res, True)
+        except NeedMore:
+            needs_more = True
+
+        self.assertTrue(needs_more)
+
+    def test_parse_arithmetic_simple_whitespace(self):
+        self.assertArithmeticParser("$(( 1 + 1 ))")
+
+    def test_parse_arithmetic_nested1(self):
+        self.assertArithmeticParser("$((1+$((1+2))))")
+
+    def test_parse_arithmetic_nested2(self):
+        self.assertArithmeticParser("$(($((1+2))+$((1+2))))")
+
+    def assertCommandParser(self, command="", sep=("$(", ")"), eof=True):
+        result = [sep[0], ""]
+        buf = ''.join([command, sep[1]])
+
+        wl = WordLexer()
+        pos, got_eof = WordLexer._parse_command(wl, buf, result, True)
+
+        self.assertEqual(result, [sep[0], command, sep[1]])
+
+    def test_parse_command_simple(self):
+        self.assertCommandParser("uptime")
+
+    def test_parse_command_backticks(self):
+        self.assertCommandParser("echo bar", sep=("`", "`"))
+
+    #def test_parse_command_nested(self):
+    #    self.assertCommandParser("echo $(uptime)")
-- 
1.7.10.4




^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [WIP 0/2] Adding support for shell arithmetics
  2013-05-07  9:22 [WIP 0/2] Adding support for shell arithmetics Olof Johansson
  2013-05-07  9:22 ` [WIP 1/2] pysh: Say what kind of token isn't implemented Olof Johansson
  2013-05-07  9:22 ` [WIP 2/2] initial work on supporting shell arith Olof Johansson
@ 2013-06-14 13:55 ` Olof Johansson
  2013-06-17 16:36   ` Richard Purdie
  2 siblings, 1 reply; 5+ messages in thread
From: Olof Johansson @ 2013-06-14 13:55 UTC (permalink / raw)
  To: bitbake-devel@lists.openembedded.org

On 2013-05-07 11:22, Olof Johansson wrote:
> Hi,
> 
> A week or so I found out that shell fragments in bitbake recipes
> don't support shell arithmetics (`$((1+1))`). The reason for this
> is the shell lexing throwing NotImplemented exceptions. I've made
> some progress in adding support for shell arithmetics, but I have
> a hard time getting support for corner cases.
> 
> It was suprising to see that currently, only a subset of posix
> shell scripts is supported, even though they are sent through to
> be executed by the system shell. I think I understand the reason
> for having to do the shell lexing; knowing what functions and
> variables to export right? But I wonder if it's possible to do
> the shell lexing as a best effort, and ignore any errors.
> 
> What are the risks with this approach? Is it feasibile to do so?
> 
> This patch series is a work in progress, and does support the use
> cases we had issues with, however, I currently skip one unit
> test, as I can't get it to work. Comments and feedback much
> appreciated!

Did anybody have any feedback on this? I have had a hard time
understanding the design of the lexing. I really could use some
pointers --- is there some documents somewhere that could make me
understand it better? I haven't had time to work on this lately,
but it would be great to get this fixed, so I figured I'll try to
take some time in the near future.

/Olof

PS. Saw that RP merged the exception message patch, thanks :).


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [WIP 0/2] Adding support for shell arithmetics
  2013-06-14 13:55 ` [WIP 0/2] Adding support for shell arithmetics Olof Johansson
@ 2013-06-17 16:36   ` Richard Purdie
  0 siblings, 0 replies; 5+ messages in thread
From: Richard Purdie @ 2013-06-17 16:36 UTC (permalink / raw)
  To: Olof Johansson; +Cc: bitbake-devel@lists.openembedded.org

On Fri, 2013-06-14 at 15:55 +0200, Olof Johansson wrote:
> On 2013-05-07 11:22, Olof Johansson wrote:
> > Hi,
> > 
> > A week or so I found out that shell fragments in bitbake recipes
> > don't support shell arithmetics (`$((1+1))`). The reason for this
> > is the shell lexing throwing NotImplemented exceptions. I've made
> > some progress in adding support for shell arithmetics, but I have
> > a hard time getting support for corner cases.
> > 
> > It was suprising to see that currently, only a subset of posix
> > shell scripts is supported, even though they are sent through to
> > be executed by the system shell. I think I understand the reason
> > for having to do the shell lexing; knowing what functions and
> > variables to export right? But I wonder if it's possible to do
> > the shell lexing as a best effort, and ignore any errors.
> > 
> > What are the risks with this approach? Is it feasibile to do so?
> > 
> > This patch series is a work in progress, and does support the use
> > cases we had issues with, however, I currently skip one unit
> > test, as I can't get it to work. Comments and feedback much
> > appreciated!
> 
> Did anybody have any feedback on this? I have had a hard time
> understanding the design of the lexing. I really could use some
> pointers --- is there some documents somewhere that could make me
> understand it better? I haven't had time to work on this lately,
> but it would be great to get this fixed, so I figured I'll try to
> take some time in the near future.
> 
> /Olof
> 
> PS. Saw that RP merged the exception message patch, thanks :).

That one was easy :)

I'm afraid I don't have much knowledge of the sh lexing code, Chris
might have more knowledge than me...

Cheers,

Richard




^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2013-06-17 16:36 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2013-05-07  9:22 [WIP 0/2] Adding support for shell arithmetics Olof Johansson
2013-05-07  9:22 ` [WIP 1/2] pysh: Say what kind of token isn't implemented Olof Johansson
2013-05-07  9:22 ` [WIP 2/2] initial work on supporting shell arith Olof Johansson
2013-06-14 13:55 ` [WIP 0/2] Adding support for shell arithmetics Olof Johansson
2013-06-17 16:36   ` Richard Purdie

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.