Skip to content

Commit

Permalink
Merged with assembly loader fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
rivantsov committed Jan 23, 2012
2 parents 85f2f79 + 0068bf7 commit bb0174d
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 26 deletions.
63 changes: 51 additions & 12 deletions Irony.GrammarExplorer/GrammarLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
* Copyright (c) Roman Ivantsov
* This source code is subject to terms and conditions of the MIT License
* for Irony. A copy of the license can be found in the License.txt file
* at the root of this distribution.
* By using this source code in any fashion, you are agreeing to be bound by the terms of the
* at the root of this distribution.
* By using this source code in any fashion, you are agreeing to be bound by the terms of the
* MIT License.
* You must not remove this notice from this software.
* **********************************************************************************/
Expand All @@ -26,7 +26,36 @@ namespace Irony.GrammarExplorer {
/// </summary>
class GrammarLoader {
private TimeSpan _autoRefreshDelay = TimeSpan.FromMilliseconds(500);
private Dictionary<string, CachedAssembly> _cachedAssemblies = new Dictionary<string, CachedAssembly>();
private Dictionary<string, CachedAssembly> _cachedGrammarAssemblies = new Dictionary<string, CachedAssembly>();
private static HashSet<string> _probingPaths = new HashSet<string>();
private static Dictionary<string, Assembly> _loadedAssemblies = new Dictionary<string, Assembly>();
private static bool _enableAssemblyResolution = false;

static GrammarLoader() {
AppDomain.CurrentDomain.AssemblyLoad += (s, args) => _loadedAssemblies[args.LoadedAssembly.FullName] = args.LoadedAssembly;
AppDomain.CurrentDomain.AssemblyResolve += (s, args) => _enableAssemblyResolution ? FindAssembly(args.Name) : null;
}

static Assembly FindAssembly(string assemblyName) {
if (_loadedAssemblies.ContainsKey(assemblyName)) {
return _loadedAssemblies[assemblyName];
}
// use probing paths to look for assemblies
var fileName = assemblyName.Split(',').First() + ".dll";
foreach (var path in _probingPaths) {
var fullName = Path.Combine(path, fileName);
if (File.Exists(fullName)) {
try {
return _loadedAssemblies[assemblyName] = Assembly.LoadFrom(fullName);
}
catch {
// the file seems to be bad, let's try to find another one
}
}
}
// assembly not found, don't search for it again
return _loadedAssemblies[assemblyName] = null;
}

class CachedAssembly {
public long FileSize;
Expand All @@ -43,8 +72,15 @@ public Grammar CreateGrammar() {
if (SelectedGrammar == null)
return null;

var type = SelectedGrammarAssembly.GetType(SelectedGrammar.TypeName, true, true);
return Activator.CreateInstance(type) as Grammar;
// resolve dependencies while loading and creating grammars
_enableAssemblyResolution = true;
try {
var type = SelectedGrammarAssembly.GetType(SelectedGrammar.TypeName, true, true);
return Activator.CreateInstance(type) as Grammar;
}
finally {
_enableAssemblyResolution = false;
}
}

Assembly SelectedGrammarAssembly {
Expand All @@ -54,24 +90,24 @@ Assembly SelectedGrammarAssembly {

// create assembly cache entry as needed
var location = SelectedGrammar.Location;
if (!_cachedAssemblies.ContainsKey(location)) {
if (!_cachedGrammarAssemblies.ContainsKey(location)) {
var fileInfo = new FileInfo(location);
_cachedAssemblies[location] =
_cachedGrammarAssemblies[location] =
new CachedAssembly {
LastWriteTime = fileInfo.LastWriteTime,
FileSize = fileInfo.Length,
Assembly = null
};

// set up file system watcher
_cachedAssemblies[location].Watcher = CreateFileWatcher(location);
_cachedGrammarAssemblies[location].Watcher = CreateFileWatcher(location);
}

// get loaded assembly from cache if possible
var assembly = _cachedAssemblies[location].Assembly;
var assembly = _cachedGrammarAssemblies[location].Assembly;
if (assembly == null) {
assembly = LoadAssembly(location);
_cachedAssemblies[location].Assembly = assembly;
_cachedGrammarAssemblies[location].Assembly = assembly;
}

return assembly;
Expand All @@ -88,7 +124,7 @@ private FileSystemWatcher CreateFileWatcher(string location) {
return;

// check if assembly was changed indeed to work around multiple FileSystemWatcher event firing
var cacheEntry = _cachedAssemblies[location];
var cacheEntry = _cachedGrammarAssemblies[location];
var fileInfo = new FileInfo(location);
if (cacheEntry.LastWriteTime == fileInfo.LastWriteTime && cacheEntry.FileSize == fileInfo.Length)
return;
Expand All @@ -115,7 +151,10 @@ private void OnAssemblyUpdated(string location) {
AssemblyUpdated(this, EventArgs.Empty);
}

Assembly LoadAssembly(string fileName) {
public static Assembly LoadAssembly(string fileName) {
// save assembly path for dependent assemblies probing
var path = Path.GetDirectoryName(fileName);
_probingPaths.Add(path);
// 1. Assembly.Load doesn't block the file
// 2. Assembly.Load doesn't check if the assembly is already loaded in the current AppDomain
return Assembly.Load(File.ReadAllBytes(fileName));
Expand Down
27 changes: 13 additions & 14 deletions Irony.GrammarExplorer/fmSelectGrammars.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
* Copyright (c) Roman Ivantsov
* This source code is subject to terms and conditions of the MIT License
* for Irony. A copy of the license can be found in the License.txt file
* at the root of this distribution.
* By using this source code in any fashion, you are agreeing to be bound by the terms of the
* at the root of this distribution.
* By using this source code in any fashion, you are agreeing to be bound by the terms of the
* MIT License.
* You must not remove this notice from this software.
* **********************************************************************************/
Expand All @@ -20,7 +20,7 @@
using System.Windows.Forms;
using System.Reflection;
using Irony.Parsing;
using System.IO;
using System.IO;

namespace Irony.GrammarExplorer {
public partial class fmSelectGrammars : Form {
Expand All @@ -30,44 +30,43 @@ public fmSelectGrammars() {

public static GrammarItemList SelectGrammars(string assemblyPath, GrammarItemList loadedGrammars) {
var fromGrammars = LoadGrammars(assemblyPath);
if (fromGrammars == null)
if (fromGrammars == null)
return null;
//fill the listbox and show the form
fmSelectGrammars form = new fmSelectGrammars();
var listbox = form.lstGrammars;
listbox.Sorted = false;
listbox.Sorted = false;
foreach(GrammarItem item in fromGrammars) {
listbox.Items.Add(item);
if (!ContainsGrammar(loadedGrammars, item))
listbox.SetItemChecked(listbox.Items.Count - 1, true);
}
listbox.Sorted = true;
listbox.Sorted = true;

if (form.ShowDialog() != DialogResult.OK) return null;
GrammarItemList result = new GrammarItemList();
for (int i = 0; i < listbox.Items.Count; i++) {
if (listbox.GetItemChecked(i)) {
var item = listbox.Items[i] as GrammarItem;
item._loading = false;
item._loading = false;
result.Add(item);
}
}
return result;
}

private static GrammarItemList LoadGrammars(string assemblyPath) {
Assembly asm = null;
Assembly asm = null;
try {
// enforce loading every time, even if assembly name is not changed
asm = Assembly.Load(File.ReadAllBytes(assemblyPath));
asm = GrammarLoader.LoadAssembly(assemblyPath);
} catch (Exception ex) {
MessageBox.Show("Failed to load assembly: " + ex.Message);
return null;
return null;
}
var types = asm.GetTypes();
var grammars = new GrammarItemList();
foreach (Type t in types) {
if (t.IsAbstract) continue;
if (t.IsAbstract) continue;
if (!t.IsSubclassOf(typeof(Grammar))) continue;
grammars.Add(new GrammarItem(t, assemblyPath));
}
Expand All @@ -82,13 +81,13 @@ private static bool ContainsGrammar(GrammarItemList items, GrammarItem item) {
foreach (var listItem in items)
if (listItem.TypeName == item.TypeName && listItem.Location == item.Location)
return true;
return false;
return false;
}

private void btnCheckUncheck_Click(object sender, EventArgs e) {
bool check = sender == btnCheckAll;
for (int i = 0; i < lstGrammars.Items.Count; i++)
lstGrammars.SetItemChecked(i, check);
lstGrammars.SetItemChecked(i, check);
}

}//class
Expand Down
1 change: 1 addition & 0 deletions Irony_All.2010.sln
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{44A7
Languages\Refal\UnitTests\Sources\order-v1.ref = Languages\Refal\UnitTests\Sources\order-v1.ref
Languages\Refal\UnitTests\Sources\order-v2.ref = Languages\Refal\UnitTests\Sources\order-v2.ref
Languages\Refal\UnitTests\Sources\palyndrome.ref = Languages\Refal\UnitTests\Sources\palyndrome.ref
Languages\Refal\UnitTests\Sources\pretty.ref = Languages\Refal\UnitTests\Sources\pretty.ref
Languages\Refal\UnitTests\Sources\quine-plain.ref = Languages\Refal\UnitTests\Sources\quine-plain.ref
Languages\Refal\UnitTests\Sources\quine-simple.ref = Languages\Refal\UnitTests\Sources\quine-simple.ref
Languages\Refal\UnitTests\Sources\quine-xplained.ref = Languages\Refal\UnitTests\Sources\quine-xplained.ref
Expand Down
6 changes: 6 additions & 0 deletions Languages/Refal/UnitTests/Refal.UnitTests.NUnit.2010.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,12 @@
<ItemGroup>
<EmbeddedResource Include="Sources\brainfuck.txt" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Sources\pretty.ref" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Sources\pretty.txt" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.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.
Expand Down
6 changes: 6 additions & 0 deletions Languages/Refal/UnitTests/Refal.UnitTests.VsTest.2010.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@
<ItemGroup>
<EmbeddedResource Include="Sources\brainfuck.txt" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Sources\pretty.ref" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Sources\pretty.txt" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.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.
Expand Down
6 changes: 6 additions & 0 deletions Languages/Refal/UnitTests/RefalRegressionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ public void RefalTest_ArithmeticTranslator()
RunSampleAndCompareResults("arith.ref", "arith.txt");
}

[TestMethod]
public void RefalTest_PrettyPrintExpressions()
{
RunSampleAndCompareResults("pretty.ref", "pretty.txt");
}

[TestMethod]
public void RefalTest_QuinePlain()
{
Expand Down
29 changes: 29 additions & 0 deletions Languages/Refal/UnitTests/Sources/pretty.ref
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
* Pretty-printer for Refal expressions
* Example program from Refal book, chapter 3.7 (slightly modified)
* http://refal.net/chap3_r5.html

$ENTRY Go { =
<Pprout 'if a<>b'('print a')'do'('if a>0'('a:=a-b')'else break')'while true'>
}

* Pretty print-out of expressions
Pprout {
e.X = <Pprt () e.X>;
}

* <Pprt (e.Blanks-offset) e.Expression>
Pprt {
(e.B) e.1(e.2)e.3 =
<Pr-ne (e.B) e.1>
<Prout e.B '('>
<Pprt (e.B ' ') e.2>
<Prout e.B')'>
<Pprt (e.B) e.3>;
(e.B) e.1 = <Pr-ne (e.B) e.1>;
}

* Print if non-empty
Pr-ne {
(e.B) = ;
(e.B) e.1 = <Prout e.B e.1>;
}
13 changes: 13 additions & 0 deletions Languages/Refal/UnitTests/Sources/pretty.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
if a<>b
(
print a
)
do
(
if a>0
(
a:=a-b
)
else break
)
while true

0 comments on commit bb0174d

Please sign in to comment.