hejsan hoppsan

This commit is contained in:
Linus Björnstam 2026-03-31 13:36:22 +02:00
parent 27a169b30c
commit f971423f04
9 changed files with 264 additions and 98 deletions

View file

@ -10,32 +10,55 @@ open System.Collections.Generic
// 1. AST & Utils
// ==========================================
module Ast =
type Attr = { id: string; classes: string list; kvp: (string * string) list }
type InlineNode =
| Text of string
| RawHtml of string
| Emph of InlineNode list
| Underline of InlineNode list
| Strong of InlineNode list
| Strikeout of InlineNode list
| Superscript of InlineNode list
| Subscript of InlineNode list
| Link of attributes: Attr * target: Target
| Code of attributes: Attr * text: string
| Image of attributes: Attr * altText: InlineNode list * target: Url
| Note of BlockNode list
| SoftBreak
| LineBreak
| Expr of code: string * result: string option
| Element of tag: string * args: (string * string) list * children: InlineNode list
type BlockNode =
| Section of level: int * args: (string * string) list * children: InlineNode list
| Command of tag: string * args: string list * kwargs: Map<string, string> * children: InlineNode list
and BlockNode =
| CodeBlock of attributes: Attr * text: string
| Figure of attributes: Attr * caption: InlineNode list * blocks: BlockNode list
| ListBlock of ListKind
| Plain of InlineNode list
| Paragraph of children: InlineNode list
| Section of level: int * args: (string * string) list * children: InlineNode list
and ListKind =
| Orderedlist of attributes: Attr * start: int * blocksList: (BlockNode list) list
| BulletList of attributes: Attr * blocksList: (BlockNode list) list
and Url = string
and Target = Url * InlineNode list
type Document = BlockNode list
type TagRenderer = Map<string,string> -> (string * string) list -> InlineNode list -> InlineNode
type TagRenderer = Map<string,string>
-> string list
-> Map<string,string>
-> InlineNode list -> InlineNode
let rec stringifyNodes (nodes: InlineNode list) =
let tupleToString (t: string * string) = sprintf "%s=\"%s\"" (fst t) (snd t)
nodes
|> List.map (function
| Text t -> t
| RawHtml h -> h
| Element(tag, args, children) ->
// Omvandla inre taggar till HTML
let attrs = if args.IsEmpty then "" else " " + String.concat " " (List.map tupleToString args)
sprintf "<%s%s>%s</%s>" tag attrs (stringifyNodes children) tag
| Expr(_, Some res) -> res
| Expr(code, None) -> sprintf "@(%s)" code // Fallback om den inte evaluerats
| _ -> failwith "haha"
)
|> String.concat ""
@ -68,7 +91,10 @@ module Utils =
(String.concat "\n" dedented).Trim('\n', '\r')
let positional f: TagRenderer =
fun meta (args: (string*string) list) children -> f meta (List.map snd args) children
fun _ (args: string list) _ children -> f args children
let onlyArgs f =
fun _ args kwargs children -> f args kwargs
let getArgIdx (args: (string*string) list) index defaultVal =
let unnamed = args |> List.filter (fun (k, _) -> k = "")
@ -87,10 +113,6 @@ module Utils =
let withArg2 (k1: string) (d1: string) (k2: string) (d2: string) (f: string -> string -> InlineNode list -> InlineNode) =
fun _ args children -> f (getArg args k1 0 d1) (getArg args k2 1 d2) children
let nameToElement (n:string) : TagRenderer =
fun meta args children -> Element(n, args, children)
@ -112,18 +134,46 @@ module Parser =
let isSection (name: string) = name.EndsWith("section")
let pNewline = newline
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 ","))
let pNamedArg =
// Leta efter "nyckel=värde"
attempt (many1Chars (asciiLetter <|> digit <|> anyOf "-_")
.>> spaces .>> pchar '=' .>> spaces)
.>>. manyChars (noneOf ",]")
|>> fun (k, v) -> (k, v.Trim())
let pPositionalArg =
// Bara "värde"
manyChars (noneOf ",]") |>> fun v -> ("", v.Trim())
let pSingleArg = spaces >>. (pNamedArg <|> pPositionalArg) .>> spaces
let pArgs =
between (pstring "[") (pstring "]") (sepBy pSingleArg (pchar ','))
>>= fun args ->
// Validera att positionella argument alltid kommer först
let rec validate canBePositional = function
| [] -> preturn args // Allt är okej, returnera listan
| ("", _) :: tail ->
if not canBePositional then
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 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>()
@ -174,16 +224,36 @@ module Parser =
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 [])
let pInlineCommand =
attempt (
pchar '@' >>. many1Chars asciiLetter >>= fun name ->
if isSection name then fail "Sektioner är block-element."
else preturn name
)
attempt (pchar '@' >>. many1Chars asciiLetter)
.>>. opt pArgs
.>>. opt pBody
|>> fun ((n, a), b) -> Element(n, defaultArg a [], defaultArg b [])
|>> 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 []
// 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
pInlineRef.Value <- choice [
pMultilineCode
@ -244,47 +314,13 @@ module Execution =
open Ast
let rec transform (metadata: Map<string, string>) (prelude: Map<string, TagRenderer>) (eval: IEvaluator) = function
| Element(n, a, c) when prelude.ContainsKey n ->
prelude.[n] metadata a (c |> List.map (transform metadata prelude eval))
| Element(n, a, c) ->
Element(n, a, c |> List.map (transform metadata prelude eval))
| Command(name, args, kwargs, children) when prelude.ContainsKey name ->
prelude.[name] metadata args kwargs (children |> List.map (transform metadata prelude eval))
| Command(n, _, _ ,_) -> failwithf "%s is not a defined command" n
| Expr(c, _) -> RawHtml (eval.Evaluate c)
| n -> n
module HtmlPrinter =
open Ast
let voidElements = [ "area"; "base"; "br"; "col"; "command"; "embed"; "hr"; "img"; "input"; "keygen"; "link"; "meta"; "param"; "source"; "track"; "wbr"]
let renderAttributes args =
args
|> List.map (fun m -> sprintf "%s=\"%s\"" (fst m) (snd m))
|> String.concat " "
let rec renderInline =
function
| Text t -> WebUtility.HtmlEncode t
| RawHtml h -> h
| Element(t,a,c) when List.contains t voidElements -> sprintf "<%s %s />" t (renderAttributes a)
| Element(t, a, c) -> sprintf "<%s %s>%s</%s>" t (renderAttributes a) (c |> List.map renderInline |> String.concat "") t
| _ -> ""
let render (header, blocks) =
blocks
|> List.map (function
| Paragraph [RawHtml html] ->
html
// 2. (Frivillig) Mer robust guard om parsern råkar lämna kvar
// blanksteg (Text " ") runt ditt @md-block i samma paragraf
| Paragraph nodes when nodes |> List.forall (function
| RawHtml _ -> true
| Text t when System.String.IsNullOrWhiteSpace(t) -> true
| _ -> false) ->
nodes
|> List.choose (function RawHtml h -> Some h | _ -> None)
|> String.concat "\n"
| Paragraph c -> sprintf "<p>%s</p>" (c |> List.map renderInline |> String.concat "")
| Section(l, _, c) -> sprintf "<h%d>%s</h%d>" l (c |> List.map renderInline |> String.concat "") l)
|> String.concat "\n"
module Evaluators =
@ -293,8 +329,8 @@ module Evaluators =
open FSharp.Compiler.Interactive.Shell
type FsiEvaluator() =
let sbOut = new StringBuilder()
let sbErr = new StringBuilder()
let sbOut = StringBuilder()
let sbErr = StringBuilder()
let inStream = new StringReader("")
let outStream = new StringWriter(sbOut)
let errStream = new StringWriter(sbErr)