From f2e6d8ec0f745451bf001d21897de972aa9793fe Mon Sep 17 00:00:00 2001 From: rbdannenberg Date: Mon, 1 Nov 2010 03:15:39 +0000 Subject: [PATCH] Nyquist Effects now treat Nyquist code and output as UTF-8. Nyquist Prompt effect accepts both LISP and SAL (in SAL, either define function main or write return at the top level). --- nyquist/sal-parse.lsp | 18 +++-- nyquist/sal.lsp | 15 +++++ src/effects/nyquist/Nyquist.cpp | 115 +++++++++++++++++++++++++++++--- src/effects/nyquist/Nyquist.h | 5 +- 4 files changed, 136 insertions(+), 17 deletions(-) diff --git a/nyquist/sal-parse.lsp b/nyquist/sal-parse.lsp index 6a75614ba..9c7f15551 100644 --- a/nyquist/sal-parse.lsp +++ b/nyquist/sal-parse.lsp @@ -1026,7 +1026,9 @@ (if (token-is '(:define :load :chdir :variable :function ; :system :play :print :display)) - (parse-command))) + (parse-command) + (if (and (token-is '(:return)) *audacity-top-level-return-flag*) + (parse-command)))) (defun parse-command () @@ -1044,6 +1046,8 @@ (parse-print-display :print 'sal-print)) ((token-is :display) (parse-print-display :display 'display)) + ((and *audacity-top-level-return-flag* (token-is :return)) + (parse-return)) ; ((token-is :output) ; (parse-output)) (t @@ -1251,12 +1255,16 @@ (defun parse-return () (or (token-is :return) (error "parse-return internal error")) - (let (loc) - (if (null *sal-fn-name*) + (let (loc expr) + ;; this seems to be a redundant test + (if (and (null *sal-fn-name*) + (not *audacity-top-level-return-flag*)) (errexit "Return must be inside a function body")) (setf loc (parse-token)) - (add-line-info-to-stmt (list 'sal-return-from *sal-fn-name* - (parse-sexpr)) loc))) + (setf expr (parse-sexpr)) + (if *sal-fn-name* + (add-line-info-to-stmt (list 'sal-return-from *sal-fn-name* expr) loc) + (list 'defun 'main '() (add-line-info-to-stmt expr loc))))) (defun parse-load () diff --git a/nyquist/sal.lsp b/nyquist/sal.lsp index ede64d341..a26176979 100644 --- a/nyquist/sal.lsp +++ b/nyquist/sal.lsp @@ -487,6 +487,21 @@ (if *sal-traceback* (sal-traceback)) (setf *sal-call-stack* stack)) ;; clear the stack + +;; when true, top-level return statement is legal and compiled into MAIN +(setf *audacity-top-level-return-flag* nil) + +;; SAL-COMPILE-AUDACITY -- special treatment of RETURN +;; +;; This works like SAL-COMPILE, but if there is a top-level +;; return statement (not normally legal), it is compiled into +;; a function named MAIN. This is a shorthand for Audacity plug-ins +;; +(defun sal-compile-audacity (input eval-flag multiple-statements filename) + (progv '(*audacity-top-level-return-flag*) '(t) + (sal-compile input eval-flag multiple-statements filename))) + + ;; SAL-COMPILE -- translate string or token list to lisp and eval ;; ;; input is either a string or a token list diff --git a/src/effects/nyquist/Nyquist.cpp b/src/effects/nyquist/Nyquist.cpp index 6b2512001..0796f185e 100644 --- a/src/effects/nyquist/Nyquist.cpp +++ b/src/effects/nyquist/Nyquist.cpp @@ -71,6 +71,7 @@ WX_DEFINE_OBJARRAY(NyqControlArray); EffectNyquist::EffectNyquist(wxString fName) { mAction = _("Applying Nyquist Effect..."); + mInputCmd = wxEmptyString; mCmd = wxEmptyString; SetEffectFlags(HIDDEN_EFFECT); mInteractive = false; @@ -105,6 +106,18 @@ EffectNyquist::~EffectNyquist() { } +wxString EffectNyquist::NyquistToWxString(const char *nyqString) +{ + wxString str(nyqString, wxConvUTF8); + if (nyqString != NULL && nyqString[0] && str.IsEmpty()) { + // invalid UTF-8 string, convert as Latin-1 + str = _("[Warning: Nyquist returned invalid UTF-8 string, converted here as Latin-1]"); + str += LAT1CTOWX(nyqString); + } + return str; +} + + void EffectNyquist::Break() { mBreak = true; @@ -416,7 +429,7 @@ bool EffectNyquist::PromptUser() NyquistInputDialog dlog(wxGetTopLevelParent(NULL), -1, _("Nyquist Prompt"), _("Enter Nyquist Command: "), - mCmd); + mInputCmd); dlog.CentreOnParent(); int result = dlog.ShowModal(); @@ -429,7 +442,83 @@ bool EffectNyquist::PromptUser() }*/ mDebug = (result == eDebugID); - mCmd = dlog.GetCommand(); + // remember exact input in mInputCmd which will appear in the next + // NyquistInputDialog. Copy to mCmd for possible embedding in + // "function main() begin ... end": + mCmd = mInputCmd = dlog.GetCommand(); + + // Is this LISP or SAL? Both allow comments. After comments, LISP + // must begin with "(". Technically, a LISP expression could be a + // symbol or number or string, etc., but these are not really + // useful expressions. If the input begins with a symbol, number, + // or string, etc., it is more likely an erroneous attempt to type + // a SAL expression (which should probably begin with "return"), + // so we will treat it as SAL. + + // this is a state machine to scan past LISP comments and white + // space to find the first real character of LISP or SAL. Note + // that #| ... |# style comments are not valid in SAL, so we do + // not skip these. Instead, "#|" indicates LISP if found. + // + int i = 0; + bool inComment = false; // handle "; ... \n" comments + while (i < mCmd.Len()) { + if (inComment) { + inComment = (mCmd[i] != wxT('\n')); + } else if (mCmd[i] == wxT(';')) { + inComment = true; + } else if (!wxIsspace(mCmd[i])) { + break; // found the first non-comment, non-space character + } + i++; + } + + // invariant: i == mCmd.Len() | + // mCmd[i] is first non-comment, non-space character + + mIsSal = false; + mCmd = mCmd.Mid(i); // remove initial comments + if (mCmd.Len() > 0 && mCmd[0] != wxT('(') && mCmd[0] != wxT('#')) { + mIsSal = true; + wxString cmdUp = mCmd.Upper(); + int returnLoc = cmdUp.Find(wxT("RETURN")); + if (returnLoc == wxNOT_FOUND) { + wxMessageBox(_("Your code looks like SAL syntax, but there is no return statement. Either use a return statement such as\n\treturn s * 0.1\nfor SAL, or begin with an open parenthesis such as\n\t(mult s 0.1)\n for LISP."), _("Error in Nyquist code"), wxOK | wxCENTRE); + return false; + } + /* + // Allow two forms of SAL "expressions": + // 1) a bunch of statements that do not define "main" followed by + // "return ...": wrap the return statement in main + // 2) a bunch of statements that include a definition of "main": + // return the code as is + // This allows a simple input of the form "return " + // but since this does not match the syntax of a real SAL plug-in, + // we want to allow user to define "main" + + // Search for "function main". This might be fooled if user puts + // "function main" inside a string or comment + bool definesMain = false; + int loc = cmdUp.Find(wxT("FUNCTION")); + while (loc != wxNOT_FOUND) { + // remove everything up to FUNCTION and additional white space + cmdUp = cmdUp.Mid(loc + 8).Trim(false); + // see if the function name is MAIN + if (cmdUp.StartsWith(wxT("MAIN"))) { + definesMain = true; + break; + } + loc = cmdUp.Find(wxT("FUNCTION")); // look for next definition + } + if (loc == wxNOT_FOUND) { + // replace the LAST return + returnLoc = FindFromEnd(mCmd, wxT("RETURN")); + + // wrap Sal statements in a function (main) + mCmd = mCmd.Prepend(wxT("function main() begin\n")); + mCmd += wxT("\nend\n"); + */ + } return true; } @@ -601,7 +690,7 @@ bool EffectNyquist::Process() NyquistOutputDialog dlog(mParent, -1, _("Nyquist"), _("Nyquist Output: "), - wxString(mDebugOutput.c_str(), wxConvISO8859_1)); + NyquistToWxString(mDebugOutput.c_str())); dlog.CentreOnParent(); dlog.ShowModal(); } @@ -657,9 +746,12 @@ bool EffectNyquist::ProcessOne() wxString str = mControls[j].valStr; str.Replace(wxT("\\"), wxT("\\\\")); str.Replace(wxT("\""), wxT("\\\"")); - cmd += wxString::Format(wxT("(setf %s \"%s\")\n"), - mControls[j].var.c_str(), - str.c_str()); + cmd += wxT("(setf "); + // restrict variable names to 7-bit ASCII: + cmd += mControls[j].var.c_str(); + cmd += wxT(" \""); + cmd += str; // unrestricted value will become quoted UTF-8 + cmd += wxT("\")\n"); } } @@ -672,7 +764,7 @@ bool EffectNyquist::ProcessOne() } cmd += wxT("(setf *sal-call-stack* nil)\n"); - cmd += wxT("(sal-compile \"\n") + str + wxT("\n\" t t nil)\n"); + cmd += wxT("(sal-compile-audacity \"\n") + str + wxT("\n\" t t nil)\n"); cmd += wxT("(main)\n"); } else { @@ -684,11 +776,12 @@ bool EffectNyquist::ProcessOne() mCurBuffer[i] = NULL; } - rval = nyx_eval_expression(cmd.mb_str()); + rval = nyx_eval_expression(cmd.mb_str(wxConvUTF8)); if (rval == nyx_string) { - wxMessageBox(wxString(nyx_get_string(), wxConvISO8859_1), wxT("Nyquist"), - wxOK | wxCENTRE, mParent); + wxMessageBox(NyquistToWxString(nyx_get_string()), + wxT("Nyquist"), + wxOK | wxCENTRE, mParent); return true; } @@ -734,7 +827,7 @@ bool EffectNyquist::ProcessOne() nyx_get_label(l, &t0, &t1, &str); - ltrack->AddLabel(t0 + mT0, t1 + mT0, LAT1CTOWX(str)); + ltrack->AddLabel(t0 + mT0, t1 + mT0, UTF8CTOWX(str)); } return true; } diff --git a/src/effects/nyquist/Nyquist.h b/src/effects/nyquist/Nyquist.h index 80823c5d1..b8fb5fb00 100644 --- a/src/effects/nyquist/Nyquist.h +++ b/src/effects/nyquist/Nyquist.h @@ -118,6 +118,8 @@ class AUDACITY_DLL_API EffectNyquist:public Effect private: + static wxString NyquistToWxString(const char *nyqString); + bool ProcessOne(); static int StaticGetCallback(float *buffer, int channel, @@ -161,7 +163,8 @@ class AUDACITY_DLL_API EffectNyquist:public Effect */ bool mInteractive; bool mOK; - wxString mCmd; + wxString mInputCmd; // history: exactly what the user typed + wxString mCmd; // the command to be processed wxString mName; ///< Name of the Effect wxString mAction; wxString mInfo;