├── .gitignore
├── Lbac.Tests
├── MSTest.runsettings
├── app.config
├── ILTests.fs
├── LexerTests.fs
├── OptimizeILTests.fs
├── CommandLineTests.fs
├── CodeGeneratorTests.fs
├── EndToEndTests.fs
├── Lbac.Tests.fsproj
└── SyntaxTests.fs
├── Local.testsettings
├── Lbac.vsmdi
├── Lbac.Compiler
├── Compiler.fs
├── App.config
├── OptimizeIL.fs
├── CommandLine.fs
├── Program.fs
├── Lex.fs
├── Railway.fs
├── CodeGenerator.fs
├── Lbac.Compiler.fsproj
├── Syntax.fs
├── IL.fs
└── Arg.fs
├── Readme.markdown
├── License.txt
├── TraceAndTestImpact.testsettings
└── Lbac.sln
/.gitignore:
--------------------------------------------------------------------------------
1 | *.suo
2 | *.user
3 |
4 | bin/
5 | obj/
6 |
7 | TestResults
8 |
9 | _ReSharper.Lbac/
--------------------------------------------------------------------------------
/Lbac.Tests/MSTest.runsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | true
5 |
6 |
--------------------------------------------------------------------------------
/Local.testsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 | These are default test settings for a local test run.
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/Lbac.vsmdi:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Lbac.Tests/app.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/Lbac.Compiler/Compiler.fs:
--------------------------------------------------------------------------------
1 | module Compiler
2 | open System.Reflection
3 | open Lex
4 | open Syntax
5 | open IL
6 | open CodeGenerator
7 | open Railway
8 |
9 | let private identity = fun arg -> arg
10 | let private lex = Lex.tokenize
11 | let private parse = Syntax.parse
12 | let private optimize = switch identity
13 | let private codeGen = CodeGenerator.codegen
14 | let private optimizeIL = switch OptimizeIL.optimize
15 | let private methodBuilder = switch(IL.toMethod typedefof)
16 |
17 | let compile = lex >> parse >=> optimize >=> codeGen >=> optimizeIL >=> methodBuilder
18 | let toIl = lex >> parse >=> optimize >=> codeGen >=> optimizeIL
--------------------------------------------------------------------------------
/Lbac.Compiler/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/Lbac.Tests/ILTests.fs:
--------------------------------------------------------------------------------
1 | namespace Lbac.Tests
2 |
3 | open System
4 | open Microsoft.VisualStudio.TestTools.UnitTesting
5 | open CodeGenerator
6 | open Railway
7 | open IL
8 |
9 | []
10 | type ILTests() =
11 | []
12 | member x.``should produce an executable assembly`` () =
13 | let input = { Instructions = [Ldc_I4(1); Ldc_I4(2); instruction.Add]; Locals = [] } // 1 + 2
14 | let expected = 1 + 2
15 | let methodInfo = IL.toMethod typedefof input
16 | let instance = Activator.CreateInstance(methodInfo.DeclaringType)
17 | let actual = methodInfo.Invoke(instance, null) :?> System.Int32
18 | Assert.AreEqual(expected, actual)
19 |
--------------------------------------------------------------------------------
/Lbac.Tests/LexerTests.fs:
--------------------------------------------------------------------------------
1 | namespace Lbac.Tests
2 |
3 | open System
4 | open Microsoft.VisualStudio.TestTools.UnitTesting
5 | open Lex
6 |
7 | []
8 | type LexerTests() =
9 | []
10 | member x.``should lex 11 + 22`` () =
11 | let testVal = "11 + 22"
12 | let expected = [Number(11); Symbol('+'); Number(22)]
13 | let actual = Lex.tokenize testVal
14 | Assert.AreEqual(expected, actual)
15 |
16 | []
17 | member x.``should lex identifier`` () =
18 | let testVal = "foo = 1"
19 | let expected = [Identifier("foo"); Symbol('='); Number(1)]
20 | let actual = Lex.tokenize testVal
21 | Assert.AreEqual(expected, actual)
22 |
23 | []
24 | member x.``should lex multiple lines`` () =
25 | let testVal = "1\n2"
26 | let expected = [Number(1); NewLine; Number(2)]
27 | let actual = Lex.tokenize testVal
28 | Assert.AreEqual(expected, actual)
--------------------------------------------------------------------------------
/Readme.markdown:
--------------------------------------------------------------------------------
1 | Let's Build a Compiler... in F#!
2 | ================================
3 |
4 | F# translation of Jack Crenshaw's Pascal code from his article, "[Let's Build A Compiler](http://compilers.iecc.com/crenshaw/)".
5 |
6 | Have fun!
7 |
8 | Running the Application
9 | =======================
10 |
11 | Start by running the tests.
12 |
13 | You can also use the compiler interactively. Run the project Lbac.Compiler and it will compile a line after you press enter.
14 |
15 | Finally, you can run non-interactively, using files:
16 |
17 | > copy con > Foo.txt
18 | 1+2
19 | ^C
20 | > Lbac.Compiler -i Foo.txt -o Bar.exe
21 |
22 | This produces a console application which returns the result (3!).
23 |
24 | License
25 | =======
26 |
27 | Crenshaw's Pascal and text is Copyright (C) 1988 by Jack W. Crenshaw. All rights reserved. I haven't included any of his material here directly, but it was the basis for this series.
28 |
29 | My F# code and comments are Copyright 2013 by Craig Stuntz.
30 | You may use this code under terms of the MIT license. See License.txt.
--------------------------------------------------------------------------------
/License.txt:
--------------------------------------------------------------------------------
1 | Licensed under the MIT License
2 | Copyright (c) 2011 Craig Stuntz
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5 |
6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 |
8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/Lbac.Compiler/OptimizeIL.fs:
--------------------------------------------------------------------------------
1 | module OptimizeIL
2 |
3 | open IL
4 | open System
5 |
6 | let private maxByte = Convert.ToInt32 System.Byte.MaxValue
7 |
8 | let private optimalShortEncodingFor = function
9 | | Ldc_I4 0 -> Ldc_I4_0
10 | | Ldc_I4 1 -> Ldc_I4_1
11 | | Ldc_I4 2 -> Ldc_I4_2
12 | | Ldc_I4 3 -> Ldc_I4_3
13 | | Ldc_I4 4 -> Ldc_I4_4
14 | | Ldc_I4 5 -> Ldc_I4_5
15 | | Ldc_I4 6 -> Ldc_I4_6
16 | | Ldc_I4 7 -> Ldc_I4_7
17 | | Ldc_I4 8 -> Ldc_I4_8
18 | | Ldloc 0 -> Ldloc_0
19 | | Ldloc 1 -> Ldloc_1
20 | | Ldloc 2 -> Ldloc_2
21 | | Ldloc 3 -> Ldloc_3
22 | | Ldloc i when i <= maxByte -> Ldloc_S(Convert.ToByte(i))
23 | | Stloc 0 -> Stloc_0
24 | | Stloc 1 -> Stloc_1
25 | | Stloc 2 -> Stloc_2
26 | | Stloc 3 -> Stloc_3
27 | | Stloc i when i <= maxByte -> Stloc_S(Convert.ToByte(i))
28 | | instruction -> instruction
29 |
30 | let optimize (m : Method) = { m with Instructions = List.map optimalShortEncodingFor m.Instructions }
--------------------------------------------------------------------------------
/Lbac.Compiler/CommandLine.fs:
--------------------------------------------------------------------------------
1 | module Lbac.CommandLine
2 |
3 | open Microsoft.FSharp.Text
4 |
5 | type Arguments = {
6 | InFile : string option
7 | OutFile : string option
8 | Valid : bool }
9 |
10 | let parse (commandLine : string[], outw : System.IO.TextWriter, error : System.IO.TextWriter) =
11 | let usage s = outw.WriteLine "Compiler [-in filename] [-out filename] [-help]"
12 | let inFile = ref None
13 | let outFile = ref None
14 | let mutable valid = true
15 | let specs =
16 | [ "-i", ArgType.String (fun s -> inFile := Some s), "Name of input file"
17 | "-o", ArgType.String (fun s -> outFile := Some s), "Name of output file"
18 | ] |> List.map (fun (name, action, help) -> ArgInfo(name, action, help))
19 | let current = ref 0
20 | try ArgParser.ParsePartial(current, commandLine, specs, usage) with
21 | | Bad e ->
22 | valid <- false
23 | error.WriteLine(e)
24 | | HelpText h ->
25 | outw.WriteLine(h)
26 | | e ->
27 | reraise()
28 |
29 | { InFile = inFile.Value; OutFile = outFile.Value; Valid = valid }
--------------------------------------------------------------------------------
/Lbac.Compiler/Program.fs:
--------------------------------------------------------------------------------
1 | module Lbac.Program
2 |
3 | open System
4 | open System.IO
5 | open IL
6 | open Compiler
7 | open Railway
8 |
9 | let runInteractive() =
10 | let input = Console.ReadLine()
11 | match Compiler.toIl input with
12 | | Success il -> printfn "%A" il
13 | | Failure s -> Console.Error.WriteLine s
14 | Console.ReadLine() |> ignore
15 |
16 | let runWithFiles inFile outFile =
17 | let input = File.ReadAllText(inFile)
18 | match Compiler.toIl input with
19 | | Success methodWithInstructions ->
20 | let moduleName = match outFile with
21 | | Some s -> s
22 | | None -> IO.Path.ChangeExtension(inFile, ".exe")
23 | let (t, ab) = IL.compileMethod moduleName methodWithInstructions.Instructions typeof
24 | ab.Save(t.Module.ScopeName) |> ignore
25 | | Failure s -> Console.Error.WriteLine s
26 |
27 | []
28 | let main(args) =
29 | let arguments = CommandLine.parse(Array.append [|"Lbac.Compiler.exe"|] args, Console.Out, Console.Error)
30 | if arguments.Valid then
31 | match arguments.InFile with
32 | | Some filename -> runWithFiles filename arguments.OutFile
33 | | _ -> runInteractive()
34 | 0
35 | else
36 | 1
--------------------------------------------------------------------------------
/Lbac.Tests/OptimizeILTests.fs:
--------------------------------------------------------------------------------
1 | namespace Lbac.Tests
2 |
3 | open IL
4 | open OptimizeIL
5 | open Microsoft.VisualStudio.TestTools.UnitTesting
6 |
7 | []
8 | type OptimizeILTests() =
9 |
10 | []
11 | member x.``should change Ldc_I4 2 to LDC_I4_2`` () =
12 | let input = { Instructions = [Ldc_I4 2]; Locals = [] }
13 | let actual = optimize input
14 | Assert.AreEqual([Ldc_I4_2], actual.Instructions)
15 |
16 | []
17 | member x.``should leave Stloc 100000 as Stloc 100000`` () =
18 | let input = { Instructions = [Ldc_I4_1; Stloc 100000; Ldloc 100000]; Locals = [] }
19 | let actual = optimize input
20 | Assert.AreEqual([Ldc_I4_1; Stloc 100000; Ldloc 100000], actual.Instructions)
21 |
22 | []
23 | member x.``should change Stloc 10 to Stloc_S 10`` () =
24 | let input = { Instructions = [Ldc_I4_1; Stloc 10; Ldloc 10]; Locals = [] }
25 | let actual = optimize input
26 | Assert.AreEqual([Ldc_I4_1; Stloc_S 10uy; Ldloc_S 10uy], actual.Instructions)
27 |
28 | []
29 | member x.``should change Stloc 1 to Stloc_1`` () =
30 | let input = { Instructions = [Ldc_I4_1; Stloc 1; Ldloc 1]; Locals = [] }
31 | let actual = optimize input
32 | Assert.AreEqual([Ldc_I4_1; Stloc_1; Ldloc_1], actual.Instructions)
33 |
--------------------------------------------------------------------------------
/Lbac.Compiler/Lex.fs:
--------------------------------------------------------------------------------
1 | module Lex
2 | open System
3 |
4 | type Token =
5 | | Identifier of string
6 | | Number of int
7 | | Symbol of char
8 | | NewLine
9 |
10 | let tokenize (input: string) =
11 | let charIsCrlf c = Set.contains c (set ['\r'; '\n'])
12 | let rec readIdentifier acc = function
13 | | c :: rest when Char.IsLetterOrDigit(c) ->
14 | readIdentifier (acc + c.ToString()) rest
15 | | rest -> Identifier(acc), rest
16 | let rec readNumber acc = function
17 | | d :: rest when Char.IsDigit(d) ->
18 | readNumber (acc + d.ToString()) rest
19 | | rest -> Number(Int32.Parse(acc)), rest
20 | let rec tokenizeLine acc = function
21 | | n :: rest when charIsCrlf n ->
22 | match acc with
23 | | [] -> acc, rest
24 | | _ -> (NewLine :: acc), rest
25 | | d :: rest when Char.IsDigit(d) ->
26 | let num, rest' = readNumber (d.ToString()) rest
27 | tokenizeLine (num :: acc) rest'
28 | | c :: rest when Char.IsLetter(c) ->
29 | let ident, rest' = readIdentifier (c.ToString()) rest
30 | tokenizeLine (ident ::acc) rest'
31 | | [] -> acc, []
32 | | ws :: rest when Char.IsWhiteSpace(ws) -> tokenizeLine acc rest
33 | | c :: rest -> tokenizeLine (Symbol(c) :: acc) rest
34 | let rec beginningOfLine acc input =
35 | match tokenizeLine [] input with
36 | | tokens, [] -> tokens @ acc
37 | | acc', rest -> (beginningOfLine [] rest) @ acc' @acc
38 | List.rev (beginningOfLine [] (List.ofSeq input))
39 |
--------------------------------------------------------------------------------
/TraceAndTestImpact.testsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 | These are test settings for Trace and Test Impact.
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Lbac.Tests/CommandLineTests.fs:
--------------------------------------------------------------------------------
1 | namespace Lbac.Tests
2 |
3 | open Microsoft.VisualStudio.TestTools.UnitTesting
4 | open System.IO
5 | open System.Text
6 | open Lbac
7 |
8 | []
9 | type CommandLineTests() = class
10 | let parse_with_fake_output args =
11 | let outw = new StringBuilder()
12 | let error = new StringBuilder()
13 |
14 | let actual = Lbac.CommandLine.parse(args, new StringWriter(outw), new StringWriter(error))
15 |
16 | (actual, outw.ToString(), error.ToString())
17 |
18 | []
19 | member x.parse_should_find_inFile() =
20 | let expected = "foo.txt"
21 | let args = [| "programname.exe"; "-i"; expected; "-o"; "bar.exe" |]
22 |
23 | let (actual, outw, error) = parse_with_fake_output args
24 |
25 | Assert.AreEqual(expected, actual.InFile.Value)
26 | Assert.IsTrue(System.String.IsNullOrEmpty(outw))
27 | Assert.IsTrue(System.String.IsNullOrEmpty(error))
28 | Assert.IsTrue(actual.Valid)
29 |
30 | []
31 | member x.parse_should_find_outFile() =
32 | let expected = "bar.exe"
33 | let args = [| "programname.exe"; "-i"; "foo.txt"; "-o"; expected |]
34 |
35 | let (actual, out, error) = parse_with_fake_output args
36 |
37 | Assert.AreEqual(expected, actual.OutFile.Value)
38 | Assert.IsTrue(System.String.IsNullOrEmpty(out))
39 | Assert.IsTrue(System.String.IsNullOrEmpty(error))
40 | Assert.IsTrue(actual.Valid)
41 |
42 | []
43 | member x.parse_should_prompt_for_missing_values() =
44 | let expected = Some 1
45 | let args = [| "programname.exe"; "-i" |]
46 |
47 | let (actual, out, error) = parse_with_fake_output args
48 |
49 | Assert.IsFalse(System.String.IsNullOrEmpty(error))
50 | Assert.IsFalse(actual.Valid)
51 |
52 | end
--------------------------------------------------------------------------------
/Lbac.Tests/CodeGeneratorTests.fs:
--------------------------------------------------------------------------------
1 | namespace Lbac.Tests
2 |
3 | open System
4 | open Microsoft.VisualStudio.TestTools.UnitTesting
5 | open CodeGenerator
6 | open Railway
7 | open IL
8 | open Syntax
9 |
10 | []
11 | type CodeGeneratorTests() =
12 | let shouldFailWith (input, locals) error =
13 | match CodeGenerator.codegen({ Lines = [input]; Locals = locals }) with
14 | | Success _ -> Assert.Fail("Expected error.")
15 | | Failure e -> Assert.AreEqual(error, e)
16 |
17 | let shouldProduceIL (input, locals) expected =
18 | let actual = CodeGenerator.codegen({ Lines = [input]; Locals = locals })
19 | match actual with
20 | | Success il -> Assert.AreEqual(expected, il.Instructions)
21 | | Failure e -> Assert.Fail(e)
22 |
23 | let noLocalVariables = Set.empty
24 |
25 | []
26 | member x.``should codegen 1 + 2 * 0`` () =
27 | let input = Expr.Binary(Expr.Binary(Expr.Number(1), Add, Expr.Number(2)), Multiply, Expr.Number(0))
28 | let expected = [Ldc_I4 1; Ldc_I4 2; instruction.Add; Ldc_I4 0; Mul]
29 | (input, noLocalVariables) |> shouldProduceIL <| expected
30 |
31 | []
32 | member x.``should fail on undeclared variable`` () =
33 | let input = Expr.Binary(Expr.Variable("x"), Add, Expr.Number(1))
34 | let expected = "Undeclared variable x"
35 | (input, noLocalVariables) |> shouldFailWith <| expected
36 |
37 | []
38 | member x.``should codegen x + 1`` () =
39 | let input = Expr.Binary(Expr.Variable("x"), Add, Expr.Number(1))
40 | let expected = [DeclareLocal(typedefof); Ldloc 0; Ldc_I4 1; instruction.Add]
41 | (input, Set.singleton("x")) |> shouldProduceIL <| expected
42 |
43 | []
44 | member x.``should codegen x = 1`` () =
45 | let input = Expr.Assign(Expr.Variable("x"), Expr.Number(1))
46 | let expected = [DeclareLocal(typedefof); Ldc_I4(1); Stloc 0]
47 | (input, Set.singleton("x")) |> shouldProduceIL <| expected
--------------------------------------------------------------------------------
/Lbac.Tests/EndToEndTests.fs:
--------------------------------------------------------------------------------
1 | namespace Lbac.Tests
2 |
3 | open System
4 | open Microsoft.VisualStudio.TestTools.UnitTesting
5 | open Compiler
6 | open Railway
7 |
8 | []
9 | type EndToEndTests() =
10 | let execute (mi: System.Reflection.MethodInfo) =
11 | let instance = Activator.CreateInstance(mi.DeclaringType)
12 | mi.Invoke(instance, null) :?> System.Int32
13 |
14 | let shouldEqual output expected =
15 | match output with
16 | | Success mi -> Assert.AreEqual(expected, execute(mi))
17 | | Failure e -> Assert.Fail(e)
18 |
19 | []
20 | member x.``1 + 2 should equal 3`` () =
21 | Compiler.compile("1 + 2") |> shouldEqual <| 3
22 |
23 | []
24 | member x.``1 - 2 should equal -1`` () =
25 | Compiler.compile("1 - 2") |> shouldEqual <| -1
26 |
27 | []
28 | member x.``2 * 3 should equal 6`` () =
29 | Compiler.compile("2 * 3") |> shouldEqual <| 6
30 |
31 | []
32 | member x.``10 - 2 * 3 should equal 4`` () =
33 | Compiler.compile("10 - 2 * 3") |> shouldEqual <| 4
34 |
35 | []
36 | member x.``(10 - 2) * 3 should equal 24`` () =
37 | Compiler.compile("(10 - 2) * 3") |> shouldEqual <| 24
38 |
39 | []
40 | member x.``-2 + 2 should equal 0`` () =
41 | Compiler.compile("-2 + 2") |> shouldEqual <| 0
42 |
43 | []
44 | member x.``2+-3 should equal -1`` () =
45 | Compiler.compile("2+-3") |> shouldEqual <| -1
46 |
47 | []
48 | member x.``6/2 should equal 3`` () =
49 | Compiler.compile("6/2") |> shouldEqual <| 3
50 |
51 | []
52 | member x.``Assign and use local var`` () =
53 | Compiler.compile("x = 1\nx + 2") |> shouldEqual <| 3
54 |
55 | []
56 | member x.``Assign and use 2 local vars`` () =
57 | Compiler.compile("x = 1\ny = 2\nx + y") |> shouldEqual <| 3
58 |
59 | []
60 | member x.``Ignore extra CRLFs, whitespace`` () =
61 | Compiler.compile("""
62 | x = 1
63 | y = 2
64 | x + y
65 | """) |> shouldEqual <| 3
--------------------------------------------------------------------------------
/Lbac.Compiler/Railway.fs:
--------------------------------------------------------------------------------
1 | module Railway
2 | // This code largely "borrowed" from http://fsharpforfunandprofit.com/posts/recipe-part2/
3 |
4 | // the two-track type
5 | type Result<'TSuccess,'TFailure> =
6 | | Success of 'TSuccess
7 | | Failure of 'TFailure
8 |
9 | // convert a single value into a two-track result
10 | let succeed x =
11 | Success x
12 |
13 | // convert a single value into a two-track result
14 | let fail x =
15 | Failure x
16 |
17 | // apply either a success function or failure function
18 | let either successFunc failureFunc twoTrackInput =
19 | match twoTrackInput with
20 | | Success s -> successFunc s
21 | | Failure f -> failureFunc f
22 |
23 | // convert a switch function into a two-track function
24 | let bind f =
25 | either f fail
26 |
27 | // pipe a two-track value into a switch function
28 | let (>>=) x f =
29 | bind f x
30 |
31 | // compose two switches into another switch
32 | let (>=>) s1 s2 =
33 | s1 >> bind s2
34 |
35 | // convert a one-track function into a switch
36 | let switch f =
37 | f >> succeed
38 |
39 | // convert a one-track function into a two-track function
40 | let map f =
41 | either (f >> succeed) fail
42 |
43 | // convert a dead-end function into a one-track function
44 | let tee f x =
45 | f x; x
46 |
47 | // convert a one-track function into a switch with exception handling
48 | let tryCatch f exnHandler x =
49 | try
50 | f x |> succeed
51 | with
52 | | ex -> exnHandler ex |> fail
53 |
54 | // convert two one-track functions into a two-track function
55 | let doubleMap successFunc failureFunc =
56 | either (successFunc >> succeed) (failureFunc >> fail)
57 |
58 | // add two switches in parallel
59 | let plus addSuccess addFailure switch1 switch2 x =
60 | match (switch1 x),(switch2 x) with
61 | | Success s1,Success s2 -> Success (addSuccess s1 s2)
62 | | Failure f1,Success _ -> Failure f1
63 | | Success _ ,Failure f2 -> Failure f2
64 | | Failure f1,Failure f2 -> Failure (addFailure f1 f2)
65 |
66 |
--------------------------------------------------------------------------------
/Lbac.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 2012
4 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E64BB80B-07D0-49EF-8058-8A5D235EB083}"
5 | ProjectSection(SolutionItems) = preProject
6 | Lbac.vsmdi = Lbac.vsmdi
7 | License.txt = License.txt
8 | Local.testsettings = Local.testsettings
9 | Readme.markdown = Readme.markdown
10 | TraceAndTestImpact.testsettings = TraceAndTestImpact.testsettings
11 | EndProjectSection
12 | EndProject
13 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Lbac.Compiler", "Lbac.Compiler\Lbac.Compiler.fsproj", "{C26A4E00-2873-46AB-963C-27ADD62B42EE}"
14 | EndProject
15 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Lbac.Tests", "Lbac.Tests\Lbac.Tests.fsproj", "{1F3EDE6E-7D84-45BD-9B0C-00198760A714}"
16 | EndProject
17 | Global
18 | GlobalSection(TestCaseManagementSettings) = postSolution
19 | CategoryFile = Lbac.vsmdi
20 | EndGlobalSection
21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
22 | Debug|Any CPU = Debug|Any CPU
23 | Debug|Mixed Platforms = Debug|Mixed Platforms
24 | Debug|x86 = Debug|x86
25 | Release|Any CPU = Release|Any CPU
26 | Release|Mixed Platforms = Release|Mixed Platforms
27 | Release|x86 = Release|x86
28 | EndGlobalSection
29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
30 | {C26A4E00-2873-46AB-963C-27ADD62B42EE}.Debug|Any CPU.ActiveCfg = Debug|x86
31 | {C26A4E00-2873-46AB-963C-27ADD62B42EE}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
32 | {C26A4E00-2873-46AB-963C-27ADD62B42EE}.Debug|Mixed Platforms.Build.0 = Debug|x86
33 | {C26A4E00-2873-46AB-963C-27ADD62B42EE}.Debug|x86.ActiveCfg = Debug|x86
34 | {C26A4E00-2873-46AB-963C-27ADD62B42EE}.Debug|x86.Build.0 = Debug|x86
35 | {C26A4E00-2873-46AB-963C-27ADD62B42EE}.Release|Any CPU.ActiveCfg = Release|x86
36 | {C26A4E00-2873-46AB-963C-27ADD62B42EE}.Release|Mixed Platforms.ActiveCfg = Release|x86
37 | {C26A4E00-2873-46AB-963C-27ADD62B42EE}.Release|Mixed Platforms.Build.0 = Release|x86
38 | {C26A4E00-2873-46AB-963C-27ADD62B42EE}.Release|x86.ActiveCfg = Release|x86
39 | {C26A4E00-2873-46AB-963C-27ADD62B42EE}.Release|x86.Build.0 = Release|x86
40 | {1F3EDE6E-7D84-45BD-9B0C-00198760A714}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
41 | {1F3EDE6E-7D84-45BD-9B0C-00198760A714}.Debug|Any CPU.Build.0 = Debug|Any CPU
42 | {1F3EDE6E-7D84-45BD-9B0C-00198760A714}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
43 | {1F3EDE6E-7D84-45BD-9B0C-00198760A714}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
44 | {1F3EDE6E-7D84-45BD-9B0C-00198760A714}.Debug|x86.ActiveCfg = Debug|Any CPU
45 | {1F3EDE6E-7D84-45BD-9B0C-00198760A714}.Release|Any CPU.ActiveCfg = Release|Any CPU
46 | {1F3EDE6E-7D84-45BD-9B0C-00198760A714}.Release|Any CPU.Build.0 = Release|Any CPU
47 | {1F3EDE6E-7D84-45BD-9B0C-00198760A714}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
48 | {1F3EDE6E-7D84-45BD-9B0C-00198760A714}.Release|Mixed Platforms.Build.0 = Release|Any CPU
49 | {1F3EDE6E-7D84-45BD-9B0C-00198760A714}.Release|x86.ActiveCfg = Release|Any CPU
50 | EndGlobalSection
51 | GlobalSection(SolutionProperties) = preSolution
52 | HideSolutionNode = FALSE
53 | EndGlobalSection
54 | EndGlobal
55 |
--------------------------------------------------------------------------------
/Lbac.Tests/Lbac.Tests.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | AnyCPU
6 | 2.0
7 | 1f3ede6e-7d84-45bd-9b0c-00198760a714
8 | Library
9 | Lbac.Tests
10 | Lbac.Tests
11 | 4.5
12 | FsUnitTest
13 |
14 |
15 | true
16 | full
17 | false
18 | false
19 | bin\Debug\
20 | DEBUG;TRACE
21 | 3
22 | bin\Debug\FsUnitTest.XML
23 | x86
24 |
25 |
26 | pdbonly
27 | true
28 | true
29 | bin\Release\
30 | TRACE
31 | 3
32 | bin\Release\FsUnitTest.XML
33 |
34 |
35 |
36 |
37 |
38 | True
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | Lbac.Compiler
58 | {c26a4e00-2873-46ab-963c-27add62b42ee}
59 | True
60 |
61 |
62 |
63 | 11
64 |
65 |
66 |
73 |
--------------------------------------------------------------------------------
/Lbac.Tests/SyntaxTests.fs:
--------------------------------------------------------------------------------
1 | namespace Lbac.Tests
2 |
3 | open System
4 | open Microsoft.VisualStudio.TestTools.UnitTesting
5 | open Railway
6 | open Lex
7 | open Syntax
8 |
9 | []
10 | type SyntaxTests() =
11 | let shouldFailWith input expected =
12 | match Syntax.parse(input) with
13 | | Failure actual -> Assert.AreEqual(expected, actual)
14 | | Success actual -> Assert.Fail(sprintf "Expected %A, got %A." expected actual)
15 |
16 | let shouldParseTo input (expected : Expr list) =
17 | match Syntax.parse(input) with
18 | | Failure actual -> Assert.Fail(actual)
19 | | Success actual -> // This test isn't strictly necessary, but it does improve the error reporting
20 | if actual.Lines.Length <> expected.Length then
21 | Assert.Fail(sprintf "Expected %A, got %A." expected actual.Lines)
22 | let itemMatches exp act = Assert.AreEqual(exp, act, sprintf "Expected %A, got %A." expected act)
23 | List.iter (fun (e, a) -> itemMatches e a) (List.zip expected actual.Lines)
24 |
25 | []
26 | member x.``should parse 11`` () =
27 | [Token.Number(11)] |> shouldParseTo <| [ Expr.Number(11) ]
28 |
29 | []
30 | member x.``should error on garbage`` () =
31 | [Symbol('x')] |> shouldFailWith <| "Identifier expected"
32 |
33 | []
34 | member x.``should parse 11 + 22`` () =
35 | [Token.Number(11); Symbol('+'); Token.Number(22)] |> shouldParseTo <| [ Expr.Binary(Expr.Number(11), Operator.Add, Expr.Number(22)) ]
36 |
37 | []
38 | member x.``should parse 2 * 3`` () =
39 | [Token.Number(2); Symbol('*'); Token.Number(3)] |> shouldParseTo <| [ Expr.Binary(Expr.Number(2), Operator.Multiply, Expr.Number(3)) ]
40 |
41 | []
42 | member x.``(10 - 2 * 3 should fail with mismatched (`` () =
43 | [Symbol('('); Token.Number(10); Symbol('-'); Token.Number(2); Symbol('*'); Token.Number(3)]
44 | |> shouldFailWith <| "')' expected."
45 |
46 | []
47 | member x.``should parse -1`` () =
48 | [Symbol('-'); Token.Number(1)] |> shouldParseTo <| [ Expr.Minus(Expr.Number(1)) ]
49 |
50 | []
51 | member x.``should parse x + 1`` () =
52 | [Identifier("x"); Symbol('='); Token.Number(1); NewLine; Identifier("x"); Symbol('+'); Token.Number(1)]
53 | |> shouldParseTo <| [ Expr.Assign(Expr.Variable("x"), Number(1)); Expr.Binary(Expr.Variable("x"), Operator.Add, Expr.Number(1)) ]
54 |
55 | []
56 | member x.``should parse x() + 1`` () =
57 | [Identifier("x"); Symbol('('); Symbol(')'); Symbol('+'); Token.Number(1)]
58 | |> shouldParseTo <| [ Expr.Binary(Expr.Invoke("x"), Operator.Add, Expr.Number(1)) ]
59 |
60 | []
61 | member x.``should parse x = 1``() =
62 | [Identifier("x"); Symbol('='); Token.Number(1)] |> shouldParseTo <| [ Expr.Assign(Expr.Variable("x"), Expr.Number(1)) ]
63 |
64 | []
65 | member x.``should parse multiple lines``() =
66 | [Token.Number(1); NewLine; Token.Number(2)] |> shouldParseTo <| [ Expr.Number(1); Expr.Number(2) ]
67 |
68 | []
69 | member x.``should fail with undeclared local``() =
70 | [Token.Identifier("x")] |> shouldFailWith <| "Variable \"x\" not declared"
71 |
72 | []
73 | member x.``1 = 2 should fail``() =
74 | [Token.Number(1); Symbol('='); Token.Number(2)] |> shouldFailWith <| "Unexpected token: Symbol '='"
--------------------------------------------------------------------------------
/Lbac.Compiler/CodeGenerator.fs:
--------------------------------------------------------------------------------
1 | module CodeGenerator
2 |
3 | open Railway
4 | open IL
5 | open Syntax
6 |
7 | let private localVarIndex locals name =
8 | List.tryFindIndex (fun l -> System.String.Equals(l, name, System.StringComparison.Ordinal)) locals
9 |
10 | let private codegenAssign locals name =
11 | match localVarIndex locals name with
12 | | None -> Failure ("Undeclared variable " + name)
13 | | Some i -> Success(Stloc i)
14 |
15 | let private codegenOper = function
16 | | Add -> instruction.Add
17 | | Subtract -> instruction.Sub
18 | | Multiply -> instruction.Mul
19 | | Divide -> instruction.Div
20 |
21 | let private tryLdLoc ((locals : string list), (name : string)) =
22 | match localVarIndex locals name with
23 | | None -> Failure ("Undeclared variable " + name)
24 | | Some i -> Success(Ldloc i)
25 |
26 | let rec codegenExpr (acc : Method) (expr : Expr) =
27 | match expr with
28 | | Variable v ->
29 | match tryLdLoc (acc.Locals, v) with
30 | | Success inst -> Success({ acc with Instructions = acc.Instructions @ [inst] })
31 | | Failure err -> Failure err
32 | | Invoke m -> Failure "Sorry; no can do"
33 | | Minus e ->
34 | match codegenExpr acc e with
35 | | Success m -> Success({ m with Instructions = m.Instructions @ [Neg] })
36 | | err -> err
37 | | Number n -> Success({ acc with Instructions = acc.Instructions @ [Ldc_I4 n] })
38 | | Assign (n, rhs) ->
39 | let rhsMethod = codegenExpr { acc with Instructions = [] } rhs
40 | match (n, rhsMethod) with
41 | | (Variable name, Success r) ->
42 | match codegenAssign r.Locals name with
43 | | Success assign_inst ->
44 | let insts = List.concat [ r.Instructions; [ assign_inst ] ]
45 | Success({ Instructions = acc.Instructions @ insts; Locals = r.Locals })
46 | | Failure a -> Failure(a)
47 | | (_, Success r) -> failwith "A variable is required on the left hand side of an assignment." // Should never happen; parser should not emit this
48 | | (_, Failure r) -> rhsMethod
49 | | Binary (lhs, oper, rhs) ->
50 | let lhsMethod = codegenExpr { acc with Instructions = [] } lhs
51 | let rhsMethod = codegenExpr { acc with Instructions = [] } rhs
52 | let operInst = codegenOper oper
53 | match (lhsMethod, rhsMethod) with
54 | | (Success l, Success r) ->
55 | let insts = List.concat [ l.Instructions; r.Instructions; [operInst] ]
56 | let mergeLocals = List.concat [ l.Locals; List.filter (fun i2 -> not (List.exists (fun i1 -> i1 = i2) l.Locals)) r.Locals]
57 | Success({ Instructions = acc.Instructions @ insts; Locals = mergeLocals })
58 | | (Failure l, _) -> lhsMethod
59 | | (_, Failure r) -> rhsMethod
60 | | Error _ -> failwith "Sorry, you can't pass an Error here!"
61 |
62 | let rec codegen (parsed : ParseResult) =
63 | let locals =
64 | parsed.Locals
65 | |> List.ofSeq
66 | let tryCodeGenLine acc line =
67 | match acc, line with
68 | | Success accMethod, expr -> codegenExpr accMethod expr
69 | | Failure err, _ -> Failure err
70 | let localDeclarations = [for name in locals -> DeclareLocal(typedefof)]
71 | let emptyMethod = Success( { Instructions = localDeclarations; Locals = locals } )
72 | List.fold tryCodeGenLine emptyMethod parsed.Lines
73 |
--------------------------------------------------------------------------------
/Lbac.Compiler/Lbac.Compiler.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | x86
6 | 8.0.30703
7 | 2.0
8 | {c26a4e00-2873-46ab-963c-27add62b42ee}
9 | Exe
10 | Lbac.Compiler
11 | Lbac.Compiler
12 | v4.5
13 |
14 |
15 | Lbac.Compiler
16 |
17 |
18 | true
19 | full
20 | false
21 | false
22 | bin\Debug\
23 | DEBUG;TRACE
24 | 3
25 | x86
26 | bin\Debug\Lbac.Compiler.XML
27 |
28 |
29 | true
30 |
31 |
32 |
33 | pdbonly
34 | true
35 | true
36 | bin\Release\
37 | TRACE
38 | 3
39 | x86
40 | bin\Release\Lbac.Compiler.XML
41 |
42 |
43 | 11
44 |
45 |
46 |
47 |
48 |
49 |
50 | Always
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
77 |
--------------------------------------------------------------------------------
/Lbac.Compiler/Syntax.fs:
--------------------------------------------------------------------------------
1 | module Syntax
2 |
3 | open Railway
4 | open Lex
5 |
6 | type Operator =
7 | | Add
8 | | Subtract
9 | | Multiply
10 | | Divide
11 |
12 | type Expr =
13 | | Number of int
14 | | Variable of string
15 | | Invoke of string
16 | | Minus of Expr
17 | | Assign of Expr * Expr
18 | | Binary of Expr * Operator * Expr
19 | | Error of string
20 |
21 | type ParseResult = { Lines: Expr list; Locals: Set }
22 |
23 | /// Converts token list to Success(AST) if valid or Error if not
24 | let rec parseLine (acc: ParseResult) (tokens: Token list): ParseResult =
25 |
26 | /// Returns Some(oper) if head of input is a + or - token
27 | let toAddOp = function
28 | | Token.Symbol('+') :: _ -> Some(Add)
29 | | Token.Symbol('-') :: _ -> Some(Subtract)
30 | | _ -> None
31 |
32 | /// Returns Some(oper) if head of input is a * or / token
33 | let toMulOp = function
34 | | Token.Symbol('*') :: _ -> Some(Multiply)
35 | | Token.Symbol('/') :: _ -> Some(Divide)
36 | | _ -> None
37 |
38 | /// factor ::= (expression) | number | ident
39 | let rec factor acc = function
40 | | Symbol '(' :: rest ->
41 | match expression acc rest with
42 | | exp, Symbol ')' :: rest', acc' -> exp, rest', acc'
43 | | _, rest', acc' -> Error("')' expected."), rest', acc'
44 | | Token.Number n :: ts -> Number(n), ts, acc
45 | | tokens -> ident acc tokens
46 |
47 | /// ident = function() | variable
48 | and ident acc = function
49 | | Identifier id :: rest ->
50 | match rest with
51 | | Symbol '(' :: rest' -> // function invocation
52 | match rest' with
53 | // No support for argument passing yet.
54 | // Only valid function invocation is empty parens: foo()
55 | | Symbol ')' :: rest'' -> Invoke(id), rest'', acc
56 | | _ -> Error ("')' expected"), rest', acc
57 | | _ -> // dereference
58 | match acc.Locals.Contains(id) with
59 | | true -> Variable(id), rest, acc
60 | | false -> Error(sprintf "Variable %A not declared" id), rest, acc
61 | | _ -> Error("Identifier expected"), [], acc
62 |
63 | /// term ::= factor [ mulop factor ]*
64 | and term acc (tokens: Token list) =
65 | let left, rightTokens, acc' = factor acc tokens
66 | match rightTokens, toMulOp rightTokens with
67 | | mulOpSym :: ts, Some mulOp ->
68 | let right, rest, acc'' = expression acc' ts
69 | Binary(left, mulOp, right), rest, acc''
70 | | _ -> left, rightTokens, acc'
71 |
72 | and unary acc = function
73 | | Symbol '-' :: rest ->
74 | match term acc rest with
75 | | e, rest', acc' -> Minus(e), rest', acc'
76 | | tokens -> term acc tokens
77 |
78 | and assign acc = function
79 | | Identifier name :: rest ->
80 | match rest with
81 | | Symbol('=') :: rest' ->
82 | let rhs, rest'', acc' = expression { acc with Locals = acc.Locals.Add(name) } rest'
83 | Some(Assign(Variable(name), rhs), rest'', acc')
84 | | _ -> None
85 | | _ -> None
86 |
87 | /// expression ::= [addop] term [addop term]*
88 | and expression acc tokens =
89 | match assign acc tokens with
90 | | Some assignment -> assignment
91 | | None ->
92 | let leftExpr, rightTokens, acc' = unary acc tokens
93 | match rightTokens, toAddOp rightTokens with
94 | | addOpSym :: ts, Some addOp ->
95 | let right, rest, acc'' = expression acc' ts
96 | Binary(leftExpr, addOp, right), rest, acc''
97 | | _ -> leftExpr, rightTokens, acc'
98 |
99 | let ast, rest, acc' = expression acc tokens
100 |
101 | match rest with
102 | | []
103 | | NewLine :: [] -> { acc' with Lines = acc.Lines @ [ast] } // done!
104 | | NewLine :: rest' -> parseLine { acc' with Lines = acc.Lines @ [ast] } rest'
105 | // If anything remains on line, it's a syntax error
106 | | wrong :: _ -> { acc' with Lines = [ Error("Unexpected token: " + (sprintf "%A" wrong)) ] }
107 |
108 | let tryParse (tokens: Token list): ParseResult =
109 | parseLine { Lines = []; Locals = Set.empty } tokens
110 |
111 | let errorMessagesFor expr =
112 | let rec errorMessagesForImpl acc = function
113 | | Minus expr -> acc @ errorMessagesForImpl acc expr
114 | | Assign (name, value) -> acc @ errorMessagesForImpl acc value @ errorMessagesForImpl acc name
115 | | Binary (left, oper, right) -> acc @ errorMessagesForImpl acc right @ errorMessagesForImpl acc left
116 | | Error message -> [message]
117 | | Number _
118 | | Variable _
119 | | Invoke _ -> acc
120 | errorMessagesForImpl [] expr
121 |
122 | let errorsForLines (lines: Expr list) =
123 | List.collect errorMessagesFor lines
124 |
125 | let parse (tokens: Token list): Result =
126 | let result = tryParse tokens
127 | match errorsForLines result.Lines with
128 | | [] -> Success(result)
129 | | errors -> Failure(System.String.Join(System.Environment.NewLine, errors))
--------------------------------------------------------------------------------
/Lbac.Compiler/IL.fs:
--------------------------------------------------------------------------------
1 | module IL
2 | open System
3 | open System.Reflection
4 | open System.Reflection.Emit
5 |
6 | type instruction =
7 | | Add
8 | | Call of System.Reflection.MethodInfo
9 | | Callvirt of System.Reflection.MethodInfo
10 | | DeclareLocal of System.Type
11 | | Div
12 | | Ldc_I4 of int
13 | | Ldc_I4_0
14 | | Ldc_I4_1
15 | | Ldc_I4_2
16 | | Ldc_I4_3
17 | | Ldc_I4_4
18 | | Ldc_I4_5
19 | | Ldc_I4_6
20 | | Ldc_I4_7
21 | | Ldc_I4_8
22 | | Ldloc of int
23 | | Ldloc_0
24 | | Ldloc_1
25 | | Ldloc_2
26 | | Ldloc_3
27 | | Ldloc_S of byte
28 | | Mul
29 | | Neg
30 | | Newobj of System.Reflection.ConstructorInfo
31 | | Nop
32 | | Pop
33 | | Refanyval
34 | | Ret
35 | | Stloc of int
36 | | Stloc_0
37 | | Stloc_1
38 | | Stloc_2
39 | | Stloc_3
40 | | Stloc_S of byte
41 | | Sub
42 |
43 | type Method = { Instructions: instruction list; Locals: string list }
44 |
45 | let private emit (ilg : Emit.ILGenerator) inst =
46 | match inst with
47 | | Add -> ilg.Emit(OpCodes.Add)
48 | | Call mi -> ilg.Emit(OpCodes.Call, mi)
49 | | Callvirt mi -> ilg.Emit(OpCodes.Callvirt, mi)
50 | | DeclareLocal t -> ignore(ilg.DeclareLocal(t))
51 | | Div -> ilg.Emit(OpCodes.Div)
52 | | Ldc_I4 n -> ilg.Emit(OpCodes.Ldc_I4, n)
53 | | Ldc_I4_0 -> ilg.Emit(OpCodes.Ldc_I4_0)
54 | | Ldc_I4_1 -> ilg.Emit(OpCodes.Ldc_I4_1)
55 | | Ldc_I4_2 -> ilg.Emit(OpCodes.Ldc_I4_2)
56 | | Ldc_I4_3 -> ilg.Emit(OpCodes.Ldc_I4_3)
57 | | Ldc_I4_4 -> ilg.Emit(OpCodes.Ldc_I4_4)
58 | | Ldc_I4_5 -> ilg.Emit(OpCodes.Ldc_I4_5)
59 | | Ldc_I4_6 -> ilg.Emit(OpCodes.Ldc_I4_6)
60 | | Ldc_I4_7 -> ilg.Emit(OpCodes.Ldc_I4_7)
61 | | Ldc_I4_8 -> ilg.Emit(OpCodes.Ldc_I4_8)
62 | | Ldloc i -> ilg.Emit(OpCodes.Ldloc, i)
63 | | Ldloc_0 -> ilg.Emit(OpCodes.Ldloc_0)
64 | | Ldloc_1 -> ilg.Emit(OpCodes.Ldloc_1)
65 | | Ldloc_2 -> ilg.Emit(OpCodes.Ldloc_2)
66 | | Ldloc_3 -> ilg.Emit(OpCodes.Ldloc_3)
67 | | Ldloc_S i -> ilg.Emit(OpCodes.Ldloc_S, i)
68 | | Mul -> ilg.Emit(OpCodes.Mul)
69 | | Neg -> ilg.Emit(OpCodes.Neg)
70 | | Newobj ci -> ilg.Emit(OpCodes.Newobj, ci)
71 | | Nop -> ilg.Emit(OpCodes.Nop)
72 | | Pop -> ilg.Emit(OpCodes.Pop)
73 | | Refanyval -> ilg.Emit(OpCodes.Refanyval)
74 | | Ret -> ilg.Emit(OpCodes.Ret)
75 | | Stloc i -> ilg.Emit(OpCodes.Stloc, i)
76 | | Stloc_0 -> ilg.Emit(OpCodes.Stloc_0)
77 | | Stloc_1 -> ilg.Emit(OpCodes.Stloc_1)
78 | | Stloc_2 -> ilg.Emit(OpCodes.Stloc_2)
79 | | Stloc_3 -> ilg.Emit(OpCodes.Stloc_3)
80 | | Stloc_S i -> ilg.Emit(OpCodes.Stloc_S, i)
81 | | Sub -> ilg.Emit(OpCodes.Sub)
82 |
83 | let private compileEntryPoint (moduleContainingMethod : ModuleBuilder) (methodToCall: MethodBuilder) =
84 | let mb =
85 | let tb =
86 | let className = "Program"
87 | let ta = TypeAttributes.NotPublic ||| TypeAttributes.AutoLayout ||| TypeAttributes.AnsiClass ||| TypeAttributes.BeforeFieldInit
88 | moduleContainingMethod.DefineType(className, ta)
89 | let ma = MethodAttributes.Public ||| MethodAttributes.Static
90 | let methodName = "Main"
91 | tb.DefineMethod(methodName, ma)
92 | let ilg = mb.GetILGenerator() |> emit
93 | let ci = methodToCall.ReflectedType.GetConstructor([||])
94 | ilg (Newobj ci)
95 | ilg (Call methodToCall)
96 | if methodToCall.ReturnType <> null then
97 | ilg (DeclareLocal methodToCall.ReturnType)
98 | ilg Stloc_0
99 | ilg (Ldloc_S 0uy)
100 | let mi = methodToCall.ReturnType.GetMethod("ToString", [||])
101 | ilg (Call mi)
102 | let writeln = typeof.GetMethod("WriteLine", [| typeof |])
103 | ilg (Call writeln)
104 | ilg Ret
105 | mb
106 |
107 | let private entryPointMethodName = "__Main"
108 |
109 | let compileMethod(moduleName: string) (instructions: seq) (methodResultType) =
110 | let assemblyBuilder =
111 | let assemblyName = AssemblyName(moduleName)
112 | AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave)
113 | let moduleBuilder = assemblyBuilder.DefineDynamicModule(moduleName)
114 | let typeBuilder =
115 | let className = "__CompiledMethods"
116 | let typeAttributes = TypeAttributes.Public ||| TypeAttributes.AutoLayout ||| TypeAttributes.AnsiClass ||| TypeAttributes.BeforeFieldInit
117 | moduleBuilder.DefineType(className, typeAttributes)
118 | let methodBuilder =
119 | let methodAttributes = MethodAttributes.Public ||| MethodAttributes.HideBySig
120 | typeBuilder.DefineMethod(entryPointMethodName, methodAttributes, methodResultType, System.Type.EmptyTypes)
121 | let ilg = methodBuilder.GetILGenerator() |> emit
122 | Seq.iter ilg instructions
123 | ilg Ret
124 |
125 | let entryPointType = typeBuilder.CreateType()
126 | let entryPoint = compileEntryPoint moduleBuilder methodBuilder
127 | assemblyBuilder.SetEntryPoint(entryPoint, PEFileKinds.ConsoleApplication)
128 | moduleBuilder.CreateGlobalFunctions()
129 | (entryPointType, assemblyBuilder)
130 |
131 | let toMethod resultType methodWithInstructions =
132 | let moduleName = "test.exe"
133 | let (t, _) = compileMethod moduleName methodWithInstructions.Instructions resultType
134 | t.GetMethod(entryPointMethodName)
135 |
136 | let execute<'TMethodResultType> (instructions, saveAs) =
137 | let moduleName = match saveAs with
138 | | Some s -> s
139 | | None -> "test.exe"
140 | let (entryPointType, assemblyBuilder) = compileMethod moduleName instructions typeof<'TMethodResultType>
141 | if saveAs.IsSome then
142 | assemblyBuilder.Save(entryPointType.Module.ScopeName)
143 | let instance = Activator.CreateInstance(entryPointType)
144 | entryPointType.GetMethod(entryPointMethodName).Invoke(instance, null) :?> 'TMethodResultType
145 |
146 | let print (instructions: seq) =
147 | let p = sprintf "%A"
148 | Seq.map p instructions
--------------------------------------------------------------------------------
/Lbac.Compiler/Arg.fs:
--------------------------------------------------------------------------------
1 | // This file is from the F# PowerPack, which is licensed under the Apache license.
2 | // Original source: http://fsharppowerpack.codeplex.com/SourceControl/changeset/view/66957#685014
3 | // See http://fsharppowerpack.codeplex.com/license, or scroll to end of file for full license
4 |
5 | // (c) Microsoft Corporation 2005-2009.
6 |
7 | #if INTERNALIZED_POWER_PACK
8 | namespace Internal.Utilities
9 | #else
10 | namespace Microsoft.FSharp.Text
11 | #endif
12 |
13 |
14 | type ArgType =
15 | | ClearArg of bool ref
16 | | FloatArg of (float -> unit)
17 | | IntArg of (int -> unit)
18 | | RestArg of (string -> unit)
19 | | SetArg of bool ref
20 | | StringArg of (string -> unit)
21 | | UnitArg of (unit -> unit)
22 | static member Clear r = ClearArg r
23 | static member Float r = FloatArg r
24 | static member Int r = IntArg r
25 | static member Rest r = RestArg r
26 | static member Set r = SetArg r
27 | static member String r = StringArg r
28 | static member Unit r = UnitArg r
29 |
30 |
31 | type ArgInfo (name,action,help) =
32 | member x.Name = name
33 | member x.ArgType = action
34 | member x.HelpText = help
35 |
36 | exception Bad of string
37 | exception HelpText of string
38 |
39 | []
40 | type ArgParser() =
41 | static let getUsage specs u =
42 | let sbuf = new System.Text.StringBuilder 100
43 | let pstring (s:string) = sbuf.Append s |> ignore
44 | let pendline s = pstring s; pstring "\n"
45 | pendline u;
46 | List.iter (fun (arg:ArgInfo) ->
47 | match arg.Name, arg.ArgType, arg.HelpText with
48 | | (s, (UnitArg _ | SetArg _ | ClearArg _), helpText) -> pstring "\t"; pstring s; pstring ": "; pendline helpText
49 | | (s, StringArg _, helpText) -> pstring "\t"; pstring s; pstring " : "; pendline helpText
50 | | (s, IntArg _, helpText) -> pstring "\t"; pstring s; pstring " : "; pendline helpText
51 | | (s, FloatArg _, helpText) -> pstring "\t"; pstring s; pstring " : "; pendline helpText
52 | | (s, RestArg _, helpText) -> pstring "\t"; pstring s; pstring " ...: "; pendline helpText)
53 | specs;
54 | pstring "\t"; pstring "--help"; pstring ": "; pendline "display this list of options";
55 | pstring "\t"; pstring "-help"; pstring ": "; pendline "display this list of options";
56 | sbuf.ToString()
57 |
58 |
59 | static member ParsePartial(cursor,argv,argSpecs:seq,?other,?usageText) =
60 | let other = defaultArg other (fun _ -> ())
61 | let usageText = defaultArg usageText ""
62 | let nargs = Array.length argv
63 | incr cursor;
64 | let argSpecs = argSpecs |> Seq.toList
65 | let specs = argSpecs |> List.map (fun (arg:ArgInfo) -> arg.Name, arg.ArgType)
66 | while !cursor < nargs do
67 | let arg = argv.[!cursor]
68 | let rec findMatchingArg args =
69 | match args with
70 | | ((s, action) :: _) when s = arg ->
71 | let getSecondArg () =
72 | if !cursor + 1 >= nargs then
73 | raise(Bad("option "+s+" needs an argument.\n"+getUsage argSpecs usageText));
74 | argv.[!cursor+1]
75 |
76 | match action with
77 | | UnitArg f ->
78 | f ();
79 | incr cursor
80 | | SetArg f ->
81 | f := true;
82 | incr cursor
83 | | ClearArg f ->
84 | f := false;
85 | incr cursor
86 | | StringArg f->
87 | let arg2 = getSecondArg()
88 | f arg2;
89 | cursor := !cursor + 2
90 | | IntArg f ->
91 | let arg2 = getSecondArg ()
92 | let arg2 = try int32 arg2 with _ -> raise(Bad(getUsage argSpecs usageText)) in
93 | f arg2;
94 | cursor := !cursor + 2;
95 | | FloatArg f ->
96 | let arg2 = getSecondArg()
97 | let arg2 = try float arg2 with _ -> raise(Bad(getUsage argSpecs usageText)) in
98 | f arg2;
99 | cursor := !cursor + 2;
100 | | RestArg f ->
101 | incr cursor;
102 | while !cursor < nargs do
103 | f (argv.[!cursor]);
104 | incr cursor;
105 |
106 | | (_ :: more) -> findMatchingArg more
107 | | [] ->
108 | if arg = "-help" || arg = "--help" || arg = "/help" || arg = "/help" || arg = "/?" then
109 | raise (HelpText (getUsage argSpecs usageText))
110 | // Note: for '/abc/def' does not count as an argument
111 | // Note: '/abc' does
112 | elif arg.Length>0 && (arg.[0] = '-' || (arg.[0] = '/' && not (arg.Length > 1 && arg.[1..].Contains ("/")))) then
113 | raise (Bad ("unrecognized argument: "+ arg + "\n" + getUsage argSpecs usageText))
114 | else
115 | other arg;
116 | incr cursor
117 | findMatchingArg specs
118 |
119 | static member Usage (specs,?usage) =
120 | let usage = defaultArg usage ""
121 | System.Console.Error.WriteLine (getUsage (Seq.toList specs) usage)
122 |
123 | #if FX_NO_COMMAND_LINE_ARGS
124 | #else
125 | static member Parse (specs,?other,?usageText) =
126 | let current = ref 0
127 | let argv = System.Environment.GetCommandLineArgs()
128 | try ArgParser.ParsePartial (current, argv, specs, ?other=other, ?usageText=usageText)
129 | with
130 | | Bad h
131 | | HelpText h ->
132 | System.Console.Error.WriteLine h;
133 | System.Console.Error.Flush();
134 | System.Environment.Exit(1);
135 | | e ->
136 | reraise()
137 | #endif
138 |
139 | (*
140 | Apache License
141 | Version 2.0, January 2004
142 | http://www.apache.org/licenses/
143 |
144 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
145 |
146 | 1. Definitions.
147 |
148 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
149 |
150 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
151 |
152 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
153 |
154 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
155 |
156 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
157 |
158 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
159 |
160 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
161 |
162 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
163 |
164 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
165 |
166 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
167 |
168 | 2. Grant of Copyright License.
169 |
170 | Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
171 |
172 | 3. Grant of Patent License.
173 |
174 | Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
175 |
176 | 4. Redistribution.
177 |
178 | You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
179 |
180 | 1. You must give any other recipients of the Work or Derivative Works a copy of this License; and
181 |
182 | 2. You must cause any modified files to carry prominent notices stating that You changed the files; and
183 |
184 | 3. You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
185 |
186 | 4. If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
187 |
188 | You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
189 |
190 | 5. Submission of Contributions.
191 |
192 | Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
193 |
194 | 6. Trademarks.
195 |
196 | This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
197 |
198 | 7. Disclaimer of Warranty.
199 |
200 | Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
201 |
202 | 8. Limitation of Liability.
203 |
204 | In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
205 |
206 | 9. Accepting Warranty or Additional Liability.
207 |
208 | While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
209 | *)
--------------------------------------------------------------------------------