Skip to content

Commit

Permalink
Add Wrapper Generator to generator wrapper classes usable from C#
Browse files Browse the repository at this point in the history
  • Loading branch information
hmansell committed Sep 6, 2012
1 parent 70cccb5 commit 090ddc1
Show file tree
Hide file tree
Showing 4 changed files with 227 additions and 0 deletions.
6 changes: 6 additions & 0 deletions RProvider.sln
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "RProvider", "RProvider.fsproj", "{5624F098-BE3F-4C8E-B50F-1CFC9E1E0708}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "RWrapperGenerator", "RWrapperGenerator\RWrapperGenerator.fsproj", "{66A390A5-526A-4BD1-B876-E7425B643C70}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -13,6 +15,10 @@ Global
{5624F098-BE3F-4C8E-B50F-1CFC9E1E0708}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5624F098-BE3F-4C8E-B50F-1CFC9E1E0708}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5624F098-BE3F-4C8E-B50F-1CFC9E1E0708}.Release|Any CPU.Build.0 = Release|Any CPU
{66A390A5-526A-4BD1-B876-E7425B643C70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{66A390A5-526A-4BD1-B876-E7425B643C70}.Debug|Any CPU.Build.0 = Debug|Any CPU
{66A390A5-526A-4BD1-B876-E7425B643C70}.Release|Any CPU.ActiveCfg = Release|Any CPU
{66A390A5-526A-4BD1-B876-E7425B643C70}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
17 changes: 17 additions & 0 deletions RWrapperGenerator/App.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="FSharp.Core" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="4.0.0.0" newVersion="4.3.0.0"/>
<bindingRedirect oldVersion="2.3.5.0" newVersion="4.3.0.0"/>
<bindingRedirect oldVersion="2.0.0.0" newVersion="4.3.0.0"/>

</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
122 changes: 122 additions & 0 deletions RWrapperGenerator/Program.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
open RProvider.RInterop
open RProvider.RInteropInternal
open System.IO
open System
open System.Collections.Generic
open System.Globalization

let preamble = @"
using System;
using RDotNet;
using RProvider;
namespace RWrappers {
"

// Attempt to encode C# identifier rules from http://msdn.microsoft.com/en-us/library/aa664670(v=VS.71).aspx
let isValidIdentifier (identifier: string) =
let isLetterCharacter c =
match Char.GetUnicodeCategory(c) with
| UnicodeCategory.LowercaseLetter | UnicodeCategory.UppercaseLetter | UnicodeCategory.LetterNumber
| UnicodeCategory.ModifierLetter | UnicodeCategory.OtherLetter | UnicodeCategory.TitlecaseLetter -> true
| _ -> false

let isValidStartCharacter c =
isLetterCharacter c || c = '_'

let isValidPartCharacter c =
match Char.GetUnicodeCategory(c) with
| UnicodeCategory.DecimalDigitNumber | UnicodeCategory.ConnectorPunctuation | UnicodeCategory.SpacingCombiningMark | UnicodeCategory.Format -> true
| _ when isLetterCharacter c -> true
| _ -> false

let identifier = identifier.Replace("_","__").Replace(".", "_")
isValidStartCharacter identifier.[0] && Array.TrueForAll(identifier.ToCharArray(), Predicate<char> isValidPartCharacter)

// C# Keywords from http://msdn.microsoft.com/en-us/library/aa664671(v=vs.71)
let isKeyword = function
| "abstract" | "as" | "base" | "bool" | "break" | "byte" | "case" | "catch" | "char" | "checked" | "class"
| "const" | "continue" | "decimal" | "default" | "delegate" | "do" | "double" | "else" | "enum" | "event"
| "explicit" | "extern" | "false" | "finally" | "fixed" | "float" | "for" | "foreach" | "goto" | "if" | "implicit"
| "in" | "int" | "interface" | "internal" | "is" | "lock" | "long" | "namespace" | "new" | "null" | "object" | "operator"
| "out" | "override" | "params" | "private" | "protected" | "public" | "readonly" | "ref" | "return" | "sbyte" | "sealed"
| "short" | "sizeof" | "stackalloc" | "static" | "string" | "struct" | "switch" | "this" | "throw" | "true" | "try"
| "typeof" | "uint" | "ulong" | "unchecked" | "unsafe" | "ushort" | "using" | "virtual" | "void" | "volatile" | "while" -> true
| _ -> false

let rec safeName name =
if isKeyword name then
// This prefixes allows us to use keywords as identifiers
"@" + name
else
// Dots are so common that we replace with underscore, and we replace underscore with double-underscore.
name.Replace("_","__").Replace(".", "_")

let internal generateProperty (writer: TextWriter) (packageName: string) (name: string) =
fprintfn writer "\t\tpublic static SymbolicExpression %s { get { return RInterop.call(\"%s\", \"%s\", emptyArr, emptyArr); } }\n" (safeName name) packageName name

let internal generateFunction (writer: TextWriter) (packageName: string) (name: string) (args: RParameter list) (hasVarArgs: bool) =
let argArr = [|
for arg in args -> sprintf "object %s = null" (safeName arg)
if hasVarArgs then yield "params object[] paramArray"
|]

fprintfn writer "\t\tpublic static SymbolicExpression %s(%s) {" (safeName name) (String.Join(", ", argArr))
let argsPass = String.Join(", ", args |> List.map safeName |> List.toArray)
fprintfn writer "\t\t\tvar namedArgs = new object[] { %s };" argsPass
fprintfn writer "\t\t\treturn RInterop.call(\"%s\", \"%s\", namedArgs, %s);" packageName name (if hasVarArgs then "paramArray" else "emptyArr")
fprintfn writer "\t\t}\n"

let generatePackage (writer: TextWriter) (exposedNames: HashSet<string>) (packageName: string) =
//fprintfn writer "\tpublic class %s {" (safeName packageName)
//fprintfn writer "\t\tprivate static object[] emptyArr = new object[0];"

loadPackage packageName

for name, rval in Map.toSeq (getBindings packageName) do
let name = if exposedNames.Contains(name) then packageName + "." + name else name

match rval with
| RValue.Value -> generateProperty writer packageName name
| RValue.Function(args, hasVarArgs) -> if isValidIdentifier name then generateFunction writer packageName name args hasVarArgs

ignore <| exposedNames.Add(name)

//fprintfn writer "}"

let parseArgs (argv: string[]) =
Map.ofSeq [
for arg in argv ->
let idx = arg.IndexOf("=")
if idx < 0 then
let k = if arg.[0] = '/' then arg.Substring(1) else arg
k, k
else
let k = if arg.[0] = '/' then arg.Substring(1, idx - 1) else arg.Substring(0, idx)
let v = arg.Substring(idx + 1)
k, v
]

[<EntryPoint>]
let main argv =
let args = parseArgs argv
let outFile, packages =
match (args.TryFind "outFile"), (args.TryFind "packages") with
| Some outFile, Some packages -> outFile, packages
| _ -> failwithf "Usage:\n\nRWrapperGenerator outFile=<outfilename> packages=<PackageNames>\nwhere PackageNames is a comma-separated list of R package names"

use writer = new StreamWriter(outFile)

fprintfn writer "%s" preamble

let exposedNames = new HashSet<string>()

fprintfn writer "\tpublic class R {"
fprintfn writer "\t\tprivate static object[] emptyArr = new object[0];\n"

Seq.iter (generatePackage writer exposedNames) (packages.Split([|','|], StringSplitOptions.RemoveEmptyEntries))

fprintfn writer "\t}"
fprintfn writer "}"

0
82 changes: 82 additions & 0 deletions RWrapperGenerator/RWrapperGenerator.fsproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>66a390a5-526a-4bd1-b876-e7425b643c70</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>RWrapperGenerator</RootNamespace>
<AssemblyName>RWrapperGenerator</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<Name>RWrapperGenerator</Name>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<Tailcalls>false</Tailcalls>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<WarningLevel>3</WarningLevel>
<PlatformTarget>AnyCPU</PlatformTarget>
<DocumentationFile>bin\Debug\RWrapperGenerator.XML</DocumentationFile>
<Prefer32Bit>true</Prefer32Bit>
<StartArguments>/outFile=c:\temp\out.cs /packges=base,stats,graphics,grDevices,tseries,zoo</StartArguments>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<Tailcalls>true</Tailcalls>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<WarningLevel>3</WarningLevel>
<PlatformTarget>AnyCPU</PlatformTarget>
<DocumentationFile>bin\Release\RWrapperGenerator.XML</DocumentationFile>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="mscorlib" />
<Reference Include="FSharp.Core, Version=4.3.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<Private>True</Private>
</Reference>
<Reference Include="RDotNet">
<HintPath>..\..\Sierra\3rdParty\RProvider\RDotNet.dll</HintPath>
</Reference>
<Reference Include="RDotNet.NativeLibrary">
<HintPath>..\..\Sierra\3rdParty\RProvider\RDotNet.NativeLibrary.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.ComponentModel.Composition" />
<Reference Include="System.Core" />
<Reference Include="System.Numerics" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<Compile Include="..\ProvidedTypes-0.2.fsi">
<Link>ProvidedTypes-0.2.fsi</Link>
</Compile>
<Compile Include="..\ProvidedTypes-0.2.fs">
<Link>ProvidedTypes-0.2.fs</Link>
</Compile>
<Compile Include="..\CharacterDeviceInterceptor.fs">
<Link>CharacterDeviceInterceptor.fs</Link>
</Compile>
<Compile Include="..\RInterop.fs">
<Link>RInterop.fs</Link>
</Compile>
<Compile Include="Program.fs" />
</ItemGroup>
<PropertyGroup>
<MinimumVisualStudioVersion Condition="'$(MinimumVisualStudioVersion)' == ''">11</MinimumVisualStudioVersion>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets" Condition=" Exists('$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets')" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

0 comments on commit 090ddc1

Please sign in to comment.