From 8402ff3a3cf035da660a6e53379f83ae6745cea5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20Bj=C3=B6rnstam?= Date: Tue, 31 Mar 2026 15:22:16 +0200 Subject: [PATCH] amessage --- src/Bif/Program.fs | 1 + src/Bif/document.fib | 4 + src/FibLib/HtmlPrinter.fs | 4 +- src/FibLib/Library.fs | 160 +++++++++++++++++++------------------- 4 files changed, 85 insertions(+), 84 deletions(-) diff --git a/src/Bif/Program.fs b/src/Bif/Program.fs index 6aa1d39..4f6aa83 100644 --- a/src/Bif/Program.fs +++ b/src/Bif/Program.fs @@ -24,6 +24,7 @@ let myPrelude : Map = Text "[Fel: quotient kräver två argument]" "bold", fun _ _ _ children -> Strong(children) + "kursiv", fun _ _ _ children -> Emph(children) "image", image "value", value "link", link diff --git a/src/Bif/document.fib b/src/Bif/document.fib index d49ee33..9bd2254 100644 --- a/src/Bif/document.fib +++ b/src/Bif/document.fib @@ -42,4 +42,8 @@ Tredje saken Första anropet: @(increment()) Andra anropet: @(increment()) +@bold{detta är ibdenrerat. + detta också. + hej} + Test av utskrift: @value[date] diff --git a/src/FibLib/HtmlPrinter.fs b/src/FibLib/HtmlPrinter.fs index 72546fd..d435718 100644 --- a/src/FibLib/HtmlPrinter.fs +++ b/src/FibLib/HtmlPrinter.fs @@ -14,8 +14,8 @@ module HtmlPrinter = let rec renderInline = function | Text t -> WebUtility.HtmlEncode t + | Strong(t) -> sprintf "%s" (renderInline t) | RawHtml h -> - printfn "%s" "hej" h | _ -> "" @@ -56,4 +56,4 @@ module HtmlPrinter = | Plain nodes -> List.map renderInline nodes |> String.concat " " blocks |> List.map renderNode - |> String.concat "\n" \ No newline at end of file + |> String.concat "\n" diff --git a/src/FibLib/Library.fs b/src/FibLib/Library.fs index b22a1ca..87fa452 100644 --- a/src/FibLib/Library.fs +++ b/src/FibLib/Library.fs @@ -66,29 +66,57 @@ module Ast = module Utils = open Ast - let smartDedent (input: string) = - let lines = input.Replace("\r\n", "\n").Split '\n' |> List.ofArray - - // 1. Hitta den minsta indenteringen bland alla rader som har text + let dedentNodes (nodes: InlineNode list) = + let fullText = + nodes |> List.choose (function Text t -> Some t | _ -> None) |> String.concat "" + + 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 = - lines - |> List.filter (fun l -> not (System.String.IsNullOrWhiteSpace(l))) - |> List.map (fun l -> l.Length - l.TrimStart().Length) - |> function - | [] -> 0 - | indents -> List.min indents + if lines.Length <= 1 then 0 + else + lines |> Array.skip 1 + |> Array.filter (fun l -> not (System.String.IsNullOrWhiteSpace(l))) + |> Array.map (fun l -> l.Length - l.TrimStart().Length) + |> function [||] -> 0 | arr -> Array.min arr - // 2. Dra av exakt så många mellanslag från alla rader - // för att dessa inte ska vara med i det slutgiltiga dokumentet. - let dedented = - lines - |> List.map (fun l -> - if System.String.IsNullOrWhiteSpace(l) then "" - else l.Substring(minIndent) + let mutable isFirstText = true + let indentStr = "\n" + String.replicate minIndent " " + + // 2. Applicera formateringen + let dedented = + nodes |> List.map (function + | 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 - (String.concat "\n" dedented).Trim('\n', '\r') + + // 3. Städa bort överflödiga radbrytningar och blanksteg i ytterkanterna + 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 = 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." else validate true tail | _ :: tail -> - validate false tail // Vi hittade ett namngivet argument, inga fler positionella tillåts + validate false tail 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) --- - let pRawBody, pRawBodyRef = createParserForwardedToRef() - pRawBodyRef.Value <- - many (choice [ - 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 + // Lägg till en referens för pBody högst upp bland dina referenser + // (Bör ligga precis under let pInline, pInlineRef = ...) + let pBody, pBodyRef = createParserForwardedToRef() // --- 2. Parentes-parser (för @(...) med sträng-stöd) --- let pParenBody, pParenBodyRef = createParserForwardedToRef() - // En inre parser som känner igen F#-strängar och escape-tecken (\") let pFSharpString = let normal = many1Chars (noneOf "\"\\") let escaped = pstring "\\" >>. anyChar |>> sprintf "\\%c" pstring "\"" .>>. manyStrings (normal <|> escaped) .>>. pstring "\"" |>> 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 <- manyStrings (choice [ pFSharpString @@ -222,39 +219,38 @@ module Parser = // --- Övriga inline-parsers --- let pMultilineCode = pstring "@\"\"\"" >>. manyCharsTill anyChar (pstring "\"\"\"") - |>> fun c -> Expr(Utils.smartDedent c, None) - - // let pInlineCommand = - // 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 []) + |>> fun c -> Expr(c, None) + + // pInlineCommand använder nu forward-referensen pBodyRef let pInlineCommand = attempt (pchar '@' >>. many1Chars asciiLetter) .>>. opt pArgs - .>>. opt pBody + .>>. opt pBody |>> 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 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 - let posArgs = - rawArgs - |> List.choose (fun (k, v) -> if k = "" then Some v else None) - - // 2. Filtrera fram namngivna argument och gör om dem till en Map - let kwargs = - rawArgs - |> List.filter (fun (k, _) -> k <> "") - |> Map.ofList - - // Skapa din nya Command-nod! - Command(name, posArgs, kwargs, defaultArg bodyOpt []) - // MÅSTE tilldelas efter att alla pExpr, pInlineCommand etc. är definierade + // dedentNodes anropas här från Utils + let children = defaultArg bodyOpt [] |> Utils.dedentNodes + + Command(name, posArgs, kwargs, children) + + // Nu när pInlineCommand, pExpr och pMultilineCode är definierade + // kan vi skapa pInnerInline + let pInnerInline = + choice [ + attempt pInlineCommand + attempt pExpr + pMultilineCode + many1Chars (noneOf "@}") |>> Text + pchar '@' |>> fun _ -> Text "@" + ] + + // Tilldela värdet till pBodyRef + pBodyRef.Value <- between (pstring "{") (pstring "}") (many pInnerInline) + + // Tilldela värdet till pInlineRef pInlineRef.Value <- choice [ pMultilineCode pExpr