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

@ -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 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 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<string, unit>()
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<InlineNode list, unit>()
// --- 2. Parentes-parser (för @(...) med sträng-stöd) ---
let pParenBody, pParenBodyRef = createParserForwardedToRef<string, unit>()
// 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