wisp
 
(Arne Babenhauserheide)
2013-03-25: allow escaping : and _.

allow escaping : and _.

diff --git a/example.w b/example.w
--- a/example.w
+++ b/example.w
@@ -24,3 +24,10 @@ defun b : :n o
   . "second defun : with a docstring!"
   message "I am here"
   . t
+
+defun _ : \:
+__
+__ . \:
+
+\_ b
+      
\ No newline at end of file
diff --git a/wisp.py b/wisp.py
--- a/wisp.py
+++ b/wisp.py
@@ -28,6 +28,53 @@ but crave the power of lisp.
 """
 
 
+def replaceinwisp(code, string, replacement):
+    """Replace the given string with the replacement, but only in
+    indentation sensitive parts of the code.
+    
+    Essentially replace everywhere except in brackets or strings.
+    
+    :param code: Arbitrary wisp code to process.
+    :param string: A string to replace.
+    :param replacement: The replacement string.
+    
+    :return: (code, count): The new code and a count of replacements.
+    """
+    count = 0
+    instring = False
+    incomment = False
+    inbrackets = 0
+    strlen = len(string)
+    for n in range(len(code) - strlen):
+        i = code[n]
+        # comments start with a ; - but only in regular wisp code.
+        if not incomment and not instring and not inbrackets and i == ";":
+            incomment = not incomment
+        # a linebreak ends the comment
+        if incomment:
+            if i == "\n":
+                incomment = not incomment
+            # all processing stops in comments
+            continue
+        if i == '"':
+            instring = not instring
+        # all processing stops in strings
+        if instring:
+            continue
+        if i == "(":
+            inbrackets += 1
+        elif i == ")":
+            inbrackets -= 1
+        # all processing stops in brackets
+        if inbrackets:
+            continue
+        # here we do the actual replacing
+        if code[n:n+strlen] == string:
+            count += 1
+            code = code[:n] + replacement + code[n+strlen:]
+    return code, count
+
+
 class Line:
     def __init__(self, line):
         """Parse one line in which linebreaks within strings and
@@ -41,7 +88,11 @@ class Line:
             # here line[i-1] is _. Check if line[i+1] is a space.
             if line[i:i+1] == " ":
                 line = (i)*" " + line[i:]
-                
+        # \_ escapes the underscore at the beginning of a line, so you
+        # can use identifiers which only consist of underscores.
+        elif line.startswith("\_"):
+            line = "_" + line[2:]
+            
         #: prefix to go around the outer bracket: '(, ,( or `(
         self.prefix = ""
         # check if this is a continuation of the parent line
@@ -95,8 +146,22 @@ class Line:
                 if self.content[n-1:n+2] == " : " or self.content[n-1:] == " :":
                     bracketstoclose += 1
                     self.content = self.content[:n] + "(" + self.content[n+1:]
+        
+        # after the full line processing, replace " \\: " "\n\\: " and
+        # " \\:\n" (inside line, start of a line, end of a line) by "
+        # : ", "\n: " and " :\n" respectively to allow escaping : as
+        # expression.
+        self.content, count = replaceinwisp(self.content, " \\: ", " : ")
+        if self.content.startswith("\\: "):
+            self.content = ": " + self.content[3:]
+        elif self.content.endswith(" \\:"):
+            self.content = self.content[:-3] + " :"
+        elif self.content == "\\:": # empty function or variable call
+            self.content = ":"
+        
+        # add closing brackets
         self.content += ")" * bracketstoclose
-
+        
         #: Is the line effectively empty?
         self.empty = False
         onlycomment = (line.split(";")[1:] and  # there is content after the comment sign