【问题标题】:Eliminate hyphen from AppleScript's text item delimiters从 AppleScript 的文本项分隔符中消除连字符
【发布时间】:2016-02-21 16:27:52
【问题描述】:

我正在用 AppleScript 编写一个计算器(请不要告诉我我不应该这样做,我知道 AppleScript 不是为此而设计的)。但是当涉及到方程式时,我在将单词彼此分开时遇到了问题,因为如果涉及到负变量,我就无法将方程式分成两部分。

set function to "-3x"
return word 1 of function

这将返回“3x”,因为连字符是文本项分隔符,但我希望它返回“-3x”。有什么方法可以从文本项分隔符中删除连字符,或者有什么其他方法可以将连字符包含到字符串中?

非常感谢!!

【问题讨论】:

    标签: applescript delimiter hyphen


    【解决方案1】:

    给出一个想法,这里是一个非常简单的类 Lisp 语言的简单标记器:

    -- token types
    property StartList : "START"
    property EndList : "END"
    property ANumber : "NUMBER"
    property AWord : "WORD"
    
    -- recognized token chars
    property _startlist : "("
    property _endlist : ")"
    property _number : "+-.1234567890"
    property _word : "abcdefghijklmnopqrstuvwxyz"
    property _whitespace : space & tab & linefeed & return
    
    
    to tokenizeCode(theCode)
        considering diacriticals, hyphens, punctuation and white space but ignoring case and numeric strings
            set i to 1
            set l to theCode's length
            set tokensList to {}
            repeat while i ≤ l
                set c to character i of theCode
                if c is _startlist then
                    set end of tokensList to {tokenType:StartList, tokenText:c}
                    set i to i + 1
                else if c is _endlist then
                    set end of tokensList to {tokenType:EndList, tokenText:c}
                    set i to i + 1
                else if c is in _number then
                    set tokenText to ""
                    repeat while character i of theCode is in _number and i ≤ l
                        set tokenText to tokenText & character i of theCode
                        set i to i + 1
                    end repeat
                    set end of tokensList to {tokenType:ANumber, tokenText:tokenText}
                else if c is in _word then
                    set tokenText to ""
                    repeat while character i of theCode is in _word and i ≤ l
                        set tokenText to tokenText & character i of theCode
                        set i to i + 1
                    end repeat
                    set end of tokensList to {tokenType:AWord, tokenText:tokenText}
                else if c is in _whitespace then -- skip over white space
                    repeat while character i of theCode is in _whitespace and i ≤ l
                        set i to i + 1
                    end repeat
                else
                    error "Unknown character: '" & c & "'"
                end if
            end repeat
            return tokensList
        end considering
    end tokenizeCode
    

    该语言的语法规则如下:

    • 数字表达式包含一位或多位数字、“+”或“-”符号和/或小数点。 (上面的代码目前不检查令牌是否是一个有效的数字,例如它会很乐意接受像“0.1.2-3+”这样的无意义的输入,但这很容易添加。)

    • 单词表达式包含一个或多个字符 (a-z)。

    • 列表表达式以“(”开头,以“)”结尾。列表表达式中的第一个标记必须是要应用的运算符的名称; this 后面可以跟零个或多个表示其操作数的附加表达式。

    • 任何无法识别的字符都被视为错误。

    例如,让我们用它来标记数学表达式“3 + (2.5 * -2)”,它在前缀符号中是这样写的:

    set programText to "(add 3 (multiply 2.5 -2))"
    
    set programTokens to tokenizeCode(programText)
    
    --> {{tokenType:"START", tokenText:"("}, 
         {tokenType:"WORD", tokenText:"add"}, 
         {tokenType:"NUMBER", tokenText:"3"}, 
         {tokenType:"START", tokenText:"("}, 
         {tokenType:"WORD", tokenText:"multiply"}, 
         {tokenType:"NUMBER", tokenText:"2.5"}, 
         {tokenType:"NUMBER", tokenText:"-2"}, 
         {tokenType:"END", tokenText:")"}, 
         {tokenType:"END", tokenText:")"}}
    

    一旦文本被分割成一个标记列表,下一步就是将该列表输入一个解析器,该解析器将其组装成一个抽象语法树,该树完全描述了程序的结构

    就像我说的那样,这些东西有一点学习曲线,但是一旦你掌握了基本原则,你就可以在睡梦中写出来。问一下,我稍后会添加一个示例,说明如何将这些标记解析为可用的形式。

    【讨论】:

      【解决方案2】:

      从前面开始,这里有一个解析器,它将标记器的输出转换为描述程序逻辑的基于树的数据结构。

      -- token types
      property StartList : "START"
      property EndList : "END"
      property ANumber : "NUMBER"
      property AWord : "WORD"
      
      
      -------
      -- handlers called by Parser to construct Abstract Syntax Tree nodes,
      -- simplified here for demonstration purposes
      
      to makeOperation(operatorName, operandsList)
          return {operatorName:operatorName, operandsList:operandsList}
      end makeOperation
      
      to makeWord(wordText)
          return wordText
      end makeWord
      
      to makeNumber(numberText)
          return numberText as number
      end makeNumber
      
      
      -------
      -- Parser
      
      to makeParser(programTokens)
          script ProgramParser
      
              property currentToken : missing value
      
              to advanceToNextToken()
                  if programTokens is {} then error "Found unexpected end of program after '" & currentToken & "'."
                  set currentToken to first item of programTokens
                  set programTokens to rest of programTokens
                  return
              end advanceToNextToken
      
              --
      
              to parseOperation() -- parses an '(OPERATOR [OPERANDS ...])' list expression
                  advanceToNextToken()
                  if currentToken's tokenType is AWord then -- parse 'OPERATOR'
                      set operatorName to currentToken's tokenText
                      set operandsList to {}
                      advanceToNextToken()
                      repeat while currentToken's tokenType is not EndList -- parse 'OPERAND(S)'
                          if currentToken's tokenType is StartList then
                              set end of operandsList to parseOperation()
                          else if currentToken's tokenType is AWord then
                              set end of operandsList to makeWord(currentToken's tokenText)
                          else if currentToken's tokenType is ANumber then
                              set end of operandsList to makeNumber(currentToken's tokenText)
                          else
                              error "Expected word, number, or list expression but found '" & currentToken's tokenText & "' instead."
                          end if
                          advanceToNextToken()
                      end repeat
                      return makeOperation(operatorName, operandsList)
                  else
                      error "Expected operator name but found '" & currentToken's tokenText & "' instead."
                  end if
              end parseOperation
      
              to parseProgram() -- parses the entire program
                  advanceToNextToken()
                  if currentToken's tokenType is StartList then
                      return parseOperation()
                  else
                      error "Found unexpected '" & currentToken's tokenText & "' at start of program."
                  end if
              end parseProgram
      
          end script
      end makeParser
      
      
      -------
      -- parse the tokens list produced by the tokenizer into an Abstract Syntax Tree
      
      set programTokens to {{tokenType:"START", tokenText:"("}, ¬
          {tokenType:"WORD", tokenText:"add"}, ¬
          {tokenType:"NUMBER", tokenText:"3"}, ¬
          {tokenType:"START", tokenText:"("}, ¬
          {tokenType:"WORD", tokenText:"multiply"}, ¬
          {tokenType:"NUMBER", tokenText:"2.5"}, ¬
          {tokenType:"NUMBER", tokenText:"-2"}, ¬
          {tokenType:"END", tokenText:")"}, ¬
          {tokenType:"END", tokenText:")"}}
      
      
      set parserObject to makeParser(programTokens)
      
      set abstractSyntaxTree to parserObject's parseProgram()
      --> {operatorName:"add", operandsList:{3, {operatorName:"multiply", operandsList:{2.5, -2}}}}
      

      ProgramParser 对象是一个非常非常简单的递归下降解析器,它是一个处理程序的集合,每个处理程序都知道如何将一系列标记转换为特定的数据结构。事实上,这里使用的 Lisp-y 语法非常简单,它实际上只需要两个处理程序:parseProgram,它让一切都在进行中,parseOperation,它知道如何读取组成 (OPERATOR_NAME [OPERAND1 OPERAND2 ...]) 列表的标记和将其转换为描述要执行的单个操作(加法、乘法等)的记录。

      AST 的好处是,尤其是像这样非常简单的常规 AST,您可以将其作为数据进行操作。例如,给定程序 (multiply x y)y = (add x 1) 的定义,您可以遍历 AST 并将任何提及的 y 替换为其定义,在这种情况下给出 (multiply x (add x 1))。也就是说,您不仅可以进行算术计算(算法编程),还可以进行代数运算(符号编程)。这对这里来说有点令人兴奋,但稍后我会看到如何组合一个简单的算术评估器。

      【讨论】:

        【解决方案3】:

        最后,下面是解析器输出的简单求值器:

        to makeOperation(operatorName, operandsList)
            if operatorName is "add" then
                script AddOperationNode
                    to eval(env)
                        if operandsList's length ≠ 2 then error "Wrong number of operands."
                        return ((operandsList's item 1)'s eval(env)) + ((operandsList's item 2)'s eval(env))
                    end eval
                end script
            else if operatorName is "multiply" then
                script MultiplyOperationNode
                    to eval(env)
                        if operandsList's length ≠ 2 then error "Wrong number of operands."
                        return ((operandsList's item 1)'s eval(env)) * ((operandsList's item 2)'s eval(env))
                    end eval
                end script
            -- define more operations here as needed...
            else
                error "Unknown operator: '" & operatorName & "'"
            end if
        end makeOperation
        
        
        to makeWord(wordText)
            script WordNode
                to eval(env)
                    return env's getValue(wordText)'s eval(env)
                end eval
            end script
        end makeWord
        
        
        to makeNumber(numberText)
            script NumberNode
                to eval(env)
                    return numberText as number
                end eval
            end script
        end makeNumber
        
        
        to makeEnvironment()
            script EnvironmentObject
                property _storedValues : {}
                --
                to setValue(theKey, theValue)
                    -- theKey : text
                    -- theValue : script
                    repeat with aRef in _storedValues
                        if aRef's k is theKey then
                            set aRef's v to theValue
                            return
                        end if
                    end repeat
                    set end of _storedValues to {k:theKey, v:theValue}
                    return
                end setValue
                --
                to getValue(theKey)
                    repeat with aRef in _storedValues
                        if aRef's k is theKey then return aRef's v
                    end repeat
                    error "'" & theKey & "' is undefined." number -1728
                end getValue
                --
            end script
        end makeEnvironment
        
        
        to runProgram(programText, theEnvironment)
            set programTokens to tokenizeCode(programText)
            set abstractSyntaxTree to makeParser(programTokens)'s parseProgram()
            return abstractSyntaxTree's eval(theEnvironment)
        end runProgram
        

        这用新的处理程序替换了用于测试解析器的make... 处理程序,这些处理程序构造了代表可以构成抽象语法树的每种结构类型的完整对象:数字、单词和操作。每个对象都定义了一个eval 处理程序,它知道如何评估该特定结构:在NumberNode 中它只返回数字,在WordNode 中它检索并评估存储在该名称下的结构,在AddOperationNode 它计算每个操作数,然后对它们求和,依此类推。

        例如,评估我们原来的3 + 2.5 * -2 程序:

        set theEnvironment to makeEnvironment()
        runProgram("(add 3 (multiply 2.5 -2))", theEnvironment)
        --> -2.0
        

        此外,EnvironmentObject 用于存储命名值。例如,存储一个名为 "x" 的值以供程序使用:

        set theEnvironment to makeEnvironment()
        theEnvironment's setValue("x", makeNumber(5))
        runProgram("(add 3 x)", theEnvironment)
        --> 8
        

        显然,这需要更多的工作才能使其成为一个合适的计算器:一整套运算符定义、更好的错误报告等等。另外,您可能希望用更熟悉的中缀语法替换带括号的前缀语法,为此您需要像 Pratt 解析器这样可以处理优先级、关联等的东西。但是一旦您掌握了基础知识,它就可以了阅读各种技术并逐一进行更改和改进,直到找到所需的解决方案。 HTH。

        【讨论】:

          【解决方案4】:

          如果您愿意,您可以使用 AppleScript 编写计算器,但您需要像使用任何其他语言一样编写:1. 使用标记器将输入文本拆分为标记列表,2. 输入这些标记解析器将它们组装成抽象语法树,然后 3. 评估该树以产生结果。

          对于您正在做的事情,您可以将标记器编写为正则表达式(假设您不介意通过 AppleScript-ObjC 桥接至 NSRegularExpression)。对于解析,我建议阅读 Pratt 解析器,它们易于实现但功能强大,可以支持前缀、中缀和后缀运算符以及运算符优先级。对于评估,一个简单的递归 AST 步行算法可能就足够了,但一次一步。

          这些都是很好解决的问题,因此您可以轻松找到有关如何操作的教程和其他在线信息。 (当然有很多废话,所以要准备好花一些时间弄清楚如何分辨好坏。)

          您的一个问题是您不会专门为 AppleScript 编写,因此请准备好探索围绕其他语言(Python、Java 等)编写的材料,然后自己将其翻译成 AS。这将需要一些努力和耐心来学习所有程序员的语言,但非常可行(我最初对 AppleScript 进行了尝试,现在编写了我自己的自动化脚本语言)并且是一个很好的学习练习,可以培养你的技能。

          【讨论】:

          • 首先感谢您的回答。不过,这听起来真的很复杂。是否可以从 AppleScript 的文本项分隔符列表中“阻止”一个字符(如果有的话)?
          • 获取word 元素与 TID 无关。 AS 确定字边界的规则是不透明的,尽管可能基于 Unicode 标准;无论如何,您无法更改它们。至于复杂性,那都是相对的。事实上,写一个计算器并不是一项琐碎的练习。也就是说,这是一个以前经常做的事情,你会很容易地找到大量的帮助来一步一步地引导你。如果您想要更轻松的练习,请考虑使用玩具“Lisp”解释器。这只需要一个非常简单的扫描仪来检测括号、空格和其他所有内容。
          猜你喜欢
          • 2017-12-29
          • 1970-01-01
          • 1970-01-01
          • 2019-05-15
          • 2020-02-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-12-23
          相关资源
          最近更新 更多