From f06333af1b154659d5c464d3cbe878ad0ecc3d7c Mon Sep 17 00:00:00 2001
From: "Joachim Wuttke (o)" <j.wuttke@fz-juelich.de>
Date: Wed, 20 Sep 2017 10:49:12 +0200
Subject: [PATCH] word input enclosed in single quotes may contain blanks;
 coord name(unit) now considered a word, not a string

---
 pub/CHANGELOG             |  7 +++++++
 pub/ftest/oz.f2t          |  2 +-
 pub/ftest/r_of_i.f2t      |  6 ++++--
 pub/lib/coord.cpp         |  2 +-
 pub/readplus/macro.cpp    | 25 +++++++++++++++----------
 pub/trivia/string_ops.cpp | 35 ++++++++++++++++++++++++++++++++++-
 pub/trivia/string_ops.hpp | 32 ++++++++++++++++----------------
 7 files changed, 78 insertions(+), 31 deletions(-)

diff --git a/pub/CHANGELOG b/pub/CHANGELOG
index 1dfce038..7540401e 100644
--- a/pub/CHANGELOG
+++ b/pub/CHANGELOG
@@ -1,3 +1,10 @@
+Release
+
+- UI changes:
+  - word input enclosed in single quotes may contain blanks
+  - coord name(unit) now considered a word, not a string
+  - commands oz+ and or+ now ask for coord name and unit
+
 Release 2.4.0g of 11aug17:
 
 - Support error bars in Load_96 and in fe2
diff --git a/pub/ftest/oz.f2t b/pub/ftest/oz.f2t
index ac64e68c..2575b5a9 100755
--- a/pub/ftest/oz.f2t
+++ b/pub/ftest/oz.f2t
@@ -1,6 +1,6 @@
 #!/usr/bin/env frida
 fm 7 3 h
-oz+ "myz()" 100*j
+oz+ myz() 100*j
 mz- 0
 exit_unless(z0[,2]==200,"failed")
 exit(0)
\ No newline at end of file
diff --git a/pub/ftest/r_of_i.f2t b/pub/ftest/r_of_i.f2t
index 357fa219..f56fa91b 100755
--- a/pub/ftest/r_of_i.f2t
+++ b/pub/ftest/r_of_i.f2t
@@ -1,6 +1,8 @@
 #!/usr/bin/env frida
 fm 1 1 h
-or+ "my_r()" 77
+or+ my_r() 77
+or+ 'coord name with blanks(unit with blanks)' 88
 oy r0[1+i]
-exit_unless(y==77,"result_wrong")
+exit_unless(y==77,"r0_to_y_wrong")
+exit_unless(r1==88,"r1_wrong")
 exit(0)
\ No newline at end of file
diff --git a/pub/lib/coord.cpp b/pub/lib/coord.cpp
index 13161e19..622bfb55 100644
--- a/pub/lib/coord.cpp
+++ b/pub/lib/coord.cpp
@@ -56,7 +56,7 @@ void CCoord::ask_and_set(const string& quest)
         prompt += " [" + str_std() + "]";
     prompt += " ? ";
     for (;;) {
-        string resp = NMacro::readln(prompt);
+        string resp = NMacro::readwd(prompt);
         if (resp == "\\q") {
             throw S("user escape to main menu");
         } else if (resp == "") {
diff --git a/pub/readplus/macro.cpp b/pub/readplus/macro.cpp
index 33f45932..a755b498 100644
--- a/pub/readplus/macro.cpp
+++ b/pub/readplus/macro.cpp
@@ -26,15 +26,15 @@ using std::cout;
 namespace NMacro
 {
 // internals, used only in this file:
-std::deque<string> stack;
+    std::deque<string> stack;
 
 // the following are only called indirectly through macros:
-void metacmd_incl(string r);
+    void metacmd_incl(string r);
 
 // auxiliary:
-string wordexp_cpp_unique(const string& s);
-string string_extract_quoted(string* in);
-bool is_separator(char c);
+    string wordexp_cpp_unique(const string& s);
+    string string_extract_quoted(string* in);
+    bool is_separator(char c);
 }
 
 
@@ -79,6 +79,15 @@ string NMacro::readln(const string& prompt, ifstream* F, int* lineno)
 string NMacro::readwd(const string& prompt)
 {
     string in = readln(prompt);
+    if (in[0] == '\'') { // string enclosed in quotes, may contain spaces
+        string::size_type j = triv::pos_closing(in);
+        if (j+1 < in.length()) {
+            if (!is_separator(in[j+1]))
+                throw "closing quote followed by non-space character";
+            push_remainder(in.substr(j+2));
+        }
+        return in.substr(1, j-1);
+    }
     string::size_type j = in.find_first_of(" \t");
     if (j != string::npos) {
         push_remainder(in.substr(j));
@@ -128,14 +137,10 @@ string NMacro::exemac(const string& in)
 //**************************************************************************************************
 
 
-//! Push _rem_ back on the input stack.
+//! Left-trims the argument, and puts it back on the input stack.
 
 void NMacro::push_remainder(const string& rem)
 {
-    if (rem == "")
-        return; // nothing to do
-    if (!is_separator(rem[0]))
-        throw "missing separator at beginning of [" + rem + "]";
     string::size_type j = rem.find_first_not_of(" \t");
     if (j != string::npos)
         stack.push_front(rem.substr(j));
diff --git a/pub/trivia/string_ops.cpp b/pub/trivia/string_ops.cpp
index 85b0808b..b730295d 100644
--- a/pub/trivia/string_ops.cpp
+++ b/pub/trivia/string_ops.cpp
@@ -85,7 +85,7 @@ void triv::string_extract_word(const string& in, string* out1, string* out2)
 {
     string::size_type j0 = 0, j1, j2, jf = in.size();
 
-    // handle special case "`"
+    // handle special case "`" // TODO: check if needed; reimplement using pos_closing
     if (in[0] == '`') {
         j1 = in.find_first_of('`', 1);
         if (j1 == string::npos) {
@@ -151,3 +151,36 @@ vector<string> triv::split(const string& in, const string& delimiters)
     }
     return out;
 }
+
+//! For string starting with a delimiter, return position of matching delimiter.
+//! INCOMPLETE. Currently only supports '.
+//! Has unit test 003 (StrOpsTest, PosClosing).
+
+std::string::size_type triv::pos_closing(const std::string& in)
+{
+    char opener = in[0];
+    char closer = 0;
+    switch (opener) {
+    case '\'':
+        closer = '\''; break;
+        /* NOT YET SUPPORTED:
+    case '"':
+        closer = '"'; break;
+    case '(':
+        closer = ')'; break;
+    case '[':
+        closer = ']'; break;
+    case '{':
+        closer = '}'; break;
+        */
+    default:
+        throw "BUG: pos_closing("+in+"): arg does not start with supported delimiter";
+    }
+    for (string::size_type j=1; j<in.length(); ++j) {
+        if (in[j-1]=='\\')
+            continue;
+        if (in[j]==closer)
+            return j;
+    }
+    throw string("Unmatched ")+opener+" in "+in;
+}
diff --git a/pub/trivia/string_ops.hpp b/pub/trivia/string_ops.hpp
index 27dc76f0..40ac1e37 100644
--- a/pub/trivia/string_ops.hpp
+++ b/pub/trivia/string_ops.hpp
@@ -12,25 +12,25 @@
 #include <string>
 #include <vector>
 
-namespace triv
-{
+namespace triv {
 
-bool str2vec(std::string inp, std::vector<double>* V, size_t nmax = 0, bool force = true);
-void string_extract_word(const std::string& in, std::string* out1, std::string* out2);
-void string_extract_line(const std::string& in, std::string* out1, std::string* out2);
-std::vector<std::string> split(const std::string& in, const std::string& delimiters = " \t");
-std::string strip(const std::string& str, const char* sepSet = " \n");
+    bool str2vec(std::string inp, std::vector<double>* V, size_t nmax = 0, bool force = true);
+    void string_extract_word(const std::string& in, std::string* out1, std::string* out2);
+    void string_extract_line(const std::string& in, std::string* out1, std::string* out2);
+    std::vector<std::string> split(const std::string& in, const std::string& delimiters = " \t");
+    std::string strip(const std::string& str, const char* sepSet = " \n");
+    std::string::size_type pos_closing(const std::string& str);
 
-template <class T> std::string join(const std::vector<T>& v, const std::string& separator)
-{
-    std::ostringstream result;
-    for (auto i = v.begin(); i != v.end(); i++) {
-        if (i != v.begin())
-            result << separator;
-        result << *i;
+    template <class T> std::string join(const std::vector<T>& v, const std::string& separator)
+    {
+        std::ostringstream result;
+        for (auto i = v.begin(); i != v.end(); i++) {
+            if (i != v.begin())
+                result << separator;
+            result << *i;
+        }
+        return result.str();
     }
-    return result.str();
-}
 }
 
 #endif // STRING_OPS_H
-- 
GitLab