This commit is contained in:
retoor 2026-01-26 10:59:07 +01:00
parent 9c04068eac
commit 264712dcbc
26 changed files with 663 additions and 394 deletions

23
src/module/html.wren vendored
View File

@ -31,31 +31,31 @@ class Html {
}
static decodeUtf8_(bytes) {
var result = ""
var parts = []
var i = 0
while (i < bytes.count) {
var b = bytes[i]
if (b < 128) {
result = result + String.fromCodePoint(b)
parts.add(String.fromCodePoint(b))
i = i + 1
} else if ((b & 0xE0) == 0xC0 && i + 1 < bytes.count) {
var cp = ((b & 0x1F) << 6) | (bytes[i + 1] & 0x3F)
result = result + String.fromCodePoint(cp)
parts.add(String.fromCodePoint(cp))
i = i + 2
} else if ((b & 0xF0) == 0xE0 && i + 2 < bytes.count) {
var cp = ((b & 0x0F) << 12) | ((bytes[i + 1] & 0x3F) << 6) | (bytes[i + 2] & 0x3F)
result = result + String.fromCodePoint(cp)
parts.add(String.fromCodePoint(cp))
i = i + 3
} else if ((b & 0xF8) == 0xF0 && i + 3 < bytes.count) {
var cp = ((b & 0x07) << 18) | ((bytes[i + 1] & 0x3F) << 12) | ((bytes[i + 2] & 0x3F) << 6) | (bytes[i + 3] & 0x3F)
result = result + String.fromCodePoint(cp)
parts.add(String.fromCodePoint(cp))
i = i + 4
} else {
result = result + String.fromCodePoint(0xFFFD)
parts.add(String.fromCodePoint(0xFFFD))
i = i + 1
}
}
return result
return parts.join("")
}
static urldecode(string) {
@ -65,20 +65,21 @@ class Html {
static slugify(string) {
if (!(string is String)) Fiber.abort("Argument must be a string.")
var result = ""
var parts = []
var prevHyphen = true
for (c in string.bytes) {
if ((c >= 65 && c <= 90)) {
result = result + String.fromCodePoint(c + 32)
parts.add(String.fromCodePoint(c + 32))
prevHyphen = false
} else if ((c >= 97 && c <= 122) || (c >= 48 && c <= 57)) {
result = result + String.fromCodePoint(c)
parts.add(String.fromCodePoint(c))
prevHyphen = false
} else if (!prevHyphen) {
result = result + "-"
parts.add("-")
prevHyphen = true
}
}
var result = parts.join("")
if (result.count > 0 && result[-1] == "-") {
result = result[0..-2]
}

View File

@ -35,31 +35,31 @@ static const char* htmlModuleSource =
" }\n"
"\n"
" static decodeUtf8_(bytes) {\n"
" var result = \"\"\n"
" var parts = []\n"
" var i = 0\n"
" while (i < bytes.count) {\n"
" var b = bytes[i]\n"
" if (b < 128) {\n"
" result = result + String.fromCodePoint(b)\n"
" parts.add(String.fromCodePoint(b))\n"
" i = i + 1\n"
" } else if ((b & 0xE0) == 0xC0 && i + 1 < bytes.count) {\n"
" var cp = ((b & 0x1F) << 6) | (bytes[i + 1] & 0x3F)\n"
" result = result + String.fromCodePoint(cp)\n"
" parts.add(String.fromCodePoint(cp))\n"
" i = i + 2\n"
" } else if ((b & 0xF0) == 0xE0 && i + 2 < bytes.count) {\n"
" var cp = ((b & 0x0F) << 12) | ((bytes[i + 1] & 0x3F) << 6) | (bytes[i + 2] & 0x3F)\n"
" result = result + String.fromCodePoint(cp)\n"
" parts.add(String.fromCodePoint(cp))\n"
" i = i + 3\n"
" } else if ((b & 0xF8) == 0xF0 && i + 3 < bytes.count) {\n"
" var cp = ((b & 0x07) << 18) | ((bytes[i + 1] & 0x3F) << 12) | ((bytes[i + 2] & 0x3F) << 6) | (bytes[i + 3] & 0x3F)\n"
" result = result + String.fromCodePoint(cp)\n"
" parts.add(String.fromCodePoint(cp))\n"
" i = i + 4\n"
" } else {\n"
" result = result + String.fromCodePoint(0xFFFD)\n"
" parts.add(String.fromCodePoint(0xFFFD))\n"
" i = i + 1\n"
" }\n"
" }\n"
" return result\n"
" return parts.join(\"\")\n"
" }\n"
"\n"
" static urldecode(string) {\n"
@ -69,20 +69,21 @@ static const char* htmlModuleSource =
"\n"
" static slugify(string) {\n"
" if (!(string is String)) Fiber.abort(\"Argument must be a string.\")\n"
" var result = \"\"\n"
" var parts = []\n"
" var prevHyphen = true\n"
" for (c in string.bytes) {\n"
" if ((c >= 65 && c <= 90)) {\n"
" result = result + String.fromCodePoint(c + 32)\n"
" parts.add(String.fromCodePoint(c + 32))\n"
" prevHyphen = false\n"
" } else if ((c >= 97 && c <= 122) || (c >= 48 && c <= 57)) {\n"
" result = result + String.fromCodePoint(c)\n"
" parts.add(String.fromCodePoint(c))\n"
" prevHyphen = false\n"
" } else if (!prevHyphen) {\n"
" result = result + \"-\"\n"
" parts.add(\"-\")\n"
" prevHyphen = true\n"
" }\n"
" }\n"
" var result = parts.join(\"\")\n"
" if (result.count > 0 && result[-1] == \"-\") {\n"
" result = result[0..-2]\n"
" }\n"

View File

@ -64,6 +64,10 @@ class HttpResponse {
_statusCode = statusCode
_statusText = statusText
_headers = headers
_normalizedHeaders = {}
for (key in headers.keys) {
_normalizedHeaders[Url.toLower_(key)] = headers[key]
}
_body = body
}
@ -75,10 +79,7 @@ class HttpResponse {
header(name) {
var lower = Url.toLower_(name)
for (entry in _headers) {
if (Url.toLower_(entry.key) == lower) return entry.value
}
return null
return _normalizedHeaders.containsKey(lower) ? _normalizedHeaders[lower] : null
}
json {

View File

@ -68,6 +68,10 @@ static const char* httpModuleSource =
" _statusCode = statusCode\n"
" _statusText = statusText\n"
" _headers = headers\n"
" _normalizedHeaders = {}\n"
" for (key in headers.keys) {\n"
" _normalizedHeaders[Url.toLower_(key)] = headers[key]\n"
" }\n"
" _body = body\n"
" }\n"
"\n"
@ -79,10 +83,7 @@ static const char* httpModuleSource =
"\n"
" header(name) {\n"
" var lower = Url.toLower_(name)\n"
" for (entry in _headers) {\n"
" if (Url.toLower_(entry.key) == lower) return entry.value\n"
" }\n"
" return null\n"
" return _normalizedHeaders.containsKey(lower) ? _normalizedHeaders[lower] : null\n"
" }\n"
"\n"
" json {\n"

20
src/module/jinja.wren vendored
View File

@ -1612,7 +1612,7 @@ class Parser {
parseRaw_() {
consumeBlockEnd_()
var content = ""
var parts = []
var bracePercent = "{\%"
var percentBrace = " \%}"
var doubleBrace = "{{"
@ -1628,19 +1628,20 @@ class Parser {
if (nextName.type == Token.NAME && nextName.value == "endraw") {
advance_()
consumeBlockEnd_()
return RawNode.new(content)
return RawNode.new(parts.join(""))
}
content = content + bracePercent
parts.add(bracePercent)
while (!isAtEnd_() && peek_().type != Token.BLOCK_END && peek_().type != Token.BLOCK_END_TRIM) {
var t = peek_()
if (t.value != null) {
content = content + " " + t.value.toString
parts.add(" ")
parts.add(t.value.toString)
}
advance_()
}
if (!isAtEnd_()) {
content = content + percentBrace
parts.add(percentBrace)
advance_()
}
continue
@ -1648,23 +1649,24 @@ class Parser {
if (token.type == Token.VAR_START || token.type == Token.VAR_START_TRIM) {
advance_()
content = content + doubleBrace
parts.add(doubleBrace)
while (!isAtEnd_() && peek_().type != Token.VAR_END && peek_().type != Token.VAR_END_TRIM) {
var t = peek_()
if (t.value != null) {
content = content + " " + t.value.toString
parts.add(" ")
parts.add(t.value.toString)
}
advance_()
}
if (!isAtEnd_()) {
content = content + doubleBraceEnd
parts.add(doubleBraceEnd)
advance_()
}
continue
}
if (token.type == Token.TEXT) {
content = content + token.value
parts.add(token.value)
advance_()
continue
}

View File

@ -1616,7 +1616,7 @@ static const char* jinjaModuleSource =
"\n"
" parseRaw_() {\n"
" consumeBlockEnd_()\n"
" var content = \"\"\n"
" var parts = []\n"
" var bracePercent = \"{\\%\"\n"
" var percentBrace = \" \\%}\"\n"
" var doubleBrace = \"{{\"\n"
@ -1632,19 +1632,20 @@ static const char* jinjaModuleSource =
" if (nextName.type == Token.NAME && nextName.value == \"endraw\") {\n"
" advance_()\n"
" consumeBlockEnd_()\n"
" return RawNode.new(content)\n"
" return RawNode.new(parts.join(\"\"))\n"
" }\n"
"\n"
" content = content + bracePercent\n"
" parts.add(bracePercent)\n"
" while (!isAtEnd_() && peek_().type != Token.BLOCK_END && peek_().type != Token.BLOCK_END_TRIM) {\n"
" var t = peek_()\n"
" if (t.value != null) {\n"
" content = content + \" \" + t.value.toString\n"
" parts.add(\" \")\n"
" parts.add(t.value.toString)\n"
" }\n"
" advance_()\n"
" }\n"
" if (!isAtEnd_()) {\n"
" content = content + percentBrace\n"
" parts.add(percentBrace)\n"
" advance_()\n"
" }\n"
" continue\n"
@ -1652,23 +1653,24 @@ static const char* jinjaModuleSource =
"\n"
" if (token.type == Token.VAR_START || token.type == Token.VAR_START_TRIM) {\n"
" advance_()\n"
" content = content + doubleBrace\n"
" parts.add(doubleBrace)\n"
" while (!isAtEnd_() && peek_().type != Token.VAR_END && peek_().type != Token.VAR_END_TRIM) {\n"
" var t = peek_()\n"
" if (t.value != null) {\n"
" content = content + \" \" + t.value.toString\n"
" parts.add(\" \")\n"
" parts.add(t.value.toString)\n"
" }\n"
" advance_()\n"
" }\n"
" if (!isAtEnd_()) {\n"
" content = content + doubleBraceEnd\n"
" parts.add(doubleBraceEnd)\n"
" advance_()\n"
" }\n"
" continue\n"
" }\n"
"\n"
" if (token.type == Token.TEXT) {\n"
" content = content + token.value\n"
" parts.add(token.value)\n"
" advance_()\n"
" continue\n"
" }\n"

View File

@ -10,9 +10,7 @@ class Json {
static stringify(value, indent) {
var indentStr = ""
if (indent is Num && indent > 0) {
for (i in 0...indent) {
indentStr = indentStr + " "
}
indentStr = " " * indent
} else if (indent is String) {
indentStr = indent
}

View File

@ -14,9 +14,7 @@ static const char* jsonModuleSource =
" static stringify(value, indent) {\n"
" var indentStr = \"\"\n"
" if (indent is Num && indent > 0) {\n"
" for (i in 0...indent) {\n"
" indentStr = indentStr + \" \"\n"
" }\n"
" indentStr = \" \" * indent\n"
" } else if (indent is String) {\n"
" indentStr = indent\n"
" }\n"

View File

@ -467,7 +467,7 @@ class Markdown {
}
static processCode_(text) {
var result = ""
var parts = []
var i = 0
var chars = text.toList
while (i < chars.count) {
@ -477,152 +477,169 @@ class Markdown {
end = end + 1
}
if (end < chars.count) {
var code = ""
var codeChars = []
for (j in (i + 1)...end) {
code = code + chars[j]
codeChars.add(chars[j])
}
result = result + "<code>" + code + "</code>"
parts.add("<code>")
parts.add(codeChars.join(""))
parts.add("</code>")
i = end + 1
} else {
result = result + chars[i]
parts.add(chars[i])
i = i + 1
}
} else {
result = result + chars[i]
parts.add(chars[i])
i = i + 1
}
}
return result
return parts.join("")
}
static processBold_(text) {
var result = text
while (result.contains("**")) {
var start = result.indexOf("**")
var rest = result[start + 2..-1]
var end = rest.indexOf("**")
if (end >= 0) {
var before = result[0...start]
var content = rest[0...end]
var after = rest[end + 2..-1]
result = before + "<strong>" + content + "</strong>" + after
} else {
break
}
}
while (result.contains("__")) {
var start = result.indexOf("__")
var rest = result[start + 2..-1]
var end = rest.indexOf("__")
if (end >= 0) {
var before = result[0...start]
var content = rest[0...end]
var after = rest[end + 2..-1]
result = before + "<strong>" + content + "</strong>" + after
} else {
break
}
}
var result = processMarker_(text, "**", "<strong>", "</strong>")
result = processMarker_(result, "__", "<strong>", "</strong>")
return result
}
static processMarker_(text, marker, openTag, closeTag) {
var parts = []
var pos = 0
var markerLen = marker.count
while (pos < text.count) {
var start = text.indexOf(marker, pos)
if (start < 0) {
parts.add(text[pos..-1])
break
}
var rest = text[start + markerLen..-1]
var end = rest.indexOf(marker)
if (end < 0) {
parts.add(text[pos..-1])
break
}
parts.add(text[pos...start])
parts.add(openTag)
parts.add(rest[0...end])
parts.add(closeTag)
pos = start + markerLen + end + markerLen
}
return parts.join("")
}
static processItalic_(text) {
var result = text
while (result.contains("*")) {
var start = result.indexOf("*")
if (start > 0 && result[start - 1] == "<") {
var parts = []
var pos = 0
while (pos < text.count) {
var start = text.indexOf("*", pos)
if (start < 0) {
parts.add(text[pos..-1])
break
}
var rest = result[start + 1..-1]
if (start > 0 && text[start - 1] == "<") {
parts.add(text[pos..-1])
break
}
var rest = text[start + 1..-1]
var end = rest.indexOf("*")
if (end >= 0 && end > 0) {
var before = result[0...start]
var content = rest[0...end]
var after = rest[end + 1..-1]
result = before + "<em>" + content + "</em>" + after
} else {
if (end < 0 || end == 0) {
parts.add(text[pos..-1])
break
}
parts.add(text[pos...start])
parts.add("<em>")
parts.add(rest[0...end])
parts.add("</em>")
pos = start + 1 + end + 1
}
return result
return parts.join("")
}
static processStrikethrough_(text) {
var result = text
while (result.contains("~~")) {
var start = result.indexOf("~~")
var rest = result[start + 2..-1]
var end = rest.indexOf("~~")
if (end >= 0) {
var before = result[0...start]
var content = rest[0...end]
var after = rest[end + 2..-1]
result = before + "<del>" + content + "</del>" + after
} else {
break
}
}
return result
return processMarker_(text, "~~", "<del>", "</del>")
}
static processLinks_(text) {
var result = text
while (result.contains("](")) {
var linkEnd = result.indexOf("](")
var parts = []
var pos = 0
while (pos < text.count) {
var linkEnd = text.indexOf("](", pos)
if (linkEnd < 0) {
parts.add(text[pos..-1])
break
}
var textStart = linkEnd
while (textStart > 0 && result[textStart - 1] != "[") {
while (textStart > pos && text[textStart - 1] != "[") {
textStart = textStart - 1
}
if (textStart > 0 && result[textStart - 1] == "[") {
if (textStart > pos && text[textStart - 1] == "[") {
var urlStart = linkEnd + 2
var urlEnd = urlStart
while (urlEnd < result.count && result[urlEnd] != ")") {
while (urlEnd < text.count && text[urlEnd] != ")") {
urlEnd = urlEnd + 1
}
if (urlEnd < result.count) {
var before = result[0...textStart - 1]
var linkText = result[textStart...linkEnd]
var url = result[urlStart...urlEnd]
var after = result[urlEnd + 1..-1]
result = before + "<a href=\"" + url + "\">" + linkText + "</a>" + after
if (urlEnd < text.count) {
parts.add(text[pos...textStart - 1])
var linkText = text[textStart...linkEnd]
var url = text[urlStart...urlEnd]
parts.add("<a href=\"")
parts.add(url)
parts.add("\">")
parts.add(linkText)
parts.add("</a>")
pos = urlEnd + 1
} else {
parts.add(text[pos..-1])
break
}
} else {
parts.add(text[pos..-1])
break
}
}
return result
return parts.join("")
}
static processImages_(text) {
var result = text
while (result.contains("![")) {
var start = result.indexOf("![")
var parts = []
var pos = 0
while (pos < text.count) {
var start = text.indexOf("![", pos)
if (start < 0) {
parts.add(text[pos..-1])
break
}
var altEnd = start + 2
while (altEnd < result.count && result[altEnd] != "]") {
while (altEnd < text.count && text[altEnd] != "]") {
altEnd = altEnd + 1
}
if (altEnd < result.count && altEnd + 1 < result.count && result[altEnd + 1] == "(") {
if (altEnd < text.count && altEnd + 1 < text.count && text[altEnd + 1] == "(") {
var urlStart = altEnd + 2
var urlEnd = urlStart
while (urlEnd < result.count && result[urlEnd] != ")") {
while (urlEnd < text.count && text[urlEnd] != ")") {
urlEnd = urlEnd + 1
}
if (urlEnd < result.count) {
var before = result[0...start]
var alt = result[start + 2...altEnd]
var url = result[urlStart...urlEnd]
var after = result[urlEnd + 1..-1]
result = before + "<img src=\"" + url + "\" alt=\"" + alt + "\">" + after
if (urlEnd < text.count) {
parts.add(text[pos...start])
var alt = text[start + 2...altEnd]
var url = text[urlStart...urlEnd]
parts.add("<img src=\"")
parts.add(url)
parts.add("\" alt=\"")
parts.add(alt)
parts.add("\">")
pos = urlEnd + 1
} else {
parts.add(text[pos..-1])
break
}
} else {
parts.add(text[pos..-1])
break
}
}
return result
return parts.join("")
}
static fromHtml(html) { fromHtml(html, {}) }
@ -1133,19 +1150,22 @@ class Markdown {
}
static processHrFromHtml_(html) {
var result = html
var parts = []
var i = 0
while (i < result.count) {
if (matchTagName_(result, i, "hr")) {
while (i < html.count) {
if (matchTagName_(html, i, "hr")) {
var end = i
while (end < result.count && result[end] != ">") {
while (end < html.count && html[end] != ">") {
end = end + 1
}
result = result[0...i] + "\n---\n" + result[end + 1..-1]
parts.add("\n---\n")
i = end + 1
} else {
parts.add(html[i])
i = i + 1
}
i = i + 1
}
return result
return parts.join("")
}
static processParagraphsFromHtml_(html) {
@ -1155,19 +1175,22 @@ class Markdown {
}
static processBrFromHtml_(html) {
var result = html
var parts = []
var i = 0
while (i < result.count) {
if (matchTagName_(result, i, "br")) {
while (i < html.count) {
if (matchTagName_(html, i, "br")) {
var end = i
while (end < result.count && result[end] != ">") {
while (end < html.count && html[end] != ">") {
end = end + 1
}
result = result[0...i] + "\n" + result[end + 1..-1]
parts.add("\n")
i = end + 1
} else {
parts.add(html[i])
i = i + 1
}
i = i + 1
}
return result
return parts.join("")
}
static stripContainerTags_(html) {
@ -1349,7 +1372,7 @@ class Markdown {
}
static stripAllTags_(html) {
var result = ""
var parts = []
var inTag = false
for (c in html) {
if (c == "<") {
@ -1357,10 +1380,10 @@ class Markdown {
} else if (c == ">") {
inTag = false
} else if (!inTag) {
result = result + c
parts.add(c)
}
}
return result
return parts.join("")
}
static cleanupWhitespace_(text) {

View File

@ -471,7 +471,7 @@ static const char* markdownModuleSource =
" }\n"
"\n"
" static processCode_(text) {\n"
" var result = \"\"\n"
" var parts = []\n"
" var i = 0\n"
" var chars = text.toList\n"
" while (i < chars.count) {\n"
@ -481,152 +481,169 @@ static const char* markdownModuleSource =
" end = end + 1\n"
" }\n"
" if (end < chars.count) {\n"
" var code = \"\"\n"
" var codeChars = []\n"
" for (j in (i + 1)...end) {\n"
" code = code + chars[j]\n"
" codeChars.add(chars[j])\n"
" }\n"
" result = result + \"<code>\" + code + \"</code>\"\n"
" parts.add(\"<code>\")\n"
" parts.add(codeChars.join(\"\"))\n"
" parts.add(\"</code>\")\n"
" i = end + 1\n"
" } else {\n"
" result = result + chars[i]\n"
" parts.add(chars[i])\n"
" i = i + 1\n"
" }\n"
" } else {\n"
" result = result + chars[i]\n"
" parts.add(chars[i])\n"
" i = i + 1\n"
" }\n"
" }\n"
" return result\n"
" return parts.join(\"\")\n"
" }\n"
"\n"
" static processBold_(text) {\n"
" var result = text\n"
" while (result.contains(\"**\")) {\n"
" var start = result.indexOf(\"**\")\n"
" var rest = result[start + 2..-1]\n"
" var end = rest.indexOf(\"**\")\n"
" if (end >= 0) {\n"
" var before = result[0...start]\n"
" var content = rest[0...end]\n"
" var after = rest[end + 2..-1]\n"
" result = before + \"<strong>\" + content + \"</strong>\" + after\n"
" } else {\n"
" break\n"
" }\n"
" }\n"
" while (result.contains(\"__\")) {\n"
" var start = result.indexOf(\"__\")\n"
" var rest = result[start + 2..-1]\n"
" var end = rest.indexOf(\"__\")\n"
" if (end >= 0) {\n"
" var before = result[0...start]\n"
" var content = rest[0...end]\n"
" var after = rest[end + 2..-1]\n"
" result = before + \"<strong>\" + content + \"</strong>\" + after\n"
" } else {\n"
" break\n"
" }\n"
" }\n"
" var result = processMarker_(text, \"**\", \"<strong>\", \"</strong>\")\n"
" result = processMarker_(result, \"__\", \"<strong>\", \"</strong>\")\n"
" return result\n"
" }\n"
"\n"
" static processMarker_(text, marker, openTag, closeTag) {\n"
" var parts = []\n"
" var pos = 0\n"
" var markerLen = marker.count\n"
" while (pos < text.count) {\n"
" var start = text.indexOf(marker, pos)\n"
" if (start < 0) {\n"
" parts.add(text[pos..-1])\n"
" break\n"
" }\n"
" var rest = text[start + markerLen..-1]\n"
" var end = rest.indexOf(marker)\n"
" if (end < 0) {\n"
" parts.add(text[pos..-1])\n"
" break\n"
" }\n"
" parts.add(text[pos...start])\n"
" parts.add(openTag)\n"
" parts.add(rest[0...end])\n"
" parts.add(closeTag)\n"
" pos = start + markerLen + end + markerLen\n"
" }\n"
" return parts.join(\"\")\n"
" }\n"
"\n"
" static processItalic_(text) {\n"
" var result = text\n"
" while (result.contains(\"*\")) {\n"
" var start = result.indexOf(\"*\")\n"
" if (start > 0 && result[start - 1] == \"<\") {\n"
" var parts = []\n"
" var pos = 0\n"
" while (pos < text.count) {\n"
" var start = text.indexOf(\"*\", pos)\n"
" if (start < 0) {\n"
" parts.add(text[pos..-1])\n"
" break\n"
" }\n"
" var rest = result[start + 1..-1]\n"
" if (start > 0 && text[start - 1] == \"<\") {\n"
" parts.add(text[pos..-1])\n"
" break\n"
" }\n"
" var rest = text[start + 1..-1]\n"
" var end = rest.indexOf(\"*\")\n"
" if (end >= 0 && end > 0) {\n"
" var before = result[0...start]\n"
" var content = rest[0...end]\n"
" var after = rest[end + 1..-1]\n"
" result = before + \"<em>\" + content + \"</em>\" + after\n"
" } else {\n"
" if (end < 0 || end == 0) {\n"
" parts.add(text[pos..-1])\n"
" break\n"
" }\n"
" parts.add(text[pos...start])\n"
" parts.add(\"<em>\")\n"
" parts.add(rest[0...end])\n"
" parts.add(\"</em>\")\n"
" pos = start + 1 + end + 1\n"
" }\n"
" return result\n"
" return parts.join(\"\")\n"
" }\n"
"\n"
" static processStrikethrough_(text) {\n"
" var result = text\n"
" while (result.contains(\"~~\")) {\n"
" var start = result.indexOf(\"~~\")\n"
" var rest = result[start + 2..-1]\n"
" var end = rest.indexOf(\"~~\")\n"
" if (end >= 0) {\n"
" var before = result[0...start]\n"
" var content = rest[0...end]\n"
" var after = rest[end + 2..-1]\n"
" result = before + \"<del>\" + content + \"</del>\" + after\n"
" } else {\n"
" break\n"
" }\n"
" }\n"
" return result\n"
" return processMarker_(text, \"~~\", \"<del>\", \"</del>\")\n"
" }\n"
"\n"
" static processLinks_(text) {\n"
" var result = text\n"
" while (result.contains(\"](\")) {\n"
" var linkEnd = result.indexOf(\"](\")\n"
" var parts = []\n"
" var pos = 0\n"
" while (pos < text.count) {\n"
" var linkEnd = text.indexOf(\"](\", pos)\n"
" if (linkEnd < 0) {\n"
" parts.add(text[pos..-1])\n"
" break\n"
" }\n"
" var textStart = linkEnd\n"
" while (textStart > 0 && result[textStart - 1] != \"[\") {\n"
" while (textStart > pos && text[textStart - 1] != \"[\") {\n"
" textStart = textStart - 1\n"
" }\n"
" if (textStart > 0 && result[textStart - 1] == \"[\") {\n"
" if (textStart > pos && text[textStart - 1] == \"[\") {\n"
" var urlStart = linkEnd + 2\n"
" var urlEnd = urlStart\n"
" while (urlEnd < result.count && result[urlEnd] != \")\") {\n"
" while (urlEnd < text.count && text[urlEnd] != \")\") {\n"
" urlEnd = urlEnd + 1\n"
" }\n"
" if (urlEnd < result.count) {\n"
" var before = result[0...textStart - 1]\n"
" var linkText = result[textStart...linkEnd]\n"
" var url = result[urlStart...urlEnd]\n"
" var after = result[urlEnd + 1..-1]\n"
" result = before + \"<a href=\\\"\" + url + \"\\\">\" + linkText + \"</a>\" + after\n"
" if (urlEnd < text.count) {\n"
" parts.add(text[pos...textStart - 1])\n"
" var linkText = text[textStart...linkEnd]\n"
" var url = text[urlStart...urlEnd]\n"
" parts.add(\"<a href=\\\"\")\n"
" parts.add(url)\n"
" parts.add(\"\\\">\")\n"
" parts.add(linkText)\n"
" parts.add(\"</a>\")\n"
" pos = urlEnd + 1\n"
" } else {\n"
" parts.add(text[pos..-1])\n"
" break\n"
" }\n"
" } else {\n"
" parts.add(text[pos..-1])\n"
" break\n"
" }\n"
" }\n"
" return result\n"
" return parts.join(\"\")\n"
" }\n"
"\n"
" static processImages_(text) {\n"
" var result = text\n"
" while (result.contains(\"![\")) {\n"
" var start = result.indexOf(\"![\")\n"
" var parts = []\n"
" var pos = 0\n"
" while (pos < text.count) {\n"
" var start = text.indexOf(\"![\", pos)\n"
" if (start < 0) {\n"
" parts.add(text[pos..-1])\n"
" break\n"
" }\n"
" var altEnd = start + 2\n"
" while (altEnd < result.count && result[altEnd] != \"]\") {\n"
" while (altEnd < text.count && text[altEnd] != \"]\") {\n"
" altEnd = altEnd + 1\n"
" }\n"
" if (altEnd < result.count && altEnd + 1 < result.count && result[altEnd + 1] == \"(\") {\n"
" if (altEnd < text.count && altEnd + 1 < text.count && text[altEnd + 1] == \"(\") {\n"
" var urlStart = altEnd + 2\n"
" var urlEnd = urlStart\n"
" while (urlEnd < result.count && result[urlEnd] != \")\") {\n"
" while (urlEnd < text.count && text[urlEnd] != \")\") {\n"
" urlEnd = urlEnd + 1\n"
" }\n"
" if (urlEnd < result.count) {\n"
" var before = result[0...start]\n"
" var alt = result[start + 2...altEnd]\n"
" var url = result[urlStart...urlEnd]\n"
" var after = result[urlEnd + 1..-1]\n"
" result = before + \"<img src=\\\"\" + url + \"\\\" alt=\\\"\" + alt + \"\\\">\" + after\n"
" if (urlEnd < text.count) {\n"
" parts.add(text[pos...start])\n"
" var alt = text[start + 2...altEnd]\n"
" var url = text[urlStart...urlEnd]\n"
" parts.add(\"<img src=\\\"\")\n"
" parts.add(url)\n"
" parts.add(\"\\\" alt=\\\"\")\n"
" parts.add(alt)\n"
" parts.add(\"\\\">\")\n"
" pos = urlEnd + 1\n"
" } else {\n"
" parts.add(text[pos..-1])\n"
" break\n"
" }\n"
" } else {\n"
" parts.add(text[pos..-1])\n"
" break\n"
" }\n"
" }\n"
" return result\n"
" return parts.join(\"\")\n"
" }\n"
"\n"
" static fromHtml(html) { fromHtml(html, {}) }\n"
@ -1137,19 +1154,22 @@ static const char* markdownModuleSource =
" }\n"
"\n"
" static processHrFromHtml_(html) {\n"
" var result = html\n"
" var parts = []\n"
" var i = 0\n"
" while (i < result.count) {\n"
" if (matchTagName_(result, i, \"hr\")) {\n"
" while (i < html.count) {\n"
" if (matchTagName_(html, i, \"hr\")) {\n"
" var end = i\n"
" while (end < result.count && result[end] != \">\") {\n"
" while (end < html.count && html[end] != \">\") {\n"
" end = end + 1\n"
" }\n"
" result = result[0...i] + \"\\n---\\n\" + result[end + 1..-1]\n"
" parts.add(\"\\n---\\n\")\n"
" i = end + 1\n"
" } else {\n"
" parts.add(html[i])\n"
" i = i + 1\n"
" }\n"
" i = i + 1\n"
" }\n"
" return result\n"
" return parts.join(\"\")\n"
" }\n"
"\n"
" static processParagraphsFromHtml_(html) {\n"
@ -1159,19 +1179,22 @@ static const char* markdownModuleSource =
" }\n"
"\n"
" static processBrFromHtml_(html) {\n"
" var result = html\n"
" var parts = []\n"
" var i = 0\n"
" while (i < result.count) {\n"
" if (matchTagName_(result, i, \"br\")) {\n"
" while (i < html.count) {\n"
" if (matchTagName_(html, i, \"br\")) {\n"
" var end = i\n"
" while (end < result.count && result[end] != \">\") {\n"
" while (end < html.count && html[end] != \">\") {\n"
" end = end + 1\n"
" }\n"
" result = result[0...i] + \"\\n\" + result[end + 1..-1]\n"
" parts.add(\"\\n\")\n"
" i = end + 1\n"
" } else {\n"
" parts.add(html[i])\n"
" i = i + 1\n"
" }\n"
" i = i + 1\n"
" }\n"
" return result\n"
" return parts.join(\"\")\n"
" }\n"
"\n"
" static stripContainerTags_(html) {\n"
@ -1353,7 +1376,7 @@ static const char* markdownModuleSource =
" }\n"
"\n"
" static stripAllTags_(html) {\n"
" var result = \"\"\n"
" var parts = []\n"
" var inTag = false\n"
" for (c in html) {\n"
" if (c == \"<\") {\n"
@ -1361,10 +1384,10 @@ static const char* markdownModuleSource =
" } else if (c == \">\") {\n"
" inTag = false\n"
" } else if (!inTag) {\n"
" result = result + c\n"
" parts.add(c)\n"
" }\n"
" }\n"
" return result\n"
" return parts.join(\"\")\n"
" }\n"
"\n"
" static cleanupWhitespace_(text) {\n"

View File

@ -14,9 +14,11 @@
typedef struct {
uv_tcp_t* handle;
WrenHandle* fiber; // Fiber waiting for an operation (connect, read, write)
WrenHandle* fiber;
uv_connect_t* connectReq;
uv_write_t* writeReq;
char* readBuffer;
size_t readBufferSize;
} SocketData;
typedef struct SocketListNode {
@ -47,6 +49,8 @@ void socketAllocate(WrenVM* vm)
data->fiber = NULL;
data->connectReq = NULL;
data->writeReq = NULL;
data->readBuffer = NULL;
data->readBufferSize = 0;
}
static void closeCallback(uv_handle_t* handle)
@ -60,6 +64,10 @@ void socketFinalize(void* data)
if (socketData->handle) {
uv_close((uv_handle_t*)socketData->handle, closeCallback);
}
if (socketData->readBuffer) {
free(socketData->readBuffer);
socketData->readBuffer = NULL;
}
}
static void connectCallback(uv_connect_t* req, int status)
@ -199,14 +207,19 @@ void socketWrite(WrenVM* vm)
static void allocCallback(uv_handle_t* handle, size_t suggestedSize, uv_buf_t* buf)
{
buf->base = (char*)malloc(suggestedSize);
buf->len = suggestedSize;
SocketData* data = (SocketData*)handle->data;
if (data->readBuffer == NULL || data->readBufferSize < suggestedSize) {
free(data->readBuffer);
data->readBuffer = (char*)malloc(suggestedSize);
data->readBufferSize = suggestedSize;
}
buf->base = data->readBuffer;
buf->len = data->readBufferSize;
}
static void readCallback(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf)
{
if (nread == 0) {
free(buf->base);
return;
}
@ -218,22 +231,18 @@ static void readCallback(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf
if (nread < 0) {
if (nread == UV_EOF) {
free(buf->base);
schedulerResume(fiber, true);
wrenSetSlotNull(getVM(), 2);
schedulerFinishResume();
return;
}
free(buf->base);
schedulerResumeError(fiber, uv_strerror(nread));
return;
}
// Return the data
schedulerResume(fiber, true);
wrenSetSlotBytes(getVM(), 2, buf->base, nread);
schedulerFinishResume();
free(buf->base);
}
void socketRead(WrenVM* vm)
@ -333,7 +342,9 @@ static void connectionCallback(uv_stream_t* server, int status)
sockData->fiber = NULL;
sockData->connectReq = NULL;
sockData->writeReq = NULL;
sockData->readBuffer = NULL;
sockData->readBufferSize = 0;
schedulerResume(fiber, true);
schedulerFinishResume();
} else {
@ -363,7 +374,7 @@ void serverBind(WrenVM* vm)
uv_ip4_addr(host, port, &addr);
uv_tcp_bind(data->handle, (const struct sockaddr*)&addr, 0);
int r = uv_listen((uv_stream_t*)data->handle, 128, connectionCallback);
int r = uv_listen((uv_stream_t*)data->handle, 1024, connectionCallback);
if (r != 0) {
wrenEnsureSlots(vm, 3);
wrenSetSlotString(vm, 2, uv_strerror(r));
@ -397,6 +408,8 @@ void serverAccept(WrenVM* vm)
sockData->fiber = NULL;
sockData->connectReq = NULL;
sockData->writeReq = NULL;
sockData->readBuffer = NULL;
sockData->readBufferSize = 0;
} else {
WrenHandle* fiber = wrenGetSlotHandle(vm, 1);
data->acceptFiber = fiber;

View File

@ -1,7 +1,9 @@
class Scheduler {
static add(callable) {
if (__scheduled == null) __scheduled = []
if (__scheduled == null) {
__scheduled = []
__scheduleIndex = 0
}
__scheduled.add(Fiber.new {
callable.call()
runNextScheduled_()
@ -26,22 +28,32 @@ class Scheduler {
}
static scheduleTransfer_(fiber, value) {
if (__scheduled == null) __scheduled = []
if (__scheduled == null) {
__scheduled = []
__scheduleIndex = 0
}
__scheduled.add(Fiber.new {
fiber.transfer(value)
})
}
static runNextScheduled_() {
if (__scheduled == null || __scheduled.isEmpty) {
if (__scheduled == null || __scheduleIndex >= __scheduled.count) {
__scheduled = null
__scheduleIndex = 0
return Fiber.suspend()
} else {
return __scheduled.removeAt(0).transfer()
}
var fiber = __scheduled[__scheduleIndex]
__scheduleIndex = __scheduleIndex + 1
if (__scheduleIndex > 64 && __scheduleIndex > __scheduled.count / 2) {
__scheduled = __scheduled[__scheduleIndex..-1]
__scheduleIndex = 0
}
return fiber.transfer()
}
static hasScheduled_ {
return __scheduled != null && !__scheduled.isEmpty
return __scheduled != null && __scheduleIndex < __scheduled.count
}
foreign static captureMethods_()

View File

@ -4,8 +4,10 @@
static const char* schedulerModuleSource =
"class Scheduler {\n"
" static add(callable) {\n"
" if (__scheduled == null) __scheduled = []\n"
"\n"
" if (__scheduled == null) {\n"
" __scheduled = []\n"
" __scheduleIndex = 0\n"
" }\n"
" __scheduled.add(Fiber.new {\n"
" callable.call()\n"
" runNextScheduled_()\n"
@ -30,22 +32,32 @@ static const char* schedulerModuleSource =
" }\n"
"\n"
" static scheduleTransfer_(fiber, value) {\n"
" if (__scheduled == null) __scheduled = []\n"
" if (__scheduled == null) {\n"
" __scheduled = []\n"
" __scheduleIndex = 0\n"
" }\n"
" __scheduled.add(Fiber.new {\n"
" fiber.transfer(value)\n"
" })\n"
" }\n"
"\n"
" static runNextScheduled_() {\n"
" if (__scheduled == null || __scheduled.isEmpty) {\n"
" if (__scheduled == null || __scheduleIndex >= __scheduled.count) {\n"
" __scheduled = null\n"
" __scheduleIndex = 0\n"
" return Fiber.suspend()\n"
" } else {\n"
" return __scheduled.removeAt(0).transfer()\n"
" }\n"
" var fiber = __scheduled[__scheduleIndex]\n"
" __scheduleIndex = __scheduleIndex + 1\n"
" if (__scheduleIndex > 64 && __scheduleIndex > __scheduled.count / 2) {\n"
" __scheduled = __scheduled[__scheduleIndex..-1]\n"
" __scheduleIndex = 0\n"
" }\n"
" return fiber.transfer()\n"
" }\n"
"\n"
" static hasScheduled_ {\n"
" return __scheduled != null && !__scheduled.isEmpty\n"
" return __scheduled != null && __scheduleIndex < __scheduled.count\n"
" }\n"
"\n"
" foreign static captureMethods_()\n"

View File

@ -158,11 +158,7 @@ static void queryInternal(WrenVM* vm, bool hasParams) {
case SQLITE_BLOB: {
int size = sqlite3_column_bytes(stmt, i);
const unsigned char* blob = sqlite3_column_blob(stmt, i);
wrenSetSlotNewList(vm, 4);
for (int j = 0; j < size; j++) {
wrenSetSlotDouble(vm, 2, (double)blob[j]);
wrenInsertInList(vm, 4, -1, 2);
}
wrenSetSlotBytes(vm, 4, (const char*)blob, size);
break;
}
default:

View File

@ -14,7 +14,7 @@ class Validator {
if (dot < at + 2) return false
if (dot >= value.count - 1) return false
for (c in value) {
var code = c.codePoints.toList[0]
var code = c.bytes[0]
if (c == "@" || c == "." || c == "_" || c == "-" || c == "+") continue
if (code >= 48 && code <= 57) continue
if (code >= 65 && code <= 90) continue
@ -34,7 +34,7 @@ class Validator {
if (part.count == 0 || part.count > 63) return false
if (part[0] == "-" || part[-1] == "-") return false
for (c in part) {
var code = c.codePoints.toList[0]
var code = c.bytes[0]
if (c == "-") continue
if (code >= 48 && code <= 57) continue
if (code >= 65 && code <= 90) continue
@ -56,7 +56,7 @@ class Validator {
static httpEncoded(value) {
if (!(value is String)) return false
for (c in value) {
var code = c.codePoints.toList[0]
var code = c.bytes[0]
if (c == "_" || c == "." || c == "~" || c == "-" || c == "+") continue
if (c == "\%") continue
if (code >= 48 && code <= 57) continue
@ -79,7 +79,7 @@ class Validator {
} else if (padCount > 0) {
return false
} else {
var code = c.codePoints.toList[0]
var code = c.bytes[0]
if (c == "+" || c == "/") continue
if (code >= 48 && code <= 57) continue
if (code >= 65 && code <= 90) continue

View File

@ -18,7 +18,7 @@ static const char* wdanticModuleSource =
" if (dot < at + 2) return false\n"
" if (dot >= value.count - 1) return false\n"
" for (c in value) {\n"
" var code = c.codePoints.toList[0]\n"
" var code = c.bytes[0]\n"
" if (c == \"@\" || c == \".\" || c == \"_\" || c == \"-\" || c == \"+\") continue\n"
" if (code >= 48 && code <= 57) continue\n"
" if (code >= 65 && code <= 90) continue\n"
@ -38,7 +38,7 @@ static const char* wdanticModuleSource =
" if (part.count == 0 || part.count > 63) return false\n"
" if (part[0] == \"-\" || part[-1] == \"-\") return false\n"
" for (c in part) {\n"
" var code = c.codePoints.toList[0]\n"
" var code = c.bytes[0]\n"
" if (c == \"-\") continue\n"
" if (code >= 48 && code <= 57) continue\n"
" if (code >= 65 && code <= 90) continue\n"
@ -60,7 +60,7 @@ static const char* wdanticModuleSource =
" static httpEncoded(value) {\n"
" if (!(value is String)) return false\n"
" for (c in value) {\n"
" var code = c.codePoints.toList[0]\n"
" var code = c.bytes[0]\n"
" if (c == \"_\" || c == \".\" || c == \"~\" || c == \"-\" || c == \"+\") continue\n"
" if (c == \"\\%\") continue\n"
" if (code >= 48 && code <= 57) continue\n"
@ -83,7 +83,7 @@ static const char* wdanticModuleSource =
" } else if (padCount > 0) {\n"
" return false\n"
" } else {\n"
" var code = c.codePoints.toList[0]\n"
" var code = c.bytes[0]\n"
" if (c == \"+\" || c == \"/\") continue\n"
" if (code >= 48 && code <= 57) continue\n"
" if (code >= 65 && code <= 90) continue\n"

55
src/module/web.wren vendored
View File

@ -21,6 +21,10 @@ class Request {
_path = path
_query = query
_headers = headers
_normalizedHeaders = {}
for (key in headers.keys) {
_normalizedHeaders[Str.toLower(key)] = headers[key]
}
_body = body
_params = params
_socket = socket
@ -39,11 +43,8 @@ class Request {
socket { _socket }
header(name) {
var lower = toLower_(name)
for (key in _headers.keys) {
if (toLower_(key) == lower) return _headers[key]
}
return null
var lower = Str.toLower(name)
return _normalizedHeaders.containsKey(lower) ? _normalizedHeaders[lower] : null
}
json {
@ -304,16 +305,23 @@ class Response {
}
build() {
var response = "HTTP/1.1 %(_status) %(_statusText)\r\n"
var parts = []
parts.add("HTTP/1.1 %(_status) %(_statusText)\r\n")
_headers["Content-Length"] = _body.bytes.count.toString
for (name in _headers.keys) {
response = response + name + ": " + _headers[name] + "\r\n"
parts.add(name)
parts.add(": ")
parts.add(_headers[name])
parts.add("\r\n")
}
for (cookie in _cookies) {
response = response + "Set-Cookie: " + cookie + "\r\n"
parts.add("Set-Cookie: ")
parts.add(cookie)
parts.add("\r\n")
}
response = response + "\r\n" + _body
return response
parts.add("\r\n")
parts.add(_body)
return parts.join("")
}
}
@ -855,7 +863,7 @@ class Application {
var contentLength = 0
var headersComplete = false
var headerBytes = 0
var requestData = null
var headerStr = null
while (true) {
var chunk = socket.read()
@ -867,30 +875,26 @@ class Application {
totalBytes = totalBytes + chunk.bytes.count
if (!headersComplete) {
requestData = chunks.join("")
if (requestData.contains("\r\n\r\n")) {
headerStr = headerStr == null ? chunk : headerStr + chunk
var delimIdx = headerStr.indexOf("\r\n\r\n")
if (delimIdx >= 0) {
headersComplete = true
var headerEnd = requestData.indexOf("\r\n\r\n")
headerBytes = headerEnd + 4
var headerPart = requestData[0...headerEnd]
headerBytes = delimIdx + 4
var headerPart = headerStr[0...delimIdx]
var headers = parseHeaders_(headerPart)
if (headers.containsKey("Content-Length")) {
contentLength = Num.fromString(headers["Content-Length"])
} else {
break
}
if (totalBytes - headerBytes >= contentLength) break
if (totalBytes >= headerBytes + contentLength) break
}
} else {
if (totalBytes - headerBytes >= contentLength) break
if (totalBytes >= headerBytes + contentLength) break
}
}
if (requestData == null || chunks.count > 1) {
requestData = chunks.join("")
}
var requestData = chunks.join("")
var headerEnd = requestData.indexOf("\r\n\r\n")
var headerPart = requestData[0...headerEnd]
@ -1094,12 +1098,13 @@ class Client {
socket.write(request)
var response = ""
var chunks = []
while (true) {
var chunk = socket.read()
if (chunk == null || chunk.bytes.count == 0) break
response = response + chunk
chunks.add(chunk)
}
var response = chunks.join("")
socket.close()
return parseResponse_(response)

View File

@ -25,6 +25,10 @@ static const char* webModuleSource =
" _path = path\n"
" _query = query\n"
" _headers = headers\n"
" _normalizedHeaders = {}\n"
" for (key in headers.keys) {\n"
" _normalizedHeaders[Str.toLower(key)] = headers[key]\n"
" }\n"
" _body = body\n"
" _params = params\n"
" _socket = socket\n"
@ -43,11 +47,8 @@ static const char* webModuleSource =
" socket { _socket }\n"
"\n"
" header(name) {\n"
" var lower = toLower_(name)\n"
" for (key in _headers.keys) {\n"
" if (toLower_(key) == lower) return _headers[key]\n"
" }\n"
" return null\n"
" var lower = Str.toLower(name)\n"
" return _normalizedHeaders.containsKey(lower) ? _normalizedHeaders[lower] : null\n"
" }\n"
"\n"
" json {\n"
@ -308,16 +309,23 @@ static const char* webModuleSource =
" }\n"
"\n"
" build() {\n"
" var response = \"HTTP/1.1 %(_status) %(_statusText)\\r\\n\"\n"
" var parts = []\n"
" parts.add(\"HTTP/1.1 %(_status) %(_statusText)\\r\\n\")\n"
" _headers[\"Content-Length\"] = _body.bytes.count.toString\n"
" for (name in _headers.keys) {\n"
" response = response + name + \": \" + _headers[name] + \"\\r\\n\"\n"
" parts.add(name)\n"
" parts.add(\": \")\n"
" parts.add(_headers[name])\n"
" parts.add(\"\\r\\n\")\n"
" }\n"
" for (cookie in _cookies) {\n"
" response = response + \"Set-Cookie: \" + cookie + \"\\r\\n\"\n"
" parts.add(\"Set-Cookie: \")\n"
" parts.add(cookie)\n"
" parts.add(\"\\r\\n\")\n"
" }\n"
" response = response + \"\\r\\n\" + _body\n"
" return response\n"
" parts.add(\"\\r\\n\")\n"
" parts.add(_body)\n"
" return parts.join(\"\")\n"
" }\n"
"}\n"
"\n"
@ -859,7 +867,7 @@ static const char* webModuleSource =
" var contentLength = 0\n"
" var headersComplete = false\n"
" var headerBytes = 0\n"
" var requestData = null\n"
" var headerStr = null\n"
"\n"
" while (true) {\n"
" var chunk = socket.read()\n"
@ -871,30 +879,26 @@ static const char* webModuleSource =
" totalBytes = totalBytes + chunk.bytes.count\n"
"\n"
" if (!headersComplete) {\n"
" requestData = chunks.join(\"\")\n"
" if (requestData.contains(\"\\r\\n\\r\\n\")) {\n"
" headerStr = headerStr == null ? chunk : headerStr + chunk\n"
" var delimIdx = headerStr.indexOf(\"\\r\\n\\r\\n\")\n"
" if (delimIdx >= 0) {\n"
" headersComplete = true\n"
" var headerEnd = requestData.indexOf(\"\\r\\n\\r\\n\")\n"
" headerBytes = headerEnd + 4\n"
"\n"
" var headerPart = requestData[0...headerEnd]\n"
" headerBytes = delimIdx + 4\n"
" var headerPart = headerStr[0...delimIdx]\n"
" var headers = parseHeaders_(headerPart)\n"
" if (headers.containsKey(\"Content-Length\")) {\n"
" contentLength = Num.fromString(headers[\"Content-Length\"])\n"
" } else {\n"
" break\n"
" }\n"
"\n"
" if (totalBytes - headerBytes >= contentLength) break\n"
" if (totalBytes >= headerBytes + contentLength) break\n"
" }\n"
" } else {\n"
" if (totalBytes - headerBytes >= contentLength) break\n"
" if (totalBytes >= headerBytes + contentLength) break\n"
" }\n"
" }\n"
"\n"
" if (requestData == null || chunks.count > 1) {\n"
" requestData = chunks.join(\"\")\n"
" }\n"
" var requestData = chunks.join(\"\")\n"
"\n"
" var headerEnd = requestData.indexOf(\"\\r\\n\\r\\n\")\n"
" var headerPart = requestData[0...headerEnd]\n"
@ -1098,12 +1102,13 @@ static const char* webModuleSource =
"\n"
" socket.write(request)\n"
"\n"
" var response = \"\"\n"
" var chunks = []\n"
" while (true) {\n"
" var chunk = socket.read()\n"
" if (chunk == null || chunk.bytes.count == 0) break\n"
" response = response + chunk\n"
" chunks.add(chunk)\n"
" }\n"
" var response = chunks.join(\"\")\n"
" socket.close()\n"
"\n"
" return parseResponse_(response)\n"

55
src/module/yaml.wren vendored
View File

@ -258,12 +258,36 @@ class YamlParser {
if ((str[0] == "\"" && str[-1] == "\"") ||
(str[0] == "'" && str[-1] == "'")) {
var inner = str[1...-1]
inner = inner.replace("\\n", "\n")
inner = inner.replace("\\t", "\t")
inner = inner.replace("\\\"", "\"")
inner = inner.replace("\\'", "'")
inner = inner.replace("\\\\", "\\")
return inner
var parts = []
var i = 0
while (i < inner.count) {
if (inner[i] == "\\" && i + 1 < inner.count) {
var next = inner[i + 1]
if (next == "n") {
parts.add("\n")
i = i + 2
} else if (next == "t") {
parts.add("\t")
i = i + 2
} else if (next == "\"") {
parts.add("\"")
i = i + 2
} else if (next == "'") {
parts.add("'")
i = i + 2
} else if (next == "\\") {
parts.add("\\")
i = i + 2
} else {
parts.add(inner[i])
i = i + 1
}
} else {
parts.add(inner[i])
i = i + 1
}
}
return parts.join("")
}
return str
}
@ -391,12 +415,7 @@ class Yaml {
if (list.count == 0) return "[]"
var result = []
var prefix = ""
for (i in 0...level) {
for (j in 0...indent) {
prefix = prefix + " "
}
}
var prefix = " " * (level * indent)
for (item in list) {
if (item is Map && item.count > 0) {
@ -415,10 +434,7 @@ class Yaml {
}
first = false
} else {
var keyPrefix = prefix
for (j in 0...indent) {
keyPrefix = keyPrefix + " "
}
var keyPrefix = prefix + (" " * indent)
if (item[key] is Map || item[key] is List) {
result.add(keyPrefix + key + ":")
var nestedLines = valueStr.split("\n")
@ -450,12 +466,7 @@ class Yaml {
if (map.count == 0) return "{}"
var result = []
var prefix = ""
for (i in 0...level) {
for (j in 0...indent) {
prefix = prefix + " "
}
}
var prefix = " " * (level * indent)
for (key in map.keys) {
var keyStr = stringifyString_(key.toString)

View File

@ -262,12 +262,36 @@ static const char* yamlModuleSource =
" if ((str[0] == \"\\\"\" && str[-1] == \"\\\"\") ||\n"
" (str[0] == \"'\" && str[-1] == \"'\")) {\n"
" var inner = str[1...-1]\n"
" inner = inner.replace(\"\\\\n\", \"\\n\")\n"
" inner = inner.replace(\"\\\\t\", \"\\t\")\n"
" inner = inner.replace(\"\\\\\\\"\", \"\\\"\")\n"
" inner = inner.replace(\"\\\\'\", \"'\")\n"
" inner = inner.replace(\"\\\\\\\\\", \"\\\\\")\n"
" return inner\n"
" var parts = []\n"
" var i = 0\n"
" while (i < inner.count) {\n"
" if (inner[i] == \"\\\\\" && i + 1 < inner.count) {\n"
" var next = inner[i + 1]\n"
" if (next == \"n\") {\n"
" parts.add(\"\\n\")\n"
" i = i + 2\n"
" } else if (next == \"t\") {\n"
" parts.add(\"\\t\")\n"
" i = i + 2\n"
" } else if (next == \"\\\"\") {\n"
" parts.add(\"\\\"\")\n"
" i = i + 2\n"
" } else if (next == \"'\") {\n"
" parts.add(\"'\")\n"
" i = i + 2\n"
" } else if (next == \"\\\\\") {\n"
" parts.add(\"\\\\\")\n"
" i = i + 2\n"
" } else {\n"
" parts.add(inner[i])\n"
" i = i + 1\n"
" }\n"
" } else {\n"
" parts.add(inner[i])\n"
" i = i + 1\n"
" }\n"
" }\n"
" return parts.join(\"\")\n"
" }\n"
" return str\n"
" }\n"
@ -395,12 +419,7 @@ static const char* yamlModuleSource =
" if (list.count == 0) return \"[]\"\n"
"\n"
" var result = []\n"
" var prefix = \"\"\n"
" for (i in 0...level) {\n"
" for (j in 0...indent) {\n"
" prefix = prefix + \" \"\n"
" }\n"
" }\n"
" var prefix = \" \" * (level * indent)\n"
"\n"
" for (item in list) {\n"
" if (item is Map && item.count > 0) {\n"
@ -419,10 +438,7 @@ static const char* yamlModuleSource =
" }\n"
" first = false\n"
" } else {\n"
" var keyPrefix = prefix\n"
" for (j in 0...indent) {\n"
" keyPrefix = keyPrefix + \" \"\n"
" }\n"
" var keyPrefix = prefix + (\" \" * indent)\n"
" if (item[key] is Map || item[key] is List) {\n"
" result.add(keyPrefix + key + \":\")\n"
" var nestedLines = valueStr.split(\"\\n\")\n"
@ -454,12 +470,7 @@ static const char* yamlModuleSource =
" if (map.count == 0) return \"{}\"\n"
"\n"
" var result = []\n"
" var prefix = \"\"\n"
" for (i in 0...level) {\n"
" for (j in 0...indent) {\n"
" prefix = prefix + \" \"\n"
" }\n"
" }\n"
" var prefix = \" \" * (level * indent)\n"
"\n"
" for (key in map.keys) {\n"
" var keyStr = stringifyString_(key.toString)\n"

7
test/html/slugify_perf.wren vendored Normal file
View File

@ -0,0 +1,7 @@
// retoor <retoor@molodetz.nl>
import "html" for Html
var long = "A" * 10000
var result = Html.slugify(long)
System.print(result.count) // expect: 10000

11
test/http/header_lookup.wren vendored Normal file
View File

@ -0,0 +1,11 @@
// retoor <retoor@molodetz.nl>
import "http" for HttpResponse
var headers = {}
for (i in 0...100) {
headers["Header-%(i)"] = "value%(i)"
}
var resp = HttpResponse.new_(200, "OK", headers, "")
System.print(resp.header("header-50")) // expect: value50
System.print(resp.header("HEADER-99")) // expect: value99

7
test/markdown/large_bold.wren vendored Normal file
View File

@ -0,0 +1,7 @@
// retoor <retoor@molodetz.nl>
import "markdown" for Markdown
var text = "**bold** " * 100
var result = Markdown.toHtml(text)
System.print(result.contains("<strong>")) // expect: true

42
test/web/concurrent_handlers.wren vendored Normal file
View File

@ -0,0 +1,42 @@
// retoor <retoor@molodetz.nl>
import "scheduler" for Scheduler
import "timer" for Timer
var results = []
var completed = 0
Scheduler.add {
results.add("fiber1_start")
Timer.sleep(1)
results.add("fiber1_end")
completed = completed + 1
}
Scheduler.add {
results.add("fiber2_start")
Timer.sleep(1)
results.add("fiber2_end")
completed = completed + 1
}
Scheduler.add {
results.add("fiber3_start")
Timer.sleep(1)
results.add("fiber3_end")
completed = completed + 1
}
System.print(completed) // expect: 0
System.print(results.count) // expect: 0
Timer.sleep(10)
System.print(completed) // expect: 3
System.print(results.count) // expect: 6
System.print(results.contains("fiber1_start")) // expect: true
System.print(results.contains("fiber1_end")) // expect: true
System.print(results.contains("fiber2_start")) // expect: true
System.print(results.contains("fiber2_end")) // expect: true
System.print(results.contains("fiber3_start")) // expect: true
System.print(results.contains("fiber3_end")) // expect: true

46
test/web/error_isolation.wren vendored Normal file
View File

@ -0,0 +1,46 @@
// retoor <retoor@molodetz.nl>
import "scheduler" for Scheduler
import "timer" for Timer
var results = []
var errors = []
var handleSafe = Fn.new { |task|
var fiber = Fiber.new {
task.call()
}
var error = fiber.try()
if (error != null) {
errors.add(error)
}
}
Scheduler.add {
handleSafe.call {
results.add("good1")
}
}
Scheduler.add {
handleSafe.call {
Fiber.abort("intentional error")
}
}
Scheduler.add {
handleSafe.call {
results.add("good2")
}
}
System.print(results.count) // expect: 0
System.print(errors.count) // expect: 0
Timer.sleep(10)
System.print(results.count) // expect: 2
System.print(errors.count) // expect: 1
System.print(results[0]) // expect: good1
System.print(results[1]) // expect: good2
System.print(errors[0]) // expect: intentional error

41
test/web/scheduler_integration.wren vendored Normal file
View File

@ -0,0 +1,41 @@
// retoor <retoor@molodetz.nl>
import "scheduler" for Scheduler
import "timer" for Timer
import "web" for SessionStore
var store = SessionStore.new()
var results = []
var taskCount = 5
for (i in 0...taskCount) {
var index = i
Scheduler.add {
var session = store.create()
session["task"] = index
store.save(session)
results.add({"id": session.id, "index": index})
}
}
System.print(results.count) // expect: 0
Timer.sleep(50)
System.print(results.count) // expect: 5
var uniqueIds = {}
for (r in results) {
uniqueIds[r["id"]] = true
}
System.print(uniqueIds.count) // expect: 5
var hasAll = true
for (i in 0...5) {
var found = false
for (r in results) {
if (r["index"] == i) found = true
}
if (!found) hasAll = false
}
System.print(hasAll) // expect: true