Files
IPS_G-assignment/Fasto/Fasto.fsx
NikolajDanger e46e002e9f
2022-06-01 15:53:24 +02:00

255 lines
9.2 KiB
Plaintext

// The Fasto compiler command-line interface.
//
// This is the main program for when this compiler is turned into an executable.
// It ties together all the compiler modules. You can build the compiler by
// running 'make' or 'dotnet build Fasto' in the top-level directory.
open System.Text
open FSharp.Text.Lexing
open System.IO
open AbSyn
open Inlining
open DeadFunctionRemoval
open DeadBindingRemoval
open CopyConstPropFold
// YOU DO NOT NEED TO UNDERSTAND THIS; IT IS A HACK: State machine for getting
// line and position numbers from a Parser error string. This is really nasty.
// The problem is that we can only define the needed 'parse_error_rich' function
// in Parser.fsp at the top of the file, which means that we have not defined
// the actual tokens yet, so we cannot pattern match on them for extracting
// their source code positions, although we *can* print them. An alternative
// solution is to inject a proper 'parse_error_rich' function in the bottom of
// the generated Parser.fs.
exception SyntaxError of int * int
let printPos (errString : string) : unit =
let rec state3 (s : string) (p : int) (lin : string) (col : int) =
(* read digits until not *)
let c = s.[p]
if System.Char.IsDigit c
then state3 s (p-1) (System.Char.ToString c + lin) col
else raise (SyntaxError (System.Int32.Parse lin, col))
let rec state2 (s : string) (p : int) (col : string) =
(* skip from position until digit *)
let c = s.[p]
if System.Char.IsDigit c
then state3 s (p-1) (System.Char.ToString c) (System.Int32.Parse col)
else state2 s (p-1) col
let rec state1 (s : string) (p : int) (col : string) =
(* read digits until not *)
let c = s.[p]
if System.Char.IsDigit c
then state1 s (p-1) (System.Char.ToString c + col)
else state2 s (p-1) col
let rec state0 (s : string) (p : int) =
(* skip from end until digit *)
let c = s.[p]
if System.Char.IsDigit c
then state1 s (p-1) (System.Char.ToString c)
else state0 s (p-1)
state0 errString (String.length errString - 1)
// Parse program from string.
let parseString (s : string) : AbSyn.UntypedProg =
Parser.Prog Lexer.Token
<| LexBuffer<_>.FromBytes (Encoding.UTF8.GetBytes s)
////////////////////
/// Usage helper ///
////////////////////
let usage =
[ " fasto -i tests/fib.fo\n"
; " Run 'fib.fo' in the 'tests' directory in interpreted mode.\n"
; " and print the result.\n"
; "\n"
; " fasto -r tests/fib.fo\n"
; " Run 'fib.fo' in interpreted mode, but do not print the result.\n"
; "\n"
; " fasto -c tests/fib.fo\n"
; " Compile 'tests/fib.fo' into the MIPS program 'tests/fib.asm'.\n"
; "\n"
; " fasto -o [opts] tests/fib.fo\n"
; " Compile the optimised 'tests/fib.fo' into 'tests/fib.asm'.\n"
; "\n"
; " fasto -p [opts] tests/fib.fo\n"
; " Optimise 'tests/fib.fo' and print the result on standard output.\n"
; " <opts> is a sequence of characters corresponding to optimisation\n"
; " passes, where: \n"
; " i - Inline functions.\n"
; " c - Copy propagation and constant folding.\n"
; " d - Remove dead bindings.\n"
; " D - Remove dead functions.\n"
]
// Print error message to the standard error channel.
let errorMessage (message : string) : Unit =
printfn "%s\n" message
let errorMessage' (errorType : string, message : string, line : int, col : int) =
printfn "%s: %s at line %d, column %d" errorType message line col
let bad () : Unit =
errorMessage "Unknown command-line arguments. Usage:\n"
errorMessage (usage |> List.fold (+) "")
exception FileProblem of string
// Remove trailing .fo from filename.
let sanitiseFilename (argFilename : string) : string =
if argFilename.EndsWith ".fo"
then argFilename.Substring(0, (String.length argFilename)-3)
else argFilename
// Save the content of a string to file.
let saveFile (filename : string) (content : string) : Unit =
try
let outFile = File.CreateText filename
// Generate code here.
outFile.Write content
outFile.Close()
with
| ex ->
printfn "Problem writing file named: %s, error: %s,\n where content is:\n %s\n" filename ex.Message content
System.Environment.Exit 1
let parseFastoFile (filename : string) : AbSyn.UntypedProg =
let txt = try // read text from file given as parameter with added extension
let inStream = File.OpenText (filename + ".fo")
let txt = inStream.ReadToEnd()
inStream.Close()
txt
with // or return empty string
| ex -> ""
if txt <> "" then // valid file content
let program =
try
parseString txt
with
| Lexer.LexicalError (info,(line,col)) ->
printfn "%s at line %d, position %d\n" info line col
System.Environment.Exit 1
[]
| ex ->
if ex.Message = "parse error"
then printPos Parser.ErrorContextDescriptor
else printfn "%s" ex.Message
System.Environment.Exit 1
[]
program
else failwith "Invalid file name or empty file"
let compile (filename : string) optimiser : Unit =
let pgm = parseFastoFile filename
let pgm_decorated = TypeChecker.checkProg pgm
//printfn "%A" pgm_decorated
let pgm_optimised = optimiser pgm_decorated
//printfn "%A" pgm_optimised
let mips_code = CodeGen.compile pgm_optimised
let mips_code_text = Mips.ppMipsProg mips_code
saveFile (filename + ".asm") mips_code_text
let interpret (filename : string) : Unit =
let pgm = parseFastoFile filename
printfn "Program is:\n\n%s" (AbSyn.ppProg pgm)
printfn "\n+-----------------------------------------+"
printfn "\n| You might need to enter some input now. |"
printfn "\n+-----------------------------------------+"
printfn "\n"
let res = Interpreter.evalProg pgm
printfn "\n\nResult of 'main': %s\n" (AbSyn.ppVal 0 res)
let interpretSimple (filename : string) : AbSyn.Value =
let pgm = parseFastoFile filename
Interpreter.evalProg pgm
let printOptimised (argFilename : string) optimiser : Unit =
let pgm = parseFastoFile argFilename
let pgm_decorated = TypeChecker.checkProg pgm
let pgm_optimised = optimiser pgm_decorated
printfn "%s\n" (ppProg pgm_optimised)
let withoutOptimisations (prog : TypedProg) = prog
let defaultOptimisations (prog : TypedProg) =
(removeDeadFunction <<
removeDeadBindings <<
optimiseProgram <<
inlineOptimiseProgram) prog
type opt = char
let rec extractOpts (opts : opt list) =
match opts with
| [] -> Some (fun x -> x)
| opt::opls ->
let extractOpt (op : opt) =
match op with
| 'i' -> Some inlineOptimiseProgram
| 'c' -> Some optimiseProgram
| 'd' -> Some removeDeadBindings
| 'D' -> Some removeDeadFunction
| _ -> None
match (extractOpt opt, extractOpts opls) with
| (Some opt', Some opts') -> Some (fun x -> opts' (opt' x))
| _ -> None
let explode (s:string) =
[for c in s -> c]
[<EntryPoint>]
let main (paramList: string[]) : int =
try
match paramList with
| [|"-i"; file|] -> interpret (sanitiseFilename file)
| [|"-r"; file|] -> let res = interpretSimple (sanitiseFilename file)
printfn "\n\nResult of 'main': %s\n" (AbSyn.ppVal 0 res)
| [|"-c"; file|] -> compile (sanitiseFilename file) (fun x -> x)
| [|"-o"; file|] -> compile (sanitiseFilename file) defaultOptimisations
| [|"-o"; opts; file|] ->
match extractOpts (explode opts) with
| Some (opts') -> compile (sanitiseFilename file) opts'
| None -> bad ()
| [|"-P"; file|] ->
printOptimised (sanitiseFilename file) withoutOptimisations
| [|"-p"; file|] ->
printOptimised (sanitiseFilename file) defaultOptimisations
| [|"-p"; opts; file|] ->
match extractOpts (explode opts) with
| Some (opts') -> printOptimised (sanitiseFilename file) opts'
| None -> bad ()
| _ -> bad ()
0
with
| SyntaxError (line, col) ->
errorMessage' ("Parse error", "Error", line, col)
System.Environment.Exit 1
1
| Lexer.LexicalError (message, (line, col)) ->
errorMessage' ("Lexical error", message, line, col)
System.Environment.Exit 1
1
| Interpreter.MyError (message, (line, col)) ->
errorMessage' ("Interpreter error", message, line, col)
System.Environment.Exit 1
1
| CodeGen.MyError (message, (line, col)) ->
errorMessage' ("Code generator error", message, line, col)
System.Environment.Exit 1
1
| TypeChecker.MyError (message, (line, col)) ->
errorMessage' ("Type error", message, line, col)
System.Environment.Exit 1
1
| FileProblem filename ->
errorMessage ("There was a problem with the file: " + filename)
System.Environment.Exit 1
1