amessage
This commit is contained in:
parent
f971423f04
commit
8402ff3a3c
4 changed files with 85 additions and 84 deletions
|
|
@ -24,6 +24,7 @@ let myPrelude : Map<string, TagRenderer> =
|
||||||
Text "[Fel: quotient kräver två argument]"
|
Text "[Fel: quotient kräver två argument]"
|
||||||
"bold", fun _ _ _ children ->
|
"bold", fun _ _ _ children ->
|
||||||
Strong(children)
|
Strong(children)
|
||||||
|
"kursiv", fun _ _ _ children -> Emph(children)
|
||||||
"image", image
|
"image", image
|
||||||
"value", value
|
"value", value
|
||||||
"link", link
|
"link", link
|
||||||
|
|
|
||||||
|
|
@ -42,4 +42,8 @@ Tredje saken
|
||||||
Första anropet: @(increment())
|
Första anropet: @(increment())
|
||||||
Andra anropet: @(increment())
|
Andra anropet: @(increment())
|
||||||
|
|
||||||
|
@bold{detta är ibdenrerat.
|
||||||
|
detta också.
|
||||||
|
hej}
|
||||||
|
|
||||||
Test av utskrift: @value[date]
|
Test av utskrift: @value[date]
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,8 @@ module HtmlPrinter =
|
||||||
let rec renderInline =
|
let rec renderInline =
|
||||||
function
|
function
|
||||||
| Text t -> WebUtility.HtmlEncode t
|
| Text t -> WebUtility.HtmlEncode t
|
||||||
|
| Strong(t) -> sprintf "<strong>%s</strong>" (renderInline t)
|
||||||
| RawHtml h ->
|
| RawHtml h ->
|
||||||
printfn "%s" "hej"
|
|
||||||
h
|
h
|
||||||
| _ -> ""
|
| _ -> ""
|
||||||
|
|
||||||
|
|
@ -56,4 +56,4 @@ module HtmlPrinter =
|
||||||
| Plain nodes -> List.map renderInline nodes |> String.concat " "
|
| Plain nodes -> List.map renderInline nodes |> String.concat " "
|
||||||
blocks
|
blocks
|
||||||
|> List.map renderNode
|
|> List.map renderNode
|
||||||
|> String.concat "\n"
|
|> String.concat "\n"
|
||||||
|
|
|
||||||
|
|
@ -66,29 +66,57 @@ module Ast =
|
||||||
|
|
||||||
module Utils =
|
module Utils =
|
||||||
open Ast
|
open Ast
|
||||||
let smartDedent (input: string) =
|
let dedentNodes (nodes: InlineNode list) =
|
||||||
let lines = input.Replace("\r\n", "\n").Split '\n' |> List.ofArray
|
let fullText =
|
||||||
|
nodes |> List.choose (function Text t -> Some t | _ -> None) |> String.concat ""
|
||||||
// 1. Hitta den minsta indenteringen bland alla rader som har text
|
|
||||||
|
let lines = fullText.Replace("\r\n", "\n").Split('\n')
|
||||||
|
|
||||||
|
// 1. Räkna BARA ut minIndent från rader som kommer efter en radbrytning (skippa rad 0)
|
||||||
let minIndent =
|
let minIndent =
|
||||||
lines
|
if lines.Length <= 1 then 0
|
||||||
|> List.filter (fun l -> not (System.String.IsNullOrWhiteSpace(l)))
|
else
|
||||||
|> List.map (fun l -> l.Length - l.TrimStart().Length)
|
lines |> Array.skip 1
|
||||||
|> function
|
|> Array.filter (fun l -> not (System.String.IsNullOrWhiteSpace(l)))
|
||||||
| [] -> 0
|
|> Array.map (fun l -> l.Length - l.TrimStart().Length)
|
||||||
| indents -> List.min indents
|
|> function [||] -> 0 | arr -> Array.min arr
|
||||||
|
|
||||||
// 2. Dra av exakt så många mellanslag från alla rader
|
let mutable isFirstText = true
|
||||||
// för att dessa inte ska vara med i det slutgiltiga dokumentet.
|
let indentStr = "\n" + String.replicate minIndent " "
|
||||||
let dedented =
|
|
||||||
lines
|
// 2. Applicera formateringen
|
||||||
|> List.map (fun l ->
|
let dedented =
|
||||||
if System.String.IsNullOrWhiteSpace(l) then ""
|
nodes |> List.map (function
|
||||||
else l.Substring(minIndent)
|
| Text t ->
|
||||||
|
let t1 = t.Replace("\r\n", "\n")
|
||||||
|
|
||||||
|
// Ta bort inledande mellanslag på den allra första texten direkt efter '{'
|
||||||
|
let t2 =
|
||||||
|
if isFirstText then
|
||||||
|
isFirstText <- false
|
||||||
|
t1.TrimStart(' ', '\t')
|
||||||
|
else t1
|
||||||
|
|
||||||
|
// Ta bort minIndent antal mellanslag efter varje radbrytning i noden
|
||||||
|
let t3 = if minIndent > 0 then t2.Replace(indentStr, "\n") else t2
|
||||||
|
Text t3
|
||||||
|
| otherNode -> otherNode
|
||||||
)
|
)
|
||||||
|
|
||||||
// 3. Slå ihop och städa bort överflödiga radbrytningar i början/slutet
|
// 3. Städa bort överflödiga radbrytningar och blanksteg i ytterkanterna
|
||||||
(String.concat "\n" dedented).Trim('\n', '\r')
|
let rec trimStart = function
|
||||||
|
| Text t :: rest ->
|
||||||
|
let trimmed = t.TrimStart('\n', '\r', ' ', '\t')
|
||||||
|
if trimmed = "" then trimStart rest else Text trimmed :: rest
|
||||||
|
| other -> other
|
||||||
|
|
||||||
|
let rec trimEnd = function
|
||||||
|
| Text t :: rest ->
|
||||||
|
let trimmed = t.TrimEnd('\n', '\r', ' ', '\t')
|
||||||
|
if trimmed = "" then trimEnd rest else Text trimmed :: rest
|
||||||
|
| other -> other
|
||||||
|
|
||||||
|
dedented |> trimStart |> List.rev |> trimEnd |> List.rev
|
||||||
|
|
||||||
let positional f: TagRenderer =
|
let positional f: TagRenderer =
|
||||||
fun _ (args: string list) _ children -> f args children
|
fun _ (args: string list) _ children -> f args children
|
||||||
|
|
@ -159,55 +187,24 @@ module Parser =
|
||||||
fail "Syntaxfel: Positionella argument får inte komma efter namngivna argument."
|
fail "Syntaxfel: Positionella argument får inte komma efter namngivna argument."
|
||||||
else validate true tail
|
else validate true tail
|
||||||
| _ :: tail ->
|
| _ :: tail ->
|
||||||
validate false tail // Vi hittade ett namngivet argument, inga fler positionella tillåts
|
validate false tail
|
||||||
|
|
||||||
validate true args
|
validate true args
|
||||||
// let pArg =
|
|
||||||
// spaces >>.
|
|
||||||
// choice [
|
|
||||||
// attempt (many1Chars (asciiLetter <|> digit <|> anyOf "-_")
|
|
||||||
// .>> spaces .>> pchar '=' .>> spaces
|
|
||||||
// .>>. manyChars (noneOf ",]"))
|
|
||||||
// |>> fun (k, v) -> (k, v.Trim())
|
|
||||||
//
|
|
||||||
// // Fallback: bara "värde" (ges en tom nyckel)
|
|
||||||
// manyChars (noneOf ",]") |>> fun v -> ("", v.Trim())
|
|
||||||
// ] .>> spaces
|
|
||||||
// let pArgs = between (pstring "[") (pstring "]") (sepBy pArg (pstring ","))
|
|
||||||
|
|
||||||
// --- 1. Måsvinge-parser (för @kommandon) ---
|
// --- 1. Måsvinge-parser (för @kommandon) ---
|
||||||
let pRawBody, pRawBodyRef = createParserForwardedToRef<string, unit>()
|
// Lägg till en referens för pBody högst upp bland dina referenser
|
||||||
pRawBodyRef.Value <-
|
// (Bör ligga precis under let pInline, pInlineRef = ...)
|
||||||
many (choice [
|
let pBody, pBodyRef = createParserForwardedToRef<InlineNode list, unit>()
|
||||||
many1Chars (noneOf "{}")
|
|
||||||
pchar '{' >>. pRawBodyRef.Value .>> pchar '}' |>> sprintf "{%s}"
|
|
||||||
]) |>> String.concat ""
|
|
||||||
|
|
||||||
let pBody =
|
|
||||||
between (pstring "{") (pstring "}") pRawBodyRef.Value >>= fun raw ->
|
|
||||||
// En mer tillåtande parser isolerad för innehållet inuti {...}
|
|
||||||
let pInnerInline =
|
|
||||||
choice [
|
|
||||||
attempt pInline
|
|
||||||
// Fångar upp dubbla radbrytningar och måsvingar som pInline normalt blockerar
|
|
||||||
many1Chars (anyOf "\r\n{}") |>> Text
|
|
||||||
]
|
|
||||||
|
|
||||||
match run (many pInnerInline .>> eof) (Utils.smartDedent raw) with
|
|
||||||
| Success(n, _, _) -> preturn n
|
|
||||||
| Failure(m, _, _) -> fail m
|
|
||||||
|
|
||||||
// --- 2. Parentes-parser (för @(...) med sträng-stöd) ---
|
// --- 2. Parentes-parser (för @(...) med sträng-stöd) ---
|
||||||
let pParenBody, pParenBodyRef = createParserForwardedToRef<string, unit>()
|
let pParenBody, pParenBodyRef = createParserForwardedToRef<string, unit>()
|
||||||
|
|
||||||
// En inre parser som känner igen F#-strängar och escape-tecken (\")
|
|
||||||
let pFSharpString =
|
let pFSharpString =
|
||||||
let normal = many1Chars (noneOf "\"\\")
|
let normal = many1Chars (noneOf "\"\\")
|
||||||
let escaped = pstring "\\" >>. anyChar |>> sprintf "\\%c"
|
let escaped = pstring "\\" >>. anyChar |>> sprintf "\\%c"
|
||||||
pstring "\"" .>>. manyStrings (normal <|> escaped) .>>. pstring "\""
|
pstring "\"" .>>. manyStrings (normal <|> escaped) .>>. pstring "\""
|
||||||
|>> fun ((start, inner), end_) -> start + inner + end_
|
|>> fun ((start, inner), end_) -> start + inner + end_
|
||||||
|
|
||||||
// Själva loopen letar nu efter strängar FÖRST, sen vanlig text, och sist inre parenteser
|
|
||||||
pParenBodyRef.Value <-
|
pParenBodyRef.Value <-
|
||||||
manyStrings (choice [
|
manyStrings (choice [
|
||||||
pFSharpString
|
pFSharpString
|
||||||
|
|
@ -222,39 +219,38 @@ module Parser =
|
||||||
// --- Övriga inline-parsers ---
|
// --- Övriga inline-parsers ---
|
||||||
let pMultilineCode =
|
let pMultilineCode =
|
||||||
pstring "@\"\"\"" >>. manyCharsTill anyChar (pstring "\"\"\"")
|
pstring "@\"\"\"" >>. manyCharsTill anyChar (pstring "\"\"\"")
|
||||||
|>> fun c -> Expr(Utils.smartDedent c, None)
|
|>> fun c -> Expr(c, None)
|
||||||
|
|
||||||
// let pInlineCommand =
|
// pInlineCommand använder nu forward-referensen pBodyRef
|
||||||
// attempt (
|
|
||||||
// pchar '@' >>. many1Chars asciiLetter >>= fun name ->
|
|
||||||
// if isSection name then fail "Sektioner är block-element."
|
|
||||||
// else preturn name
|
|
||||||
// )
|
|
||||||
// .>>. opt pArgs
|
|
||||||
// .>>. opt pBody
|
|
||||||
// |>> fun ((n, a), b) -> Command(n, defaultArg a [], defaultArg b [])
|
|
||||||
let pInlineCommand =
|
let pInlineCommand =
|
||||||
attempt (pchar '@' >>. many1Chars asciiLetter)
|
attempt (pchar '@' >>. many1Chars asciiLetter)
|
||||||
.>>. opt pArgs
|
.>>. opt pArgs
|
||||||
.>>. opt pBody
|
.>>. opt pBody
|
||||||
|>> fun ((name, argsOpt), bodyOpt) ->
|
|>> fun ((name, argsOpt), bodyOpt) ->
|
||||||
// Hämta den råa tupel-listan från parsern (eller en tom lista om inga argument angavs)
|
|
||||||
let rawArgs = defaultArg argsOpt []
|
let rawArgs = defaultArg argsOpt []
|
||||||
|
let posArgs = rawArgs |> List.choose (fun (k, v) -> if k = "" then Some v else None)
|
||||||
|
let kwargs = rawArgs |> List.filter (fun (k, _) -> k <> "") |> Map.ofList
|
||||||
|
|
||||||
// 1. Filtrera fram positionella argument (de som har en tom nyckel) och plocka ut värdet
|
// dedentNodes anropas här från Utils
|
||||||
let posArgs =
|
let children = defaultArg bodyOpt [] |> Utils.dedentNodes
|
||||||
rawArgs
|
|
||||||
|> List.choose (fun (k, v) -> if k = "" then Some v else None)
|
Command(name, posArgs, kwargs, children)
|
||||||
|
|
||||||
// 2. Filtrera fram namngivna argument och gör om dem till en Map
|
// Nu när pInlineCommand, pExpr och pMultilineCode är definierade
|
||||||
let kwargs =
|
// kan vi skapa pInnerInline
|
||||||
rawArgs
|
let pInnerInline =
|
||||||
|> List.filter (fun (k, _) -> k <> "")
|
choice [
|
||||||
|> Map.ofList
|
attempt pInlineCommand
|
||||||
|
attempt pExpr
|
||||||
// Skapa din nya Command-nod!
|
pMultilineCode
|
||||||
Command(name, posArgs, kwargs, defaultArg bodyOpt [])
|
many1Chars (noneOf "@}") |>> Text
|
||||||
// MÅSTE tilldelas efter att alla pExpr, pInlineCommand etc. är definierade
|
pchar '@' |>> fun _ -> Text "@"
|
||||||
|
]
|
||||||
|
|
||||||
|
// Tilldela värdet till pBodyRef
|
||||||
|
pBodyRef.Value <- between (pstring "{") (pstring "}") (many pInnerInline)
|
||||||
|
|
||||||
|
// Tilldela värdet till pInlineRef
|
||||||
pInlineRef.Value <- choice [
|
pInlineRef.Value <- choice [
|
||||||
pMultilineCode
|
pMultilineCode
|
||||||
pExpr
|
pExpr
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue