From 13bfa731df53edbd6262a69587a6d50442b501f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Sat, 16 Jan 2016 09:38:24 +0100 Subject: [PATCH] Initial commit --- LICENSE | 27 ++++++++++++++ README.md | 3 ++ parse.go | 91 +++++++++++++++++++++++++++++++++++++++++++++++ sh_test.go | 31 ++++++++++++++++ testdata/empty.sh | 0 5 files changed, 152 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 parse.go create mode 100644 sh_test.go create mode 100644 testdata/empty.sh diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..5babf241d --- /dev/null +++ b/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2015, Daniel Martí. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 000000000..4f53aeea0 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# sh + +A shell parser. Not yet usable. diff --git a/parse.go b/parse.go new file mode 100644 index 000000000..f2ef65a59 --- /dev/null +++ b/parse.go @@ -0,0 +1,91 @@ +// Copyright (c) 2016, Daniel Martí +// See LICENSE for licensing information + +package sh + +import ( + "bufio" + "fmt" + "io" +) + +const ( + _ = -iota + EOF + IDENT +) + +func parse(r io.Reader) error { + p := &parser{ + r: bufio.NewReader(r), + } + return p.program() +} + +type parser struct { + r *bufio.Reader + tok int32 +} + +func isIdentChar(r rune) bool { + return r == '_' || (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') +} + +func (p *parser) next() error { + r, _, err := p.r.ReadRune() + if err == io.EOF { + p.tok = EOF + return nil + } + if err != nil { + return err + } + if r == ' ' { + return p.next() + } + if isIdentChar(r) { + for isIdentChar(r) { + r, _, err = p.r.ReadRune() + if err != nil { + return err + } + } + p.tok = IDENT + return nil + } + p.tok = r + return nil +} + +func (p *parser) discardLine() error { + if _, err := p.r.ReadBytes('\n'); err != nil { + return err + } + return p.next() +} + +func (p *parser) got(tok int32) bool { + if p.tok == tok { + p.next() + return true + } + return false +} + +func (p *parser) program() error { + if err := p.next(); err != nil { + return err + } + for p.tok != EOF { + switch { + case p.got('#'): + if err := p.discardLine(); err != nil { + return err + } + case p.got(IDENT): + default: + return fmt.Errorf("unexpected token") + } + } + return nil +} diff --git a/sh_test.go b/sh_test.go new file mode 100644 index 000000000..a09df86de --- /dev/null +++ b/sh_test.go @@ -0,0 +1,31 @@ +// Copyright (c) 2016, Daniel Martí +// See LICENSE for licensing information + +package sh + +import ( + "os" + "path/filepath" + "testing" +) + +func TestParse(t *testing.T) { + paths, err := filepath.Glob("testdata/*.sh") + if err != nil { + t.Fatal(err) + } + for _, path := range paths { + testParse(t, path) + } +} + +func testParse(t *testing.T, path string) { + f, err := os.Open(path) + if err != nil { + t.Fatal(err) + } + defer f.Close() + if err := parse(f); err != nil { + t.Fatalf("Error in %s: %v", path, err) + } +} diff --git a/testdata/empty.sh b/testdata/empty.sh new file mode 100644 index 000000000..e69de29bb