From 57b0a419f702671276baa885340dbb4842c84492 Mon Sep 17 00:00:00 2001 From: sloum Date: Mon, 6 Sep 2021 15:40:15 -0700 Subject: [PATCH] Fixes issues with string unescaping re: literal backslashes --- README.md | 3 ++- helpers.go | 7 ++++++- lexer.go | 11 ++++++----- lib.go | 17 +++++++++++++++++ main.go | 2 +- static.go | 1 + 6 files changed, 33 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 44d1adf..4946221 100644 --- a/README.md +++ b/README.md @@ -208,7 +208,7 @@ So... Implemented: -`length`, `cons`, `car`, `cdr`, `append`, `list`, `map`, `for-each`, `list->string`, `list-ref`, `list-sort`, `reverse`, `assoc`, `member?` +`length`, `cons`, `car`, `cdr`, `append`, `list`, `map`, `for-each`, `list->string`, `list-ref`, `list-sort`, `reverse`, `assoc`, `member?`, `list-seed` - `list` can be shorthanded by using square brackets: `[1 2 3]` vs `(list 1 2 3)` - `length` and `reverse` will take a string or a list. @@ -233,6 +233,7 @@ Implemented:
  • (reverse [list|string]): list
  • (assoc [assoc-list] [key: value] [[value: value]]): value
  • (member? [list] [value]): bool
  • +
  • (list-seed [length: number] [value]): list More efficient than recursion for building large lists and wont run into stack overflows
  • diff --git a/helpers.go b/helpers.go index dc10a72..d9583ad 100644 --- a/helpers.go +++ b/helpers.go @@ -158,6 +158,7 @@ func unescapeString(s string) string { var slash bool for _, c := range []rune(s) { + fmt.Printf("%c\n", c) if slash && !altNum { switch c { case 't': @@ -172,6 +173,8 @@ func unescapeString(s string) string { out.WriteRune('\a') case 'b': out.WriteRune('\b') + case '\\': + out.WriteRune('\\') case 'f': out.WriteRune('\f') case '0': @@ -522,6 +525,7 @@ func createTimeFormatString(s string) string { func stringParensMatch(s string) bool { count := 0 inString := false + prevPrev := rune(0) prev := ' ' for _, c := range s { @@ -537,10 +541,11 @@ func stringParensMatch(s string) bool { case '"': if !inString { inString = true - } else if prev != '\\' { + } else if prev != '\\' || (prev == '\\' && prevPrev == '\\') { inString = false } } + prevPrev = c prev = c } diff --git a/lexer.go b/lexer.go index f4f7152..3010d36 100644 --- a/lexer.go +++ b/lexer.go @@ -41,19 +41,20 @@ func eatWhiteSpace(r *strings.Reader) { func eatString(r *strings.Reader) string { var buf strings.Builder buf.WriteRune('"') + prevPrev := rune(0) previous := '"' for r.Len() > 0 { c, _, err := r.ReadRune() if err != nil { panic("Lexing error while eating string") } - if c == '\n' || (c != '"' && r.Len() == 0) { + buf.WriteRune(c) + if c == '"' && (previous != '\\' || previous == '\\' && prevPrev == '\\') { + break + } else if c == '\n' || (c != '"' && r.Len() == 0) { panic(fmt.Sprintf("Parse error: Unclosed string on line %d ", lexLine)) } - buf.WriteRune(c) - if c == '"' && previous != '\\' { - break - } + prevPrev = previous previous = c } return buf.String() diff --git a/lib.go b/lib.go index 16f4b17..0a7b8bb 100644 --- a/lib.go +++ b/lib.go @@ -732,6 +732,23 @@ var stdLibrary = vars{ } return strings.Join(sSlice, joinOn) }, + "list-seed": func(a ...expression) expression { + if len(a) < 2 { + return exception("insufficient number of arguments given to 'list-seed', expected number and value") + } + count, ok := a[0].(number) + if !ok { + return exception("'list-seed' expected a number as its first argument, but was given a non-number value") + } + if count < 1 { + return exception("'list-seed' expects a positive non-zero number as its first argument, a number less than 1 was given") + } + l := make([]expression, int(count)) + for i := range l { + l[i] = a[1] // TODO may need to do a deep copy for slices that are passed this way + } + return l + }, "list-sort": func(a ...expression) expression { if len(a) == 0 { return exception("insufficient number of arguments given to 'list-sort', expected a list") diff --git a/main.go b/main.go index e40c5f8..950d5df 100644 --- a/main.go +++ b/main.go @@ -49,7 +49,7 @@ func String(v expression, rawString bool) string { if rawString { return fmt.Sprintf("\"%s\"", escapeString(v)) } - return unescapeString(v) + return v case exception: return string(v) case bool: diff --git a/static.go b/static.go index 91ffb45..7be2607 100644 --- a/static.go +++ b/static.go @@ -76,6 +76,7 @@ var usageStrings = map[string]string{ "list": "(list [value...]) => list", "list?": "(list? [value]) => bool", "list-ref": "(list-ref [list] [index: number]) => value\n\nList indexing starts at 0", + "list-seed": "(list-seed [length: number] [value]) => list\n\nUsing list seed to create lists filled with a certain element is more efficient than recursion for large lists and will not overflow the stack\n\n`length` should be a positive number larger than zero. If a floating point number is given, it will be floored", "list-sort": "(list-sort [list] [[sub-list-index: number]]) => list\n\nIf `list` is a list of lists, sorting can be done by the index value of a sublist if desired. `list-sort` always sorts the list in ascending order, but can be reversed with `reverse`", "list->string": "(list->string [list] [[join-on: string]]) => string", "load": "(load [filepath: string...]) => symbol\n\n`load` will open the file represented by filepath and evaluate its contents as slope . It will run any within the file. `load` can accept a relative reference, which will be treated as relative to the current working directory. Any `define` statements within the laoded file will result in the symbol getting added to the global environment",