summaryrefslogtreecommitdiff
path: root/src/sexpr
diff options
context:
space:
mode:
Diffstat (limited to 'src/sexpr')
-rw-r--r--src/sexpr/lexer.cpp228
-rw-r--r--src/sexpr/lexer.hpp73
-rw-r--r--src/sexpr/lexer_test.cpp113
-rw-r--r--src/sexpr/main.cpp130
-rw-r--r--src/sexpr/parser.cpp79
-rw-r--r--src/sexpr/parser.hpp80
-rw-r--r--src/sexpr/parser.py25
-rw-r--r--src/sexpr/parser_test.cpp89
8 files changed, 817 insertions, 0 deletions
diff --git a/src/sexpr/lexer.cpp b/src/sexpr/lexer.cpp
new file mode 100644
index 0000000..8c1c380
--- /dev/null
+++ b/src/sexpr/lexer.cpp
@@ -0,0 +1,228 @@
+#include "lexer.hpp"
+// lexer.cpp - tokenize a stream of S-expressions
+//
+// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// 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 Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "../strings/mstring.hpp"
+
+#include "../io/cxxstdio.hpp"
+
+#include "../poison.hpp"
+
+namespace sexpr
+{
+ Lexeme Lexer::_adv()
+ {
+ XString whitespace = " \t\n\r\v\f";
+ while (true)
+ {
+ if (!_in.get(_span.begin))
+ {
+ if (!_depth.empty())
+ {
+ _depth.back().error("Unmatched '('");
+ return TOK_ERROR;
+ }
+ return TOK_EOF;
+ }
+ char co = _span.begin.ch();
+ if (!whitespace.contains(co))
+ break;
+ _in.adv();
+ }
+
+ char co = _span.begin.ch();
+ _in.adv();
+ _span.end = _span.begin;
+ switch (co)
+ {
+ case '(':
+ _string = "(";
+ _depth.push_back(_span.end);
+ return TOK_OPEN;
+ case ')':
+ _string = ")";
+ if (_depth.empty())
+ {
+ _span.end.error("Unmatched ')'");
+ return TOK_ERROR;
+ }
+ _depth.pop_back();
+ return TOK_CLOSE;
+ case '"':
+ {
+ MString collect;
+ // read until " and consume it
+ // but handle \s
+ while (true)
+ {
+ if (!_in.get(_span.end))
+ {
+ _span.error("EOF in string literal");
+ return TOK_ERROR;
+ }
+ char ch = _span.end.ch();
+ _in.adv();
+ if (ch == '"')
+ break;
+
+ if (ch != '\\')
+ {
+ collect += ch;
+ continue;
+ }
+
+ if (!_in.get(_span.end))
+ {
+ _span.end.error("EOF at backslash in string");
+ return TOK_ERROR;
+ }
+ ch = _span.end.ch();
+ _in.adv();
+ switch (ch)
+ {
+ default:
+ _span.end.error("Unknown backslash sequence");
+ return TOK_ERROR;
+ case 'a': collect += '\a'; break;
+ case 'b': collect += '\b'; break;
+ case 'e': collect += '\e'; break;
+ case 'f': collect += '\f'; break;
+ case 'n': collect += '\n'; break;
+ case 'r': collect += '\r'; break;
+ case 't': collect += '\t'; break;
+ case 'v': collect += '\v'; break;
+ case '\\': collect += '\\'; break;
+ case '\"': collect += '\"'; break;
+ case 'x':
+ {
+ unsigned char tmp = 0;
+ for (int i = 0; i < 2; ++i)
+ {
+ tmp *= 16;
+ if (!_in.get(_span.end))
+ {
+ _span.end.error("EOF after \\x in string");
+ return TOK_ERROR;
+ }
+ char cx = _span.end.ch();
+ _in.adv();
+ if ('0' <= cx && cx <= '9')
+ tmp += cx - '0';
+ else if ('A' <= cx && cx <= 'F')
+ tmp += cx - 'A' + 10;
+ else if ('a' <= cx && cx <= 'a')
+ tmp += cx - 'a' + 10;
+ else
+ {
+ _span.end.error("Non-hex char after \\x");
+ return TOK_ERROR;
+ }
+ }
+ collect += tmp;
+ }
+ }
+ }
+ _string = AString(collect);
+ return TOK_STRING;
+ }
+ case '\'':
+ case '\\':
+ _span.end.error("forbidden character");
+ return TOK_ERROR;
+ default:
+ // this includes integers - they are differentiated in parsing
+ {
+ MString collect;
+ collect += co;
+ // read until whitespace, (, ), ", or EOF
+ io::LineChar tmp;
+ while (_in.get(tmp))
+ {
+ char ct = tmp.ch();
+ if (ct == '\'' || ct == '\\')
+ // error later
+ break;
+ if (ct == '(' || ct == ')' || ct == '"')
+ break;
+ if (whitespace.contains(ct))
+ break;
+ collect += ct;
+ _span.end = tmp;
+ _in.adv();
+ }
+ _string = AString(collect);
+ if (!_string.is_print())
+ _span.error("String is not entirely printable");
+ return TOK_TOKEN;
+ }
+ }
+ }
+
+ VString<4> escape(char c)
+ {
+ switch (c)
+ {
+ case '\a': return {"\\a"};
+ case '\b': return {"\\b"};
+ case '\e': return {"\\e"};
+ case '\f': return {"\\f"};
+ //case '\n': return {"\\n"};
+ case '\r': return {"\\r"};
+ case '\t': return {"\\t"};
+ case '\v': return {"\\v"};
+ case '\\': return {"\\\\"};
+ case '\"': return {"\\\""};
+ default:
+ if (c == '\n')
+ return c;
+ if (' ' <= c && c <= '~')
+ return c;
+ else
+ return STRNPRINTF(5, "\\x%02x", static_cast<uint8_t>(c));
+ }
+ }
+ AString escape(XString s)
+ {
+ MString m;
+ m += '"';
+ for (char c : s)
+ m += escape(c);
+ m += '"';
+ return AString(m);
+ }
+
+ ZString token_name(Lexeme tok)
+ {
+ switch (tok)
+ {
+ case TOK_EOF:
+ return ZString("EOF");
+ case TOK_OPEN:
+ return ZString("OPEN");
+ case TOK_CLOSE:
+ return ZString("CLOSE");
+ case TOK_STRING:
+ return ZString("STRING");
+ case TOK_TOKEN:
+ return ZString("TOKEN");
+ default:
+ return ZString("ERROR");
+ }
+ }
+} // namespace sexpr
diff --git a/src/sexpr/lexer.hpp b/src/sexpr/lexer.hpp
new file mode 100644
index 0000000..7bce620
--- /dev/null
+++ b/src/sexpr/lexer.hpp
@@ -0,0 +1,73 @@
+#ifndef TMWA_SEXPR_LEXER_HPP
+#define TMWA_SEXPR_LEXER_HPP
+// lexer.hpp - tokenize a stream of S-expressions
+//
+// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// 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 Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# include "../sanity.hpp"
+
+# include "../strings/astring.hpp"
+# include "../strings/vstring.hpp"
+# include "../strings/xstring.hpp"
+# include "../strings/zstring.hpp"
+
+# include "../io/line.hpp"
+
+namespace sexpr
+{
+ enum Lexeme
+ {
+ TOK_EOF = 0,
+ TOK_ERROR,
+ TOK_OPEN,
+ TOK_CLOSE,
+ TOK_STRING,
+ TOK_TOKEN,
+ };
+
+ class Lexer
+ {
+ io::LineCharReader _in;
+ Lexeme _current;
+ AString _string;
+ io::LineSpan _span;
+ std::vector<io::LineChar> _depth;
+ private:
+ Lexeme _adv();
+ public:
+ explicit
+ Lexer(ZString filename)
+ : _in(filename), _current(TOK_EOF), _span(), _depth()
+ { adv(); }
+ // for unit tests
+ Lexer(ZString fake, io::FD fd)
+ : _in(fake, fd), _current(TOK_EOF), _span(), _depth()
+ { adv(); }
+ Lexeme peek() { return _current; }
+ void adv() { _current = _adv(); }
+ ZString val_string() { return _string; }
+ io::LineSpan span() { return _span; }
+ };
+
+ VString<4> escape(char c);
+ AString escape(XString s);
+
+ ZString token_name(Lexeme tok);
+} // namespace sexpr
+
+#endif // TMWA_SEXPR_LEXER_HPP
diff --git a/src/sexpr/lexer_test.cpp b/src/sexpr/lexer_test.cpp
new file mode 100644
index 0000000..7a2dc09
--- /dev/null
+++ b/src/sexpr/lexer_test.cpp
@@ -0,0 +1,113 @@
+#include "lexer.hpp"
+
+#include <gtest/gtest.h>
+
+static
+io::FD string_pipe(ZString sz)
+{
+ io::FD rfd, wfd;
+ if (-1 == io::FD::pipe(rfd, wfd))
+ return io::FD();
+ if (sz.size() != wfd.write(sz.c_str(), sz.size()))
+ {
+ rfd.close();
+ wfd.close();
+ return io::FD();
+ }
+ wfd.close();
+ return rfd;
+}
+
+TEST(sexpr, escape)
+{
+ EXPECT_EQ(sexpr::escape('\0'), "\\x00");
+ EXPECT_EQ(sexpr::escape('\x1f'), "\\x1f");
+ EXPECT_EQ(sexpr::escape('\x20'), " ");
+ EXPECT_EQ(sexpr::escape('\x7e'), "~");
+ EXPECT_EQ(sexpr::escape('\x7f'), "\\x7f");
+ EXPECT_EQ(sexpr::escape('\x80'), "\\x80");
+ EXPECT_EQ(sexpr::escape('\xff'), "\\xff");
+ EXPECT_EQ(sexpr::escape('\a'), "\\a");
+ EXPECT_EQ(sexpr::escape('\b'), "\\b");
+ EXPECT_EQ(sexpr::escape('\e'), "\\e");
+ EXPECT_EQ(sexpr::escape('\f'), "\\f");
+ //EXPECT_EQ(sexpr::escape('\n'), "\\n");
+ EXPECT_EQ(sexpr::escape('\n'), "\n");
+ EXPECT_EQ(sexpr::escape('\r'), "\\r");
+ EXPECT_EQ(sexpr::escape('\t'), "\\t");
+ EXPECT_EQ(sexpr::escape('\v'), "\\v");
+ EXPECT_EQ(sexpr::escape('\\'), "\\\\");
+ EXPECT_EQ(sexpr::escape('\"'), "\\\"");
+
+ EXPECT_EQ(sexpr::escape("\x1f\x20\x7e\x7f\x80\xff\a\b\e\f\r\t\v\\\""),
+ "\"\\x1f ~\\x7f\\x80\\xff\\a\\b\\e\\f\\r\\t\\v\\\\\\\"\"");
+}
+
+TEST(sexpr, lexer)
+{
+ io::LineSpan span;
+ sexpr::Lexer lexer("<lexer-test1>", string_pipe(" foo( ) 123\"\" \n"));
+ EXPECT_EQ(lexer.peek(), sexpr::TOK_TOKEN);
+ EXPECT_EQ(lexer.val_string(), "foo");
+ EXPECT_EQ(lexer.span().message_str("error", "test"),
+ "<lexer-test1>:1:2: error: test\n"
+ " foo( ) 123\"\" \n"
+ " ^~~\n"
+ );
+ lexer.adv();
+ EXPECT_EQ(lexer.peek(), sexpr::TOK_OPEN);
+ EXPECT_EQ(lexer.span().message_str("error", "test"),
+ "<lexer-test1>:1:5: error: test\n"
+ " foo( ) 123\"\" \n"
+ " ^\n"
+ );
+ lexer.adv();
+ EXPECT_EQ(lexer.peek(), sexpr::TOK_CLOSE);
+ EXPECT_EQ(lexer.span().message_str("error", "test"),
+ "<lexer-test1>:1:7: error: test\n"
+ " foo( ) 123\"\" \n"
+ " ^\n"
+ );
+ lexer.adv();
+ EXPECT_EQ(lexer.peek(), sexpr::TOK_TOKEN);
+ EXPECT_EQ(lexer.val_string(), "123");
+ EXPECT_EQ(lexer.span().message_str("error", "test"),
+ "<lexer-test1>:1:9: error: test\n"
+ " foo( ) 123\"\" \n"
+ " ^~~\n"
+ );
+ lexer.adv();
+ EXPECT_EQ(lexer.peek(), sexpr::TOK_STRING);
+ EXPECT_EQ(lexer.val_string(), "");
+ EXPECT_EQ(lexer.span().message_str("error", "test"),
+ "<lexer-test1>:1:12: error: test\n"
+ " foo( ) 123\"\" \n"
+ " ^~\n"
+ );
+ lexer.adv();
+ EXPECT_EQ(lexer.peek(), sexpr::TOK_EOF);
+}
+
+TEST(sexpr, lexbad)
+{
+ {
+ io::LineSpan span;
+ sexpr::Lexer lexer("<lexer-bad>", string_pipe("(\n"));
+ EXPECT_EQ(lexer.peek(), sexpr::TOK_OPEN);
+ lexer.adv();
+ EXPECT_EQ(lexer.peek(), sexpr::TOK_ERROR);
+ }
+ for (ZString bad : {
+ ZString(")\n"),
+ ZString("\"\n"),
+ ZString("'\n"),
+ ZString("\\\n"),
+ ZString("\"\\"),
+ ZString("\"\\z\""),
+ })
+ {
+ io::LineSpan span;
+ sexpr::Lexer lexer("<lexer-bad>", string_pipe(bad));
+ EXPECT_EQ(lexer.peek(), sexpr::TOK_ERROR);
+ }
+}
diff --git a/src/sexpr/main.cpp b/src/sexpr/main.cpp
new file mode 100644
index 0000000..7d63ddf
--- /dev/null
+++ b/src/sexpr/main.cpp
@@ -0,0 +1,130 @@
+#include <stack>
+#include <map>
+
+#include "../io/cxxstdio.hpp"
+
+#include "lexer.hpp"
+#include "parser.hpp"
+
+#include "../poison.hpp"
+
+enum Spacing
+{
+ LINES,
+ SIMPLE,
+ SPACES,
+ SPACES_1,
+ SPACES_2,
+ SPACES_3,
+ SPACES_4,
+};
+
+static
+void do_spacing(bool& first, Spacing& sp, int depth)
+{
+ if (first)
+ {
+ first = false;
+ return;
+ }
+ switch (sp)
+ {
+ case LINES:
+ PRINTF("\n%*s", (depth - 1) * 4, "");
+ return;
+ case SPACES:
+ case SIMPLE:
+ PRINTF(" ");
+ return;
+ case SPACES_1:
+ PRINTF(" ");
+ sp = LINES;
+ return;
+ case SPACES_2:
+ PRINTF(" ");
+ sp = SPACES_1;
+ return;
+ case SPACES_3:
+ PRINTF(" ");
+ sp = SPACES_2;
+ return;
+ case SPACES_4:
+ PRINTF(" ");
+ sp = SPACES_3;
+ return;
+ }
+}
+
+static
+void adjust_spacing(Spacing& sp, ZString val)
+{
+ std::map<ZString, Spacing> spaces =
+ {
+ {"BLOCK", LINES},
+ {"GUARD", LINES},
+ {"DISABLED", LINES},
+ {"PROCEDURE", SPACES_2},
+ {"SPELL", SPACES_4},
+ {"IF", SPACES_1},
+ {"set_script_variable", SPACES_2},
+ };
+ auto it = spaces.find(val);
+ if (it != spaces.end())
+ sp = it->second;
+}
+
+int main()
+{
+ if (1 == 1)
+ {
+ sexpr::Lexer lexer("/dev/stdin");
+ sexpr::SExpr sexpr;
+ while (sexpr::parse(lexer, sexpr))
+ {
+ PRINTF("");
+ }
+ if (lexer.peek() != sexpr::TOK_EOF)
+ {
+ lexer.span().error(STRPRINTF("Incomplete: %s: %s\n",
+ sexpr::token_name(lexer.peek()), lexer.val_string()));
+ }
+ return 0;
+ }
+
+ std::stack<Spacing> spacing;
+ spacing.push(LINES);
+ sexpr::Lexer lexer("/dev/stdin");
+ bool first = true;
+ while (sexpr::Lexeme tok = lexer.peek())
+ {
+ switch (tok)
+ {
+ case sexpr::TOK_OPEN:
+ if (spacing.top() == SIMPLE)
+ spacing.top() = LINES;
+ do_spacing(first, spacing.top(), spacing.size());
+ PRINTF("(");
+ spacing.push(SIMPLE);
+ first = true;
+ break;
+ case sexpr::TOK_CLOSE:
+ PRINTF(")");
+ spacing.pop();
+ first = false;
+ break;
+ case sexpr::TOK_STRING:
+ do_spacing(first, spacing.top(), spacing.size());
+ PRINTF("%s", sexpr::escape(lexer.val_string()));
+ break;
+ case sexpr::TOK_TOKEN:
+ do_spacing(first, spacing.top(), spacing.size());
+ PRINTF("%s", lexer.val_string());
+ adjust_spacing(spacing.top(), lexer.val_string());
+ break;
+ default:
+ abort();
+ }
+ lexer.adv();
+ }
+ PRINTF("\n");
+}
diff --git a/src/sexpr/parser.cpp b/src/sexpr/parser.cpp
new file mode 100644
index 0000000..2068565
--- /dev/null
+++ b/src/sexpr/parser.cpp
@@ -0,0 +1,79 @@
+#include "parser.hpp"
+// parser.cpp - build a tree of S-expressions
+//
+// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// 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 Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "../poison.hpp"
+
+namespace sexpr
+{
+ bool token_is_int(ZString s, int64_t& out, bool& ok)
+ {
+ if (!s)
+ return false;
+ if (s.startswith('-') || s.xslice_h(1).is_digit10())
+ {
+ const char *z = s.c_str();
+ char *end = nullptr;
+ errno = 0;
+ out = strtoll(z, &end, 0);
+ if (errno)
+ ok = false;
+ return !*end;
+ }
+ return false;
+ }
+
+ bool parse(Lexer& lex, SExpr& out)
+ {
+ out._list.clear();
+ out._str = RString();
+
+ bool rv = true;
+ out._span.begin = lex.span().begin;
+ switch (lex.peek())
+ {
+ default:
+ return false;
+ case TOK_STRING:
+ out._type = STRING;
+ out._str = lex.val_string();
+ break;
+ case TOK_TOKEN:
+ out._type = TOKEN;
+ out._str = lex.val_string();
+ if (token_is_int(out._str, out._int, rv))
+ out._type = INT;
+ break;
+ case TOK_OPEN:
+ out._type = LIST;
+ lex.adv();
+ while (lex.peek() != TOK_CLOSE)
+ {
+ SExpr tmp;
+ if (!parse(lex, tmp))
+ return false;
+ out._list.push_back(std::move(tmp));
+ }
+ break;
+ }
+ out._span.end = lex.span().end;
+ lex.adv();
+ return rv;
+ }
+} // namespace sexpr
diff --git a/src/sexpr/parser.hpp b/src/sexpr/parser.hpp
new file mode 100644
index 0000000..6097f78
--- /dev/null
+++ b/src/sexpr/parser.hpp
@@ -0,0 +1,80 @@
+#ifndef TMWA_SEXPR_PARSER_HPP
+#define TMWA_SEXPR_PARSER_HPP
+// parser.hpp - build a tree of S-expressions
+//
+// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// 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 Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# include "../sanity.hpp"
+
+# include "../strings/zstring.hpp"
+
+# include "../io/line.hpp"
+
+# include "lexer.hpp"
+
+namespace sexpr
+{
+ enum Type
+ {
+ LIST,
+ INT,
+ STRING,
+ TOKEN,
+ };
+
+ struct SExpr
+ {
+ Type _type;
+ int64_t _int;
+ RString _str;
+ std::vector<SExpr> _list;
+
+ io::LineSpan _span;
+
+ SExpr() : _type(), _int(), _str(), _list(), _span() {}
+ };
+
+ inline
+ bool operator == (const SExpr& l, const SExpr& r)
+ {
+ if (l._type != r._type)
+ return false;
+ switch (l._type)
+ {
+ case LIST:
+ return l._list == r._list;
+ case INT:
+ return l._int == r._int;
+ case STRING:
+ case TOKEN:
+ return l._str == r._str;
+ }
+ abort();
+ }
+ inline
+ bool operator != (const SExpr& l, const SExpr& r)
+ {
+ return !(l == r);
+ }
+
+ bool token_is_int(ZString s, int64_t& out, bool& ok);
+ /// return false on error or eof, check lex.peek() == TOK_EOF to see
+ bool parse(Lexer& lex, SExpr& out);
+} // namespace sexpr
+
+#endif // TMWA_SEXPR_PARSER_HPP
diff --git a/src/sexpr/parser.py b/src/sexpr/parser.py
new file mode 100644
index 0000000..d638259
--- /dev/null
+++ b/src/sexpr/parser.py
@@ -0,0 +1,25 @@
+class SExpr(object):
+ ''' print a SExpr
+ '''
+ __slots__ = ('_value')
+ name = 'sexpr::SExpr'
+ enabled = True
+
+ def __init__(self, value):
+ self._value = value
+
+ def to_string(self):
+ return None
+
+ def children(self):
+ v = self._value
+ t = v['_type']
+ if t == 0:
+ yield '(list)', v['_list']
+ if t == 1:
+ yield '(int)', v['_int']
+ if t == 2:
+ yield '(str)', v['_str']
+ if t == 3:
+ yield '(token)', v['_str']
+ yield '_span', v['_span']
diff --git a/src/sexpr/parser_test.cpp b/src/sexpr/parser_test.cpp
new file mode 100644
index 0000000..a752313
--- /dev/null
+++ b/src/sexpr/parser_test.cpp
@@ -0,0 +1,89 @@
+#include "parser.hpp"
+
+#include <gtest/gtest.h>
+
+static
+io::FD string_pipe(ZString sz)
+{
+ io::FD rfd, wfd;
+ if (-1 == io::FD::pipe(rfd, wfd))
+ return io::FD();
+ if (sz.size() != wfd.write(sz.c_str(), sz.size()))
+ {
+ rfd.close();
+ wfd.close();
+ return io::FD();
+ }
+ wfd.close();
+ return rfd;
+}
+
+TEST(sexpr, parser)
+{
+ sexpr::SExpr s;
+ io::LineSpan span;
+ sexpr::Lexer lexer("<parser-test1>", string_pipe(" foo( ) 123\"\" \n"));
+
+ EXPECT_TRUE(sexpr::parse(lexer, s));
+ EXPECT_EQ(s._type, sexpr::TOKEN);
+ EXPECT_EQ(s._str, "foo");
+
+ EXPECT_TRUE(sexpr::parse(lexer, s));
+ EXPECT_EQ(s._type, sexpr::LIST);
+ EXPECT_EQ(s._list, std::vector<sexpr::SExpr>());
+
+ EXPECT_TRUE(sexpr::parse(lexer, s));
+ EXPECT_EQ(s._type, sexpr::INT);
+ EXPECT_EQ(s._int, 123);
+
+ EXPECT_TRUE(sexpr::parse(lexer, s));
+ EXPECT_EQ(s._type, sexpr::STRING);
+ EXPECT_EQ(s._str, "");
+
+ EXPECT_FALSE(sexpr::parse(lexer, s));
+ EXPECT_EQ(lexer.peek(), sexpr::TOK_EOF);
+}
+
+TEST(sexpr, parselist)
+{
+ sexpr::SExpr s;
+ sexpr::Lexer lexer("<parser-test1>", string_pipe("(foo)(bar)\n"));
+
+ EXPECT_TRUE(sexpr::parse(lexer, s));
+ EXPECT_EQ(s._type, sexpr::LIST);
+ EXPECT_EQ(s._list.size(), 1);
+ EXPECT_EQ(s._list[0]._type, sexpr::TOKEN);
+ EXPECT_EQ(s._list[0]._str, "foo");
+
+ EXPECT_TRUE(sexpr::parse(lexer, s));
+ EXPECT_EQ(s._type, sexpr::LIST);
+ EXPECT_EQ(s._list.size(), 1);
+ EXPECT_EQ(s._list[0]._type, sexpr::TOKEN);
+ EXPECT_EQ(s._list[0]._str, "bar");
+
+ EXPECT_FALSE(sexpr::parse(lexer, s));
+ EXPECT_EQ(lexer.peek(), sexpr::TOK_EOF);
+}
+
+TEST(sexpr, parsebad)
+{
+ for (ZString bad : {
+ ZString("(\n"),
+ ZString(")\n"),
+ ZString("\"\n"),
+ ZString("'\n"),
+ ZString("\\\n"),
+ ZString("\"\\"),
+ ZString("\"\\z\""),
+ ZString("(()\n"),
+ ZString("((\n"),
+ ZString("((\"\n"),
+ })
+ {
+ sexpr::SExpr s;
+ io::LineSpan span;
+ sexpr::Lexer lexer("<parse-bad>", string_pipe(bad));
+ EXPECT_FALSE(sexpr::parse(lexer, s));
+ EXPECT_EQ(lexer.peek(), sexpr::TOK_ERROR);
+ }
+}