This commit is contained in:
Linus Björnstam 2026-03-31 15:22:16 +02:00
parent f971423f04
commit 8402ff3a3c
4 changed files with 85 additions and 84 deletions

View file

@ -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

View file

@ -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]

View file

@ -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"

View file

@ -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 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 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