This commit is contained in:
Charles Torre 2021-11-14 15:25:30 -08:00
Родитель f293fce6e2
Коммит d2ab209eac
119 изменённых файлов: 14715 добавлений и 51 удалений

Просмотреть файл

@ -208,38 +208,28 @@ namespace FHTest
// Parse rules
Module module = Module.Parse("Module", repairRules, functorTable);
_ = new GuanQueryDispatcher(module);
// Create guan query
var queryDispatcher = new GuanQueryDispatcher(module);
/* Bind default arguments to goal (Mitigate). */
List<CompoundTerm> compoundTerms = new List<CompoundTerm>();
// Mitigate is the head of the rules used in FH. It's the Goal that Guan will try to accomplish based on the logical expressions (or subgoals) that form a given rule.
CompoundTerm compoundTerm = new CompoundTerm("Mitigate");
_ = new List<CompoundTerm>();
CompoundTerm term = new CompoundTerm("Mitigate");
/* Pass default arguments in query. */
// The type of metric that led FO to generate the unhealthy evaluation for the entity (App, Node, VM, Replica, etc).
// We rename these for brevity for simplified use in logic rule composition (e;g., MetricName="Threads" instead of MetricName="Total Thread Count").
foHealthData.Metric = FOErrorWarningCodes.GetMetricNameFromCode(foHealthData.Code);
// These args hold the related values supplied by FO and are available anywhere Mitigate is used as a rule head.
compoundTerm.AddArgument(new Constant(foHealthData.ApplicationName), RepairConstants.AppName);
compoundTerm.AddArgument(new Constant(foHealthData.Code), RepairConstants.FOErrorCode);
compoundTerm.AddArgument(new Constant(foHealthData.Metric), RepairConstants.MetricName);
compoundTerm.AddArgument(new Constant(foHealthData.NodeName), RepairConstants.NodeName);
compoundTerm.AddArgument(new Constant(foHealthData.NodeType), RepairConstants.NodeType);
compoundTerm.AddArgument(new Constant(foHealthData.OS), RepairConstants.OS);
compoundTerm.AddArgument(new Constant(foHealthData.ServiceName), RepairConstants.ServiceName);
compoundTerm.AddArgument(new Constant(foHealthData.SystemServiceProcessName), RepairConstants.SystemServiceProcessName);
compoundTerm.AddArgument(new Constant(foHealthData.PartitionId), RepairConstants.PartitionId);
compoundTerm.AddArgument(new Constant(foHealthData.ReplicaId), RepairConstants.ReplicaOrInstanceId);
compoundTerm.AddArgument(new Constant(Convert.ToInt64(foHealthData.Value)), RepairConstants.MetricValue);
compoundTerms.Add(compoundTerm);
term.AddArgument(new Constant(foHealthData.ApplicationName), RepairConstants.AppName);
term.AddArgument(new Constant(foHealthData.Code), RepairConstants.FOErrorCode);
term.AddArgument(new Constant(foHealthData.Metric), RepairConstants.MetricName);
term.AddArgument(new Constant(foHealthData.NodeName), RepairConstants.NodeName);
term.AddArgument(new Constant(foHealthData.NodeType), RepairConstants.NodeType);
term.AddArgument(new Constant(foHealthData.OS), RepairConstants.OS);
term.AddArgument(new Constant(foHealthData.ServiceName), RepairConstants.ServiceName);
term.AddArgument(new Constant(foHealthData.SystemServiceProcessName), RepairConstants.SystemServiceProcessName);
term.AddArgument(new Constant(foHealthData.PartitionId), RepairConstants.PartitionId);
term.AddArgument(new Constant(foHealthData.ReplicaId), RepairConstants.ReplicaOrInstanceId);
// Run Guan query.
// This is where the supplied rules are run with FO data that may or may not lead to mitigation of some supported SF entity in trouble (or a VM/Disk).
return await queryDispatcher.RunQueryAsync(compoundTerms).ConfigureAwait(false);
return true;
}
private List<string> ParseRulesFile(List<string> rules)

Просмотреть файл

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29411.108
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FabricHealer", "FabricHealer\FabricHealer.csproj", "{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"
EndProject
@ -25,6 +25,8 @@ Project("{A07B5EB6-E848-4116-A8D0-A826331D98C6}") = "FabricHealerApp", "FabricHe
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TelemetryLib", "TelemetryLib\TelemetryLib.csproj", "{7BC6991F-C840-413E-B1CD-4025947CF5FA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Guan", "guan\Guan\Guan.csproj", "{A55170F5-2ED3-4B52-BF0B-0937009E70F2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -69,6 +71,14 @@ Global
{7BC6991F-C840-413E-B1CD-4025947CF5FA}.Release|Any CPU.Build.0 = Release|Any CPU
{7BC6991F-C840-413E-B1CD-4025947CF5FA}.Release|x64.ActiveCfg = Release|x64
{7BC6991F-C840-413E-B1CD-4025947CF5FA}.Release|x64.Build.0 = Release|x64
{A55170F5-2ED3-4B52-BF0B-0937009E70F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A55170F5-2ED3-4B52-BF0B-0937009E70F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A55170F5-2ED3-4B52-BF0B-0937009E70F2}.Debug|x64.ActiveCfg = Debug|Any CPU
{A55170F5-2ED3-4B52-BF0B-0937009E70F2}.Debug|x64.Build.0 = Debug|Any CPU
{A55170F5-2ED3-4B52-BF0B-0937009E70F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A55170F5-2ED3-4B52-BF0B-0937009E70F2}.Release|Any CPU.Build.0 = Release|Any CPU
{A55170F5-2ED3-4B52-BF0B-0937009E70F2}.Release|x64.ActiveCfg = Release|Any CPU
{A55170F5-2ED3-4B52-BF0B-0937009E70F2}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

Просмотреть файл

@ -35,7 +35,7 @@
<PackageReference Include="System.Text.Json" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\guan\Guan\Guan.csproj" />
<ProjectReference Include="..\guan\Guan\Guan.csproj" />
<ProjectReference Include="..\TelemetryLib\TelemetryLib.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -76,6 +76,7 @@ Mitigate(AppName="fabric:/PortEater42", MetricName="EphemeralPorts", MetricValue
Mitigate(AppName="fabric:/CpuStress", MetricName="CpuPercent", MetricValue=?MetricValue) :- ?MetricValue >= 15,
GetHealthEventHistory(?HealthEventCount, TimeRange=00:15:00),
EmitMessage("event count: {0}", ?HealthEventCount),
?HealthEventCount >= 3,
TimeScopedRestartReplica(1, 00:15:00).
@ -93,16 +94,16 @@ Mitigate(MetricName="CpuPercent", MetricValue=?MetricValue) :- ?MetricValue >= 9
## File Handles
## This is also an example of how to use the Guan system predicate, Contains. It takes two string args, the first is the substring to look for in the second arg.
## So, arg1 is the needle and arg 2 is the haystack.
## This is also an example of how to use the Guan system predicate, match (and notmatch). It takes two args, the first is the source, the second is a regular expression,
## in this case, just a string of characters (no special regex characters).
## In practice, for this scenario, you would just pass the target app name string into Mitigate, Mitigate(AppName="fabric:/ClusterObserver", ...), for example.
## Use of the Contains GuanFunc here is just an example of how to use it. Note that in Prolog, this type of substring matching capability could be expressed as an internal predicate
## Use of the match function here is just an example of how to use it. Note that in Prolog, this type of substring matching capability could be expressed as an internal predicate
## in a much more complex format, in terms of human readability: substring(X,S) :-append(_,T,S), append(X,_,T), X \= [].
## This is because in Prolog a string is a list of characters. In Guan, a string is just a built-in .NET object, System.String, which already defines a Contains() function.
## This is because in Prolog a string is a list of characters. In Guan, a string is just a built-in .NET object, System.String.
## This is one of the great things about Guan: It's .NET all the way down.
## Constrained on AppName, MetricName (FileHandles). 5 repairs within 1 hour window.
Mitigate(AppName=?AppName, MetricName="FileHandles") :- Contains("ClusterObserver", ?AppName),
Mitigate(AppName=?AppName, MetricName="FileHandles") :- match(?AppName, "ClusterObserver"),
TimeScopedRestartCodePackage(5, 01:00:00).
## Constrained on AppName, MetricName (FileHandles). 5 repairs within 1 hour window.

Просмотреть файл

@ -83,11 +83,11 @@ Mitigate(MetricName="Threads", SystemServiceProcessName=?SysProcName) :- not(?Sy
TimeScopedRestartFabricSystemProcess(5, 01:00:00).
## Open File Handles - Specific system service process on either Windows or Linux.
## Note the use of the StringContains GuanFunc here, as Windows and Linux have different process names for some components,
## Note the use of the match GuanFunc here, as Windows and Linux have different process names for some components,
## that is, different file extensions (.dll, .exe or none, for Linux, for example, and *always* none for Windows (because FO emits just the name, not including the extension, for Windows procs)).
## Restart the offending Fabric system process named FabricGateway, regardless of OS.
Mitigate(MetricName="FileHandles", SystemServiceProcessName=?SysProcName) :- Contains("FabricGateway", ?SysProcName),
Mitigate(MetricName="FileHandles", SystemServiceProcessName=?SysProcName) :- match(?SysProcName, "FabricGateway"),
TimeScopedRestartFabricSystemProcess(15, 01:00:00).

Просмотреть файл

@ -142,7 +142,7 @@ namespace FabricHealer.Repair.Guan
}
private CheckFolderSizePredicateType(string name)
: base(name, true, 2, 2)
: base(name, true, 1, 2)
{
}

Просмотреть файл

@ -37,7 +37,7 @@ namespace FabricHealer.Repair.Guan
"(xx:yy:zz format, for example 00:30:00 represents 30 minutes).");
}
var interval = (TimeSpan)Input.Arguments[0].Value.GetEffectiveTerm().GetObjectValue();
var interval = (TimeSpan)Input.Arguments[0].Value.GetObjectValue();
if (interval == TimeSpan.MinValue)
{

Просмотреть файл

@ -50,7 +50,7 @@ namespace FabricHealer.Repair.Guan
for (int i = 1; i < Input.Arguments.Count; i++)
{
args[i - 1] = Input.Arguments[i].Value.GetEffectiveTerm();
args[i - 1] = Input.Arguments[i].Value.GetEffectiveTerm().GetObjectValue();
}
output = string.Format(CultureInfo.InvariantCulture, format, args);

Просмотреть файл

@ -25,10 +25,10 @@ namespace FabricHealer.Repair.Guan
}
protected override Task<Term> GetNextTermAsync()
protected override async Task<Term> GetNextTermAsync()
{
long eventCount = 0;
var timeRange = (TimeSpan)Input.Arguments[1].Value.GetEffectiveTerm().GetObjectValue();
var timeRange = (TimeSpan)Input.Arguments[1].Value.GetObjectValue();
if (timeRange > TimeSpan.MinValue)
{
@ -36,20 +36,21 @@ namespace FabricHealer.Repair.Guan
}
else
{
string message = "You must supply a valid TimeSpan argument for GetHealthEventHistoryPredicateType. Default result has been supplied (0).";
string message = "You must supply a valid TimeSpan argument for GetHealthEventHistoryPredicateType. " +
"Default result has been supplied (0).";
RepairTaskManager.TelemetryUtilities.EmitTelemetryEtwHealthEventAsync(
await RepairTaskManager.TelemetryUtilities.EmitTelemetryEtwHealthEventAsync(
LogLevel.Info,
$"GetHealthEventHistoryPredicateType::{FOHealthData.HealthEventProperty}",
message,
RepairTaskManager.Token).GetAwaiter().GetResult();
RepairTaskManager.Token).ConfigureAwait(false);
}
var result = new CompoundTerm();
// By using "0" for name here means the rule can pass any name for this named variable arg as long as it is consistently used as such in the corresponding rule.
result.AddArgument(new Constant(eventCount), "0");
return Task.FromResult<Term>(result);
return result;
}
}
@ -62,7 +63,7 @@ namespace FabricHealer.Repair.Guan
}
private GetHealthEventHistoryPredicateType(string name)
: base(name, true, 2, 2)
: base(name, true, 0, 2)
{
}

Просмотреть файл

@ -66,7 +66,7 @@ namespace FabricHealer.Repair.Guan
}
private GetRepairHistoryPredicateType(string name)
: base(name, true, 2, 2)
: base(name, true, 1, 2)
{
}

Просмотреть файл

@ -55,11 +55,11 @@ namespace FabricHealer.Repair.Guan
switch (typeString)
{
case "System.TimeSpan":
repairConfiguration.RepairPolicy.MaxTimePostRepairHealthCheck = (TimeSpan)Input.Arguments[i].Value.GetEffectiveTerm().GetObjectValue();
repairConfiguration.RepairPolicy.MaxTimePostRepairHealthCheck = (TimeSpan)Input.Arguments[i].Value.GetObjectValue();
break;
case "System.Boolean":
repairConfiguration.RepairPolicy.DoHealthChecks = (bool)Input.Arguments[0].Value.GetEffectiveTerm().GetObjectValue();
repairConfiguration.RepairPolicy.DoHealthChecks = (bool)Input.Arguments[0].Value.GetObjectValue();
break;
default:

Просмотреть файл

@ -52,11 +52,11 @@ namespace FabricHealer.Repair.Guan
switch (typeString)
{
case "System.TimeSpan":
repairConfiguration.RepairPolicy.MaxTimePostRepairHealthCheck = (TimeSpan)Input.Arguments[i].Value.GetEffectiveTerm().GetObjectValue();
repairConfiguration.RepairPolicy.MaxTimePostRepairHealthCheck = (TimeSpan)Input.Arguments[i].Value.GetObjectValue();
break;
case "System.Boolean":
repairConfiguration.RepairPolicy.DoHealthChecks = (bool)Input.Arguments[0].Value.GetEffectiveTerm().GetObjectValue();
repairConfiguration.RepairPolicy.DoHealthChecks = (bool)Input.Arguments[0].Value.GetObjectValue();
break;
default:

Просмотреть файл

@ -5,7 +5,7 @@
<!-- FabricHealerManager Settings -->
<Parameter Name="AutoMitigationEnabled" DefaultValue="true" />
<Parameter Name="EventSourceProviderEnabled" DefaultValue="true" />
<Parameter Name="MonitorLoopSleepSeconds" DefaultValue="30" />
<Parameter Name="MonitorLoopSleepSeconds" DefaultValue="5" />
<Parameter Name="TelemetryProviderEnabled" DefaultValue="true" />
<!-- Set VerboseLoggingEnabled to true if you want detailed local logging and telemetry/ETW with repair data.
This data will live in a folder named RepairData, which will be created in your LocalLogPath directory.

63
guan/.gitattributes поставляемый Normal file
Просмотреть файл

@ -0,0 +1,63 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain

341
guan/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,341 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- Backup*.rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
/nuget.exe

15
guan/Build-Guan.ps1 Normal file
Просмотреть файл

@ -0,0 +1,15 @@
$ErrorActionPreference = "Stop"
$Configuration="Release"
[string] $scriptPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
try {
Push-Location $scriptPath
Remove-Item $scriptPath\Guan\bin\release\netstandard2.1\ -Recurse -Force -EA SilentlyContinue
dotnet publish $scriptPath\Guan\Guan.csproj -o bin\release\netstandard2.1 -c $Configuration
}
finally {
Pop-Location
}

Просмотреть файл

@ -0,0 +1,45 @@
function Install-Nuget {
# Path to Latest nuget.exe on nuget.org
$source = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe"
# Save file to top level directory in repo
$destination = "$scriptPath\nuget.exe"
#Download the file
if (-Not [System.IO.File]::Exists($destination)) {
Invoke-WebRequest -Uri $source -OutFile $destination
}
}
function Build-Nuget {
param (
[string]
$packageId,
[string]
$basePath
)
[string] $nugetSpecTemplate = [System.IO.File]::ReadAllText([System.IO.Path]::Combine($scriptPath, "Guan.nuspec.template"))
[string] $nugetSpecPath = "$scriptPath\Guan\bin\release\netstandard2.1\$($packageId).nuspec"
[System.IO.File]::WriteAllText($nugetSpecPath, $nugetSpecTemplate.Replace("%PACKAGE_ID%", $packageId).Replace("%ROOT_PATH%", $scriptPath))
.\nuget.exe pack $nugetSpecPath -basepath $basePath -OutputDirectory bin\release\Guan\Nugets -properties NoWarn=NU5100
}
[string] $scriptPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
try {
Push-Location $scriptPath
Install-Nuget
Build-Nuget "Microsoft.Logic.Guan" "$scriptPath\Guan\bin\release\netstandard2.1"
}
finally {
Pop-Location
}

27
guan/Guan.nuspec.template Normal file
Просмотреть файл

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata minClientVersion="3.3.0">
<id>%PACKAGE_ID%</id>
<version>1.0.0-Preview</version>
<authors>Microsoft</authors>
<license type="expression">MIT</license>
<requireLicenseAcceptance>true</requireLicenseAcceptance>
<title>Guan: Logic Programming Library for .NET</title>
<icon>icon.png</icon>
<language>en-US</language>
<description>This package contains Guan, a general purpose logic programming system written in C#. It is a close approximation of Prolog with extended capabilities and some differences. Guan employs Prolog style syntax for writing logic rules. It enables easy interop between such rules with regular C# code and the vast .NET Base Class Library.</description>
<contentFiles>
<files include="**" buildAction="None" copyToOutput="true" />
</contentFiles>
<dependencies>
<group targetFramework=".NETStandard2.1" />
</dependencies>
<projectUrl>https://github.com/microsoft/guan</projectUrl>
<tags>guan logic-programming netstandard20 csharp</tags>
<copyright>© Microsoft Corporation. All rights reserved.</copyright>
</metadata>
<files>
<file src="Guan.dll" target="lib\netstandard2.1" />
<file src="%ROOT_PATH%\icon.png" target="" />
</files>
</package>

31
guan/Guan.sln Normal file
Просмотреть файл

@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31828.109
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Guan", "Guan\Guan.csproj", "{D2E013B1-29AA-48EE-9898-E2592C464511}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GuanTest", "GuanTest\GuanTest.csproj", "{4042B149-C837-4D0E-8FD9-7C58F9FD5CAA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{D2E013B1-29AA-48EE-9898-E2592C464511}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D2E013B1-29AA-48EE-9898-E2592C464511}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D2E013B1-29AA-48EE-9898-E2592C464511}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D2E013B1-29AA-48EE-9898-E2592C464511}.Release|Any CPU.Build.0 = Release|Any CPU
{4042B149-C837-4D0E-8FD9-7C58F9FD5CAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4042B149-C837-4D0E-8FD9-7C58F9FD5CAA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4042B149-C837-4D0E-8FD9-7C58F9FD5CAA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4042B149-C837-4D0E-8FD9-7C58F9FD5CAA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {7C1E58D3-EF58-422A-9311-2880445E2A02}
EndGlobalSection
EndGlobal

23
guan/Guan/Guan.csproj Normal file
Просмотреть файл

@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<OutputType>Library</OutputType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="System.Text.Json" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,64 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System;
internal class AddFunc : StandaloneFunc
{
public static readonly AddFunc Singleton = new AddFunc();
private MathsFunc mathsAdd;
private AddFunc()
: base("add")
{
this.mathsAdd = new MathsFunc("add", '+');
}
public override object Invoke(object[] args)
{
if (args.Length == 2)
{
if (args[0] is DateTime)
{
return this.AddDateTime((DateTime)args[0], args[1]);
}
object result = this.mathsAdd.TryInvoke(args);
if (result != null)
{
return result;
}
}
string s = string.Empty;
foreach (object arg in args)
{
if (arg != null)
{
s += arg.ToString();
}
}
return s;
}
private DateTime AddDateTime(DateTime arg1, object arg2)
{
if (arg2 is TimeSpan)
{
return arg1 + (TimeSpan)arg2;
}
if (arg2 is string)
{
return arg1 + TimeSpan.Parse((string)arg2);
}
throw new ArgumentException("Invalid argument to add to DateTime: " + arg2);
}
}
}

Просмотреть файл

@ -0,0 +1,67 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System.Collections.Generic;
/// <summary>
/// And function.
/// </summary>
public class AndFunc : StandaloneFunc
{
public static readonly AndFunc Singleton = new AndFunc();
private AndFunc()
: base("and")
{
}
public override object Invoke(object[] args)
{
foreach (object arg in args)
{
if (!(bool)arg)
{
return false;
}
}
return true;
}
internal override object Invoke(IPropertyContext context, List<GuanExpression> args)
{
foreach (GuanExpression exp in args)
{
object arg = exp.Evaluate(context);
if ((arg == null) || !((bool)arg))
{
return false;
}
}
return true;
}
internal override GuanFunc Bind(List<GuanExpression> args)
{
for (int i = args.Count - 1; i >= 0; i--)
{
bool arg;
if (args[i].GetLiteral(out arg))
{
if (!arg)
{
return Literal.False;
}
args.RemoveAt(i);
}
}
return this.Collapse(args);
}
}
}

Просмотреть файл

@ -0,0 +1,22 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
/// <summary>
/// Meta data about term argument.
/// </summary>
public class ArgumentDescription
{
public ArgumentDescription(string text, bool required)
{
this.Text = text;
this.Required = required;
}
public string Text { get; set; }
public bool Required { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,53 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System.Threading.Tasks;
/// <summary>
/// Predicate type for assert.
/// </summary>
internal class AssertPredicateType : PredicateType
{
public static readonly AssertPredicateType Assert = new AssertPredicateType("assert");
public static readonly AssertPredicateType Asserta = new AssertPredicateType("asserta");
public static readonly AssertPredicateType Assertz = new AssertPredicateType("assertz");
private AssertPredicateType(string name)
: base(name, true, 1, 1)
{
}
public override PredicateResolver CreateResolver(CompoundTerm input, Constraint constraint, QueryContext context)
{
return new Resolver(this != Asserta, input, constraint, context);
}
private class Resolver : BooleanPredicateResolver
{
private bool append;
public Resolver(bool append, CompoundTerm input, Constraint constraint, QueryContext context)
: base(input, constraint, context)
{
this.append = append;
}
protected override Task<bool> CheckAsync()
{
Term term = this.GetInputArgument(0);
CompoundTerm compound = term as CompoundTerm;
if (compound == null || !compound.IsGround())
{
throw new GuanException("Argument of assert must be a ground compound term: {0}", term);
}
this.Context.Assert((CompoundTerm)compound.GetGroundedCopy(), this.append);
return Task.FromResult(true);
}
}
}
}

Просмотреть файл

@ -0,0 +1,89 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
public static class AutoLoadResource
{
public static void LoadResources(Type resourceType)
{
MethodInfo addMethod = resourceType.GetMethod("Add", BindingFlags.Static | BindingFlags.Public);
Assembly thisAssembly = Assembly.GetExecutingAssembly();
string thisAssemblyName = thisAssembly.GetName().Name;
LoadAssembly(thisAssembly, resourceType, addMethod);
HashSet<string> assemblies = new HashSet<string>();
string externalAssemblies = Utility.GetConfig("ExternalAssemblies", string.Empty);
if (!string.IsNullOrEmpty(externalAssemblies))
{
foreach (string name in externalAssemblies.Split(','))
{
_ = assemblies.Add(name);
}
}
string assemblyPath = thisAssembly.Location;
string directoryPath = Path.GetDirectoryName(assemblyPath);
foreach (string path in Directory.GetFiles(directoryPath, "*.dll"))
{
string assemblyName = Path.GetFileNameWithoutExtension(path);
if ((assemblyName != thisAssemblyName) && assemblies.Contains(assemblyName))
{
try
{
Assembly assembly = Assembly.LoadFrom(path);
LoadAssembly(assembly, resourceType, addMethod);
}
catch (BadImageFormatException)
{
}
catch (FileLoadException)
{
}
catch (ReflectionTypeLoadException)
{
}
}
}
}
private static void LoadAssembly(Assembly assembly, Type resourceType, MethodInfo addMethod)
{
foreach (Type type in assembly.GetTypes())
{
BindingFlags flags = BindingFlags.Static | BindingFlags.Public;
FieldInfo[] fields = type.GetFields(flags);
foreach (FieldInfo field in fields)
{
if (field.FieldType.IsSubclassOf(resourceType))
{
LoadMember(type, field.Name, field.FieldType, BindingFlags.GetField, addMethod);
}
}
PropertyInfo[] properties = type.GetProperties(flags);
foreach (PropertyInfo property in properties)
{
if (property.PropertyType.IsSubclassOf(resourceType))
{
LoadMember(type, property.Name, property.PropertyType, BindingFlags.GetProperty, addMethod);
}
}
}
}
private static void LoadMember(Type type, string name, Type memberType, BindingFlags flags, MethodInfo addMethod)
{
object instance = type.InvokeMember(name, flags, null, null, null, CultureInfo.InvariantCulture);
_ = addMethod.Invoke(null, new object[] { instance });
}
}
}

Просмотреть файл

@ -0,0 +1,52 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System;
/// <summary>
/// Functions that take two arguments.
/// </summary>
public abstract class BinaryFunc : StandaloneFunc
{
/// <summary>
/// Initializes a new instance of the <see cref="BinaryFunc"/> class.
/// Constructor.
/// </summary>
/// <param name="name">Name of the function.</param>
protected BinaryFunc(string name)
: base(name)
{
}
/// <summary>
/// Invoke the function.
/// </summary>
/// <param name="args">The arguments.</param>
/// <returns>Function result.</returns>
public override object Invoke(object[] args)
{
if (args == null)
{
throw new ArgumentNullException("args");
}
if (args.Length != 2)
{
throw new ArgumentException("Invalid argument for BinaryFunc: " + this);
}
return this.InvokeBinary(args[0], args[1]);
}
/// <summary>
/// Invoke the function with two arguments.
/// </summary>
/// <param name="arg1">The first argument.</param>
/// <param name="arg2">The second argument.</param>
/// <returns>Function result.</returns>
protected abstract object InvokeBinary(object arg1, object arg2);
}
}

Просмотреть файл

@ -0,0 +1,36 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System.Threading.Tasks;
/// <summary>
/// Resolver that acts as a boolean condition check with empty output.
/// </summary>
public abstract class BooleanPredicateResolver : PredicateResolver
{
public BooleanPredicateResolver(CompoundTerm input, Constraint constraint, QueryContext context)
: base(input, constraint, context, 1)
{
}
public override async Task<UnificationResult> OnGetNextAsync()
{
UnificationResult result;
if (await this.CheckAsync())
{
result = UnificationResult.Empty;
}
else
{
result = null;
}
return result;
}
protected abstract Task<bool> CheckAsync();
}
}

Просмотреть файл

@ -0,0 +1,97 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System;
/// <summary>
/// Comparison functions.
/// If any of the parameter is a number (convertable to long),
/// the comparison will be performed after converting both to long
/// Otherwise string comparisons will be used.
/// </summary>
public class ComparisonFunc : BinaryFunc
{
public static readonly ComparisonFunc EQ = new ComparisonFunc("eq", Option.Equal);
public static readonly ComparisonFunc NE = new ComparisonFunc("ne", Option.GreaterThan | Option.LessThan);
public static readonly ComparisonFunc GT = new ComparisonFunc("gt", Option.GreaterThan);
public static readonly ComparisonFunc LT = new ComparisonFunc("lt", Option.LessThan);
public static readonly ComparisonFunc GE = new ComparisonFunc("ge", Option.GreaterThan | Option.Equal);
public static readonly ComparisonFunc LE = new ComparisonFunc("le", Option.LessThan | Option.Equal);
private readonly Option option;
private ComparisonFunc(string name, Option option)
: base(name)
{
this.option = option;
}
[Flags]
private enum Option
{
LessThan = 1,
Equal = 2,
GreaterThan = 4,
}
public bool Invoke(object arg1, object arg2)
{
int result;
ReleaseAssert.IsTrue(Utility.TryCompare(arg1, arg2, out result));
return this.Compare(result);
}
public ComparisonFunc Inverse()
{
if (this == ComparisonFunc.LT)
{
return ComparisonFunc.GT;
}
else if (this == ComparisonFunc.GT)
{
return ComparisonFunc.LT;
}
else if (this == ComparisonFunc.LE)
{
return ComparisonFunc.GE;
}
else if (this == ComparisonFunc.GE)
{
return ComparisonFunc.LE;
}
return this;
}
internal bool Compare(int cmp)
{
if (cmp == 0)
{
return ((this.option & Option.Equal) != 0);
}
if (cmp < 0)
{
return ((this.option & Option.LessThan) != 0);
}
return ((this.option & Option.GreaterThan) != 0);
}
protected override object InvokeBinary(object arg1, object arg2)
{
int result;
ReleaseAssert.IsTrue(
Utility.TryCompare(arg1, arg2, out result),
"Incompatible arguments to compare {0}/{1}:{2}/{3}",
arg1,
arg1 != null ? arg1.GetType() : null,
arg2,
arg2 != null ? arg2.GetType() : null);
return this.Compare(result);
}
}
}

Просмотреть файл

@ -0,0 +1,67 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System.Collections.Generic;
public class CompositePropertyContext : IPropertyContext
{
private List<IPropertyContext> contexts;
public CompositePropertyContext(IPropertyContext context, params IPropertyContext[] extraContexts)
{
this.contexts = new List<IPropertyContext>(extraContexts.Length + 1);
this.contexts.Add(context);
this.contexts.AddRange(extraContexts);
}
public virtual object this[string name]
{
get
{
int start, end;
if (name.Length > 3 && name[0] == '#' && name[2] == ':' && name[1] >= '0' && name[1] <= '9')
{
start = name[1] - '0';
end = start + 1;
name = name.Substring(3);
}
else
{
start = 0;
end = this.contexts.Count;
}
for (int i = start; i < end; i++)
{
object result = this.contexts[i][name];
if (result != null)
{
return result;
}
}
return null;
}
}
public void SetContext(IPropertyContext context, int index = 0)
{
while (this.contexts.Count < index)
{
this.contexts.Add(null);
}
if (index == this.contexts.Count)
{
this.contexts.Add(context);
}
else
{
this.contexts[index] = context;
}
}
}
}

Просмотреть файл

@ -0,0 +1,554 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System.Collections;
using System.Collections.Generic;
using System.Text;
/// <summary>
/// Compound term.
/// A grounded compound term can be used as a dictionary.
/// </summary>
public class CompoundTerm : Term, IPropertyContext, IWritablePropertyContext
{
internal const string EffectiveTypeArgumentName = "_EffectiveType";
private static readonly char[] PropertyDelimiters = new char[] { '.', '/' };
private static readonly List<TermArgument> EmptyArguments = new List<TermArgument>();
private Functor functor;
private VariableBinding binding;
private List<TermArgument> arguments;
private TermOption option;
public CompoundTerm()
: this(Functor.Empty, VariableBinding.Ground)
{
}
public CompoundTerm(string name)
: this(new Functor(name), VariableBinding.Ground)
{
}
public CompoundTerm(Functor functor)
: this(functor, VariableBinding.Ground)
{
}
internal CompoundTerm(Functor functor, VariableBinding binding)
: this(functor, binding, EmptyArguments)
{
}
internal CompoundTerm(Functor functor, VariableBinding binding, List<TermArgument> arguments)
{
this.functor = functor;
this.binding = binding;
this.arguments = arguments;
this.option = TermOption.Default;
}
public Functor Functor
{
get
{
return this.functor;
}
internal set
{
this.functor = value;
}
}
public PredicateType PredicateType
{
get
{
return (PredicateType)this.functor;
}
}
public List<TermArgument> Arguments
{
get
{
return this.arguments;
}
}
public TermOption Option
{
get
{
return this.option;
}
}
internal override VariableBinding Binding
{
get
{
return this.binding;
}
}
public virtual object this[string name]
{
get
{
CompoundTerm current = this;
Term term;
int start = 0;
while (current != null)
{
int i = name.IndexOfAny(PropertyDelimiters, start);
if (i < 0)
{
return current.GetExtendedArgument(name.Substring(start));
}
term = current.GetExtendedArgument(name.Substring(start, i - start));
current = term as CompoundTerm;
if (current == null && term != null)
{
Constant constant = term as Constant;
if (constant != null)
{
current = ObjectCompundTerm.Create(constant.Value);
}
}
start = i + 1;
}
return null;
}
set
{
CompoundTerm current = this;
int start = 0;
int i;
do
{
i = name.IndexOf('/', start);
if (i > 0)
{
string segment = name.Substring(start, i - start);
Term child = this.GetArgument(segment);
if (child == null)
{
CompoundTerm next = new CompoundTerm();
current.AddArgument(next, segment);
current = next;
}
else
{
current = child as CompoundTerm;
if (current == null)
{
throw new GuanException("Invalid path {0} to set value for {1}", name, this);
}
}
start = i + 1;
}
}
while (i > 0);
if (start > 0)
{
name = name.Substring(start);
}
Term term = Term.FromObject(value);
i = current.FindArgument(name);
if (i < 0)
{
current.AddArgument(term, name);
}
else
{
current.Arguments[i].Value = term;
}
}
}
public virtual IEnumerable<TermArgument> GetUnificationArgument()
{
return this.arguments;
}
public IEnumerator GetEnumerator()
{
return this.arguments.GetEnumerator();
}
public Term GetArgument(string name)
{
int i = this.FindArgument(name);
if (i < 0)
{
return null;
}
return this.arguments[i].Value;
}
public void AddArgument(Term argument, string name, ArgumentDescription desc = null)
{
if (this.arguments == EmptyArguments)
{
this.arguments = new List<TermArgument>();
}
this.arguments.Add(new TermArgument(name, argument, desc));
}
public bool RemoveArgument(string name)
{
int i = this.FindArgument(name);
if (i < 0)
{
return false;
}
this.arguments.RemoveAt(i);
return true;
}
public virtual Term GetExtendedArgument(string name)
{
return this.GetArgument(name);
}
public override bool IsGround()
{
Stack<CompoundTerm> terms = new Stack<CompoundTerm>();
terms.Push(this);
while (terms.Count > 0)
{
CompoundTerm term = terms.Pop();
foreach (TermArgument arg in term.Arguments)
{
CompoundTerm compound = arg.Value as CompoundTerm;
if (compound != null)
{
terms.Push(compound);
}
else if (!arg.Value.IsGround())
{
return false;
}
}
}
return true;
}
public override Term GetEffectiveTerm()
{
EvaluatedFunctor func = this.functor as EvaluatedFunctor;
if (func != null && func.Func is StandaloneFunc)
{
return func.Evaluate(this, null);
}
Term term = this.GetArgument("this");
if (term != null)
{
// This arg should be a variable, when the variable is bound, it
// contains the result term.
CompoundTerm compound = term.GetEffectiveTerm() as CompoundTerm;
if (compound != null)
{
return compound;
}
}
return this;
}
public override string ToString()
{
if (this.functor?.Name == ".")
{
return ListTerm.ToString(this);
}
StringBuilder result = new StringBuilder();
_ = result.AppendFormat("{0}(", this.functor);
this.OutputArguments(result);
if (this.Arguments.Count > 0)
{
result.Length--;
}
_ = result.Append(')');
return result.ToString();
}
internal CompoundTerm DuplicateInput(VariableBinding binding)
{
if (this.IsGround())
{
return this;
}
ReleaseAssert.IsTrue(this.binding != binding);
CompoundTerm result = new CompoundTerm(this.Functor, binding);
for (int i = 0; i < this.arguments.Count; i++)
{
Term term = this.arguments[i].Value.GetEffectiveTerm();
if (!term.IsGround())
{
Variable variable = term as Variable;
if (variable != null)
{
term = binding.AddOutputVariable(variable);
}
else
{
CompoundTerm compound = (CompoundTerm)term;
term = compound.DuplicateInput(binding);
}
}
result.AddArgument(term, this.arguments[i].Name);
}
return result;
}
internal CompoundTerm DuplicateOutput(VariableBinding binding)
{
if (this.IsGround())
{
return this;
}
CompoundTerm result = new CompoundTerm(this.Functor, binding);
for (int i = 0; i < this.arguments.Count; i++)
{
Term term = this.arguments[i].Value.GetEffectiveTerm();
if (!term.IsGround())
{
Variable variable = term as Variable;
if (variable != null)
{
OutputVariable outputVariable = term as OutputVariable;
if (outputVariable != null)
{
term = outputVariable.Original;
}
else
{
term = binding.AddForeignVariable(variable);
}
}
else
{
CompoundTerm compound = (CompoundTerm)term;
term = compound.DuplicateOutput(binding);
}
}
result.AddArgument(term, this.arguments[i].Name);
}
return result;
}
internal CompoundTerm DuplicateGoal(VariableBinding binding)
{
ReleaseAssert.IsTrue(this.Binding == VariableBinding.Ground || this.IsGround());
CompoundTerm result = new CompoundTerm(this.Functor, binding);
result.option = this.option;
for (int i = 0; i < this.arguments.Count; i++)
{
Term term = this.arguments[i].Value;
if (!term.IsGround())
{
IndexedVariable variable = term as IndexedVariable;
if (variable != null)
{
term = binding.GetLocalVariable(variable.Index);
}
else
{
CompoundTerm compound = (CompoundTerm)term;
term = compound.DuplicateGoal(binding);
}
}
result.AddArgument(term, this.arguments[i].Name);
}
result.option = this.option;
return result;
}
/// <summary>
/// Used during tail optimization to change the binding of the input goal
/// from the current binding of the rule to the previous one (that will be
/// used for output eventually) as the current binding is disappearing.
/// </summary>
/// <param name="binding">The binding for the output.</param>
internal void Migrate(VariableBinding binding)
{
Stack<CompoundTerm> compounds = new Stack<CompoundTerm>();
compounds.Push(this);
while (compounds.Count > 0)
{
CompoundTerm current = compounds.Pop();
for (int i = 0; i < current.arguments.Count; i++)
{
Term term = current.arguments[i].Value.GetEffectiveTerm();
CompoundTerm compound = term as CompoundTerm;
if (compound != null)
{
compounds.Push(compound);
}
else
{
OutputVariable outputVariable = term as OutputVariable;
if (outputVariable != null)
{
ReleaseAssert.IsTrue(outputVariable.Original.Binding == binding);
current.arguments[i].Value = outputVariable.Original;
}
else
{
Variable variable = term as Variable;
if (variable != null)
{
current.arguments[i].Value = binding.AddForeignVariable(variable);
}
}
}
}
current.binding = binding;
}
}
internal EvaluatedFunctor GetEvaluatedFunctor()
{
EvaluatedFunctor evaluatedFunctor = this.functor as EvaluatedFunctor;
if (evaluatedFunctor == null)
{
ConstraintPredicateType constraintType = this.functor as ConstraintPredicateType;
if (constraintType != null)
{
evaluatedFunctor = constraintType.Func;
}
}
return evaluatedFunctor;
}
internal Term Evaluate(QueryContext context)
{
EvaluatedFunctor evaluatedFunctor = this.GetEvaluatedFunctor();
ReleaseAssert.IsTrue(evaluatedFunctor != null, "{0} can't be evaluted", this);
return evaluatedFunctor?.Evaluate(this, context);
}
internal void PostProcessing(Rule rule)
{
this.ProcessOption(rule);
try
{
this.PredicateType.AdjustTerm(this, rule);
}
catch (GuanException e)
{
throw new GuanException(e, "Invalid {0} {1} in rule: {2}", this == rule.Head ? "head" : "goal", this, rule);
}
}
internal void OutputArguments(StringBuilder result)
{
for (int i = 0; i < this.Arguments.Count; i++)
{
CompoundTerm compound = this.arguments[i].Value as CompoundTerm;
if ((this.arguments[i].Name == i.ToString()) || (compound != null && compound.Functor.Name == this.arguments[i].Name))
{
_ = result.AppendFormat("{0},", this.arguments[i].Value);
}
else if (!this.Arguments[i].Name.StartsWith("_"))
{
_ = result.AppendFormat("{0}={1},", this.Arguments[i].Name, this.arguments[i].Value);
}
}
}
private int FindArgument(string name)
{
for (int i = 0; i < this.arguments.Count; i++)
{
if (this.arguments[i].Name == name)
{
return i;
}
}
return -1;
}
private void ProcessOption(Rule rule)
{
bool traceFail = false;
for (int i = 0; i < this.arguments.Count; i++)
{
CompoundTerm term = this.arguments[i].Value as CompoundTerm;
if (term != null && term.Functor.Name == "_option")
{
if (this == rule.Head)
{
throw new GuanException("_option can't be used in head");
}
this.option = new TermOption(term);
this.arguments.RemoveAt(i);
}
else if (this.arguments[i].Value.GetStringValue() == "_traceFail")
{
traceFail = true;
this.arguments.RemoveAt(i);
}
}
if (traceFail)
{
if (this.option == TermOption.Default)
{
this.option = new TermOption();
}
this.option[TermOption.Trace] = "!Fail";
}
PredicateType predicateType = this.functor as PredicateType;
if (predicateType != null && this.option == TermOption.Default)
{
this.option = predicateType.Option;
}
}
}
}

Просмотреть файл

@ -0,0 +1,91 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System;
using System.Collections.Generic;
internal static class ConsoleSink
{
private static ConsoleColor oldColor = Console.ForegroundColor;
private static Dictionary<ConsoleColor, ConsoleColor> colorMapping = GetColorMapping();
public static int GetColor(ConsoleColor color)
{
int bc = (int)Console.BackgroundColor;
bool bDark = (bc <= 7);
int fc = (int)color;
if (bDark)
{
fc |= 0x08;
}
else
{
fc &= 0x07;
}
return fc;
}
public static void WriteLine(int color, string text)
{
lock (colorMapping)
{
if (color >= 0)
{
ConsoleColor msgColor = (ConsoleColor)color;
if (msgColor != oldColor)
{
Console.ForegroundColor = msgColor;
}
try
{
Console.WriteLine(text);
}
finally
{
if (msgColor != oldColor)
{
Console.ForegroundColor = oldColor;
}
}
}
else
{
Console.WriteLine(text);
}
}
}
public static void WriteLine(ConsoleColor color, string format, params object[] args)
{
ConsoleColor msgColor;
if (!colorMapping.TryGetValue(color, out msgColor))
{
msgColor = color;
}
WriteLine((int)msgColor, string.Format(format, args));
}
private static Dictionary<ConsoleColor, ConsoleColor> GetColorMapping()
{
Dictionary<ConsoleColor, ConsoleColor> map = new Dictionary<ConsoleColor, ConsoleColor>();
AddColor(map, ConsoleColor.Red);
AddColor(map, ConsoleColor.Yellow);
AddColor(map, ConsoleColor.Cyan);
AddColor(map, ConsoleColor.Green);
return map;
}
private static void AddColor(Dictionary<ConsoleColor, ConsoleColor> map, ConsoleColor color)
{
map[color] = (ConsoleColor)GetColor(color);
}
}
}

140
guan/Guan/Logic/Constant.cs Normal file
Просмотреть файл

@ -0,0 +1,140 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System;
/// <summary>
/// Constant term.
/// </summary>
public class Constant : Term
{
/// <summary>
/// Wrapping a null object.
/// </summary>
public static readonly Constant Null = new Constant(null);
/// <summary>
/// Empty list.
/// </summary>
public static readonly Constant Nil = new Constant(new EmptyList());
/// <summary>
/// Boolean True.
/// </summary>
public static readonly Constant True = new Constant(true);
private object value;
public Constant(object value)
{
this.value = value;
}
public object Value
{
get
{
return this.value;
}
}
public static Constant Parse(string text)
{
if (text.Length > 1 && text[0] == text[text.Length - 1] && (text[0] == '\'' || text[0] == '"'))
{
return new Constant(text.Substring(1, text.Length - 2));
}
if (text == "null")
{
return Null;
}
if (bool.TryParse(text, out bool boolValue))
{
return new Constant(boolValue);
}
if (long.TryParse(text, out long longValue))
{
return new Constant(longValue);
}
if (ulong.TryParse(text, out ulong ulongValue))
{
return new Constant(ulongValue);
}
if (double.TryParse(text, out double doubleValue))
{
return new Constant(doubleValue);
}
if (TimeSpan.TryParse(text, out TimeSpan timeSpan))
{
return new Constant(timeSpan);
}
return new Constant(text);
}
public override bool IsGround()
{
return true;
}
public override string ToString()
{
if (this == Nil)
{
return "[]";
}
if (this.value?.GetType() == typeof(DateTime))
{
return Utility.FormatTime((DateTime)this.value);
}
if (this.value == null)
{
return string.Empty;
}
string stringValue = this.value as string;
if (stringValue != null)
{
return "'" + stringValue + "'";
}
return this.value.ToString();
}
internal bool IsTrue()
{
if (this.value == null)
{
return false;
}
if (this.value is bool)
{
return (bool)this.value;
}
string s = this.value as string;
if (s != null)
{
return s.Length > 0;
}
return true;
}
private class EmptyList
{
}
}
}

Просмотреть файл

@ -0,0 +1,490 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System.Collections.Generic;
/// <summary>
/// A constraint is a collection of criteria (each represented as an evalutable
/// compound) that can be validated.
/// Other than the criteria, separate entries for upper/lower bound and possible
/// values are maintained so that the constraint can be used not only for passive
/// validation, but also actively queried by the resolvers for more efficient
/// search.
/// </summary>
public class Constraint
{
public static readonly Constraint Empty = new Constraint();
private List<CompoundTerm> terms;
private List<BoundEntry> lowerEntries;
private List<BoundEntry> upperEnties;
private List<ValueEntry> valueEntries;
internal Constraint()
{
this.terms = new List<CompoundTerm>();
}
internal List<CompoundTerm> Terms
{
get
{
return this.terms;
}
}
public List<object> GetValues(Variable variable, bool remove)
{
if (this.valueEntries == null)
{
return null;
}
List<object> result = null;
for (int i = this.valueEntries.Count - 1; i >= 0; i--)
{
List<object> values = this.valueEntries[i].Get(variable);
if (values != null)
{
if (result == null)
{
result = values;
}
else
{
MergeValues(result, values);
}
if (remove)
{
_ = this.terms.Remove(this.valueEntries[i].Term);
this.valueEntries.RemoveAt(i);
}
}
}
return result;
}
public object GetLowerBound(Variable variable, bool remove, out bool isInclusive)
{
return this.GetBound(false, variable, remove, out isInclusive);
}
public object GetUpperBound(Variable variable, bool remove, out bool isInclusive)
{
return this.GetBound(true, variable, remove, out isInclusive);
}
internal void Add(CompoundTerm term)
{
this.Process(term);
}
internal void Add(IEnumerable<CompoundTerm> terms)
{
foreach (CompoundTerm term in terms)
{
this.Process(term);
}
}
internal int Evaluate(QueryContext context)
{
int result = 1;
foreach (CompoundTerm term in this.terms)
{
Constant evaluted = term.Evaluate(context) as Constant;
if (evaluted == null)
{
result = 0;
}
else if (!evaluted.IsTrue())
{
return -1;
}
}
return result;
}
private static void MergeValues(List<object> list1, List<object> list2)
{
for (int i = list1.Count - 1; i >= 0; i--)
{
bool found = false;
for (int j = 0; j < list2.Count && !found; j++)
{
found = object.Equals(list1[i], list2[j]);
}
if (!found)
{
list1.RemoveAt(i);
}
}
}
private void Process(CompoundTerm term)
{
EvaluatedFunctor evaluatedFunctor = term.GetEvaluatedFunctor();
bool add = true;
if (evaluatedFunctor != null)
{
if (evaluatedFunctor.Func is AndFunc)
{
foreach (TermArgument arg in term.Arguments)
{
CompoundTerm compound = arg.Value as CompoundTerm;
if (compound != null)
{
this.Process(compound);
}
}
add = false;
}
if (evaluatedFunctor.Func is OrFunc)
{
ValueEntry entry = new ValueEntry(term);
// We will only handle disjunction of a collectoin of comparisons
// with the same variable.
if (this.DecomposeDisjunction(term, entry))
{
this.AddValueEntry(entry);
add = false;
}
}
else
{
RawEntry rawEntry = RawEntry.Convert(term);
if (rawEntry != null)
{
rawEntry.AddToConstaint(this, term);
}
}
}
if (add)
{
this.terms.Add(term);
}
}
private bool DecomposeDisjunction(CompoundTerm term, ValueEntry entry)
{
foreach (TermArgument arg in term.Arguments)
{
CompoundTerm compound = arg.Value as CompoundTerm;
if (compound == null)
{
return false;
}
EvaluatedFunctor evaluatedFunctor = compound.GetEvaluatedFunctor();
if (evaluatedFunctor.Func is OrFunc)
{
if (!this.DecomposeDisjunction(compound, entry))
{
return false;
}
}
else
{
RawEntry rawEntry = RawEntry.Convert(compound);
if (rawEntry == null || !entry.Add(rawEntry))
{
return false;
}
}
}
return true;
}
private void AddValueEntry(ValueEntry entry)
{
if (this.valueEntries == null)
{
this.valueEntries = new List<ValueEntry>();
}
this.valueEntries.Add(entry);
}
private void AddUpperBoundEntry(BoundEntry entry)
{
if (this.upperEnties == null)
{
this.upperEnties = new List<BoundEntry>();
}
this.upperEnties.Add(entry);
}
private void AddLowerBoundEntry(BoundEntry entry)
{
if (this.lowerEntries == null)
{
this.lowerEntries = new List<BoundEntry>();
}
this.lowerEntries.Add(entry);
}
private object GetBound(bool upper, Variable variable, bool remove, out bool isInclusive)
{
isInclusive = false;
List<BoundEntry> entries = (upper ? this.upperEnties : this.lowerEntries);
if (entries == null)
{
return null;
}
object result = null;
for (int i = entries.Count - 1; i >= 0; i--)
{
bool inclusive;
object value = entries[i].Get(variable, out inclusive);
if (value != null)
{
if (result == null || this.CompareResult(value, inclusive, result, isInclusive, upper))
{
result = value;
isInclusive = inclusive;
}
if (remove)
{
_ = this.terms.Remove(entries[i].Term);
entries.RemoveAt(i);
}
}
}
return result;
}
private bool CompareResult(object value1, bool inclusive1, object value2, bool inclusive2, bool upper)
{
if (object.Equals(value1, value2))
{
return (!inclusive1 && inclusive2);
}
ComparisonFunc func = (upper ? ComparisonFunc.LT : ComparisonFunc.GT);
return func.Invoke(value1, value2);
}
private class BoundEntry
{
private CompoundTerm term;
private Variable variable;
private object value;
private bool isInclusive;
public BoundEntry(CompoundTerm term, Variable variable, object value, bool isInclusive)
{
this.term = term;
this.variable = variable;
this.value = value;
this.isInclusive = isInclusive;
}
public CompoundTerm Term
{
get
{
return this.term;
}
}
public object Get(Variable variable, out bool isInclusive)
{
//TODO: this is probably does not handle all the cases.
if (GetEffectiveTerm(variable) != GetEffectiveTerm(this.variable))
{
isInclusive = false;
return null;
}
isInclusive = this.isInclusive;
return this.value;
}
private static Term GetEffectiveTerm(Variable variable)
{
Term term = variable.GetEffectiveTerm();
OutputVariable outputVariable = term as OutputVariable;
if (outputVariable != null)
{
return GetEffectiveTerm(outputVariable.Original);
}
return term;
}
}
private class ValueEntry
{
private CompoundTerm term;
private Variable variable;
private List<object> values;
public ValueEntry(CompoundTerm term)
{
this.term = term;
this.values = new List<object>();
}
public ValueEntry(CompoundTerm term, Variable variable, object value)
{
this.term = term;
this.variable = variable;
this.values = new List<object>(1)
{
value
};
}
public CompoundTerm Term
{
get
{
return this.term;
}
}
public bool Add(RawEntry entry)
{
if (entry.Func != ComparisonFunc.EQ)
{
return false;
}
if (this.variable == null)
{
this.variable = entry.Variable;
}
else if (this.variable != entry.Variable)
{
return false;
}
this.values.Add(entry.Value);
return true;
}
public List<object> Get(Variable variable)
{
if (variable != this.variable.GetEffectiveTerm())
{
return null;
}
return this.values;
}
}
/// <summary>
/// Terms like "variable comparison constant" or "constant comparison variable" only
/// </summary>
private class RawEntry
{
private Variable variable;
private object value;
private ComparisonFunc func;
private RawEntry(Variable variable, object value, ComparisonFunc func)
{
this.variable = variable;
this.value = value;
this.func = func;
}
public Variable Variable
{
get
{
return this.variable;
}
}
public object Value
{
get
{
return this.value;
}
}
public ComparisonFunc Func
{
get
{
return this.func;
}
}
public static RawEntry Convert(CompoundTerm term)
{
EvaluatedFunctor evaluatedFunctor = term.GetEvaluatedFunctor();
if (evaluatedFunctor == null || term.Arguments.Count != 2)
{
return null;
}
ComparisonFunc func = evaluatedFunctor.Func as ComparisonFunc;
if (func == null)
{
return null;
}
Term term1 = term.Arguments[0].Value.GetEffectiveTerm();
Term term2 = term.Arguments[1].Value.GetEffectiveTerm();
Constant constant = null;
Variable variable = term1 as Variable;
if (variable != null)
{
constant = term2 as Constant;
}
else
{
constant = term1 as Constant;
variable = term2 as Variable;
func = func.Inverse();
}
if (variable == null || constant == null)
{
return null;
}
return new RawEntry(variable, constant.Value, func);
}
public void AddToConstaint(Constraint constraint, CompoundTerm term)
{
if (this.func == ComparisonFunc.EQ)
{
constraint.AddValueEntry(new ValueEntry(term, this.variable, this.value));
}
else if (this.func == ComparisonFunc.LT || this.func == ComparisonFunc.LE)
{
constraint.AddUpperBoundEntry(new BoundEntry(term, this.variable, this.value, this.func == ComparisonFunc.LE));
}
else if (this.func == ComparisonFunc.GT || this.func == ComparisonFunc.GE)
{
constraint.AddLowerBoundEntry(new BoundEntry(term, this.variable, this.value, this.func == ComparisonFunc.GE));
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,67 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System;
using System.Threading.Tasks;
internal class ConstraintPredicateType : PredicateType
{
private EvaluatedFunctor func;
public ConstraintPredicateType(EvaluatedFunctor evaluatedFunctor)
: base(evaluatedFunctor.Name)
{
this.func = evaluatedFunctor;
}
public EvaluatedFunctor Func
{
get
{
return this.func;
}
}
public override PredicateResolver CreateResolver(CompoundTerm input, Constraint constraint, QueryContext context)
{
return new Resolver(this.func, input, constraint, context);
}
private class Resolver : PredicateResolver
{
private EvaluatedFunctor func;
public Resolver(EvaluatedFunctor func, CompoundTerm input, Constraint constraint, QueryContext context)
: base(input, constraint, context)
{
this.func = func;
}
public override Task<UnificationResult> OnGetNextAsync()
{
UnificationResult result = null;
if (this.Iteration == 0)
{
Term term = this.func.Evaluate(this.Input, this.Context);
Constant constant = term as Constant;
if (constant == null)
{
result = new UnificationResult(0);
result.AddConstraint(this.Input);
}
else if (constant.IsTrue())
{
result = UnificationResult.Empty;
}
this.Complete();
}
return Task.FromResult(result);
}
}
}
}

Просмотреть файл

@ -0,0 +1,39 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System.Threading.Tasks;
/// <summary>
/// Predicate type for cut(!).
/// </summary>
internal class CutPredicateType : PredicateType
{
public static readonly CutPredicateType Singleton = new CutPredicateType();
private CutPredicateType()
: base("!")
{
}
public override PredicateResolver CreateResolver(CompoundTerm input, Constraint constraint, QueryContext context)
{
return new Resolver();
}
private class Resolver : BooleanPredicateResolver
{
public Resolver()
: base(null, null, null)
{
}
protected override Task<bool> CheckAsync()
{
return Task.FromResult(true);
}
}
}
}

Просмотреть файл

@ -0,0 +1,31 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
/// <summary>
/// When a predicate type is marked as dynamic, when resolving the goal
/// we will only look for asserted predicates.
/// This is different from standard Prolog where the predicates are also
/// getting resolved in standard ways.
/// </summary>
internal class DynamicPredicateType : PredicateType
{
public DynamicPredicateType(string name)
: base(name)
{
}
public override PredicateResolver CreateResolver(CompoundTerm input, Constraint constraint, QueryContext context)
{
PredicateType assertedType = context.GetAssertedPredicateType(this.Name);
if (assertedType == null)
{
assertedType = FailPredicateType.Singleton;
}
return assertedType.CreateResolver(input, constraint, context);
}
}
}

Просмотреть файл

@ -0,0 +1,26 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
/// <summary>
/// Property context that does not have any property.
/// </summary>
public sealed class EmptyPropertyContext : IPropertyContext
{
public static readonly EmptyPropertyContext Singleton = new EmptyPropertyContext();
private EmptyPropertyContext()
{
}
object IPropertyContext.this[string name]
{
get
{
return null;
}
}
}
}

Просмотреть файл

@ -0,0 +1,72 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System.Collections;
using System.Threading.Tasks;
/// <summary>
/// Predicate type for enumeration of collection object.
/// </summary>
internal class EnumerablePredicateType : PredicateType
{
public static readonly EnumerablePredicateType Singleton = new EnumerablePredicateType();
private EnumerablePredicateType()
: base("enumerable", true, 2, 2)
{
}
public override PredicateResolver CreateResolver(CompoundTerm input, Constraint constraint, QueryContext context)
{
return new Resolver(input, constraint, context);
}
private class Resolver : PredicateResolver
{
private IEnumerable collection;
private IEnumerator enumerator;
private VariableBinding binding;
public Resolver(CompoundTerm input, Constraint constraint, QueryContext context)
: base(input, constraint, context)
{
Term term = input.Arguments[0].Value.GetEffectiveTerm();
Constant constant = term as Constant;
if (constant != null)
{
this.collection = constant.Value as IEnumerable;
}
else
{
this.collection = term as IEnumerable;
}
if (this.collection != null)
{
this.enumerator = this.collection.GetEnumerator();
this.binding = new VariableBinding(VariableTable.Empty, 0, input.Binding.Level + 1);
}
}
public override Task<UnificationResult> OnGetNextAsync()
{
UnificationResult result = null;
while (result == null && this.enumerator != null && this.enumerator.MoveNext())
{
Term term = Term.FromObject(this.enumerator.Current);
if (this.binding.Unify(term, this.Input.Arguments[1].Value))
{
result = this.binding.CreateOutput();
}
this.binding.ResetOutput();
}
return Task.FromResult<UnificationResult>(result);
}
}
}
}

Просмотреть файл

@ -0,0 +1,56 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
/// <summary>
/// Functor that can transform the containing compound term.
/// </summary>
internal class EvaluatedFunctor : Functor
{
private GuanFunc func;
private ConstraintPredicateType constraintType;
public EvaluatedFunctor(GuanFunc func)
: base(func.Name)
{
this.func = func;
this.constraintType = new ConstraintPredicateType(this);
}
public GuanFunc Func
{
get
{
return this.func;
}
}
public ConstraintPredicateType ConstraintType
{
get
{
return this.constraintType;
}
}
public Term Evaluate(CompoundTerm term, QueryContext context)
{
object[] args = new object[term.Arguments.Count];
for (int i = 0; i < args.Length; i++)
{
Term arg = term.Arguments[i].Value.GetEffectiveTerm();
if (!arg.IsGround())
{
return term;
}
args[i] = arg.GetObjectValue();
}
object result = this.func.Invoke(context, args);
return Term.FromObject(result);
}
}
}

Просмотреть файл

@ -0,0 +1,66 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System;
public abstract class EventLogWriter
{
private static EventLogWriter current = new ConsoleWriter();
protected enum LogLevel
{
Trace = 0,
Debug = 1,
Information = 2,
Warning = 3,
Error = 4,
}
public static void Set(EventLogWriter writer)
{
current = writer;
}
public static void WriteInfo(string format, params object[] args)
{
current.WriteEntry(LogLevel.Information, format, args);
}
public static void WriteWarning(string format, params object[] args)
{
current.WriteEntry(LogLevel.Warning, format, args);
}
public static void WriteError(string format, params object[] args)
{
current.WriteEntry(LogLevel.Error, format, args);
}
protected abstract void WriteEntry(LogLevel level, string format, params object[] args);
private class ConsoleWriter : EventLogWriter
{
protected override void WriteEntry(LogLevel level, string format, params object[] args)
{
int color;
switch (level)
{
case LogLevel.Error:
color = (int)ConsoleColor.Red;
break;
case LogLevel.Warning:
color = (int)ConsoleColor.Yellow;
break;
default:
color = -1;
break;
}
ConsoleSink.WriteLine(color, string.Format(format, args));
}
}
}
}

Просмотреть файл

@ -0,0 +1,40 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System.Threading.Tasks;
/// <summary>
/// Predicate type for "fail"
/// </summary>
internal class FailPredicateType : PredicateType
{
public static readonly FailPredicateType Singleton = new FailPredicateType();
public static readonly FailPredicateType NotApplicable = new FailPredicateType();
private FailPredicateType()
: base("fail")
{
}
public override PredicateResolver CreateResolver(CompoundTerm input, Constraint constraint, QueryContext context)
{
return new Resolver();
}
private class Resolver : BooleanPredicateResolver
{
public Resolver()
: base(null, null, null)
{
}
protected override Task<bool> CheckAsync()
{
return Task.FromResult(false);
}
}
}
}

Просмотреть файл

@ -0,0 +1,54 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System.Threading.Tasks;
/// <summary>
/// Predicate type for forwardcut.
/// </summary>
internal class ForwardCutPredicateType : PredicateType
{
public static readonly ForwardCutPredicateType Singleton = new ForwardCutPredicateType();
private ForwardCutPredicateType()
: base("forwardcut", true, 0, 0)
{
}
public override PredicateResolver CreateResolver(CompoundTerm input, Constraint constraint, QueryContext context)
{
return new Resolver(context);
}
public override void AdjustTerm(CompoundTerm term, Rule rule)
{
base.AdjustTerm(term, rule);
int i;
for (i = 0; i < rule.Goals.Count && term != rule.Goals[i]; i++)
{
}
if (i == 0 || i >= rule.Goals.Count - 1 || rule.Goals[i - 1].PredicateType is ConstraintPredicateType)
{
throw new GuanException("Invalid use of forwardcut");
}
}
private class Resolver : BooleanPredicateResolver
{
public Resolver(QueryContext context)
: base(null, null, context)
{
}
protected override Task<bool> CheckAsync()
{
return Task.FromResult(true);
}
}
}
}

131
guan/Guan/Logic/Functor.cs Normal file
Просмотреть файл

@ -0,0 +1,131 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
/// <summary>
/// Functor for compound term.
/// </summary>
public class Functor
{
public static readonly Functor ClassObject = new Functor("object");
public static readonly Functor Empty = new Functor(string.Empty);
private static readonly Regex NamePattern = new Regex(@"^[_\w]+$", RegexOptions.Compiled);
private static readonly List<string> EmptyRequiredArgs = new List<string>();
private string name;
private Type unificationType;
private List<KeyValuePair<string, ArgumentDescription>> args;
private List<string> requiredArguments;
public Functor(string name)
{
this.name = name;
}
internal Functor(Type type)
{
this.name = type.Name;
this.unificationType = type;
}
public string Name
{
get
{
return this.name;
}
}
public List<string> RequiredArguments
{
get
{
return this.requiredArguments ?? EmptyRequiredArgs;
}
}
internal Type UnificationType
{
get
{
return this.unificationType;
}
}
public ArgumentDescription GetArgumentDescription(string name)
{
if (this.args == null)
{
return null;
}
foreach (KeyValuePair<string, ArgumentDescription> arg in this.args)
{
if (arg.Key == name)
{
return arg.Value;
}
}
return null;
}
public void AddArgumentDescription(string name, ArgumentDescription arg)
{
if (this.GetArgumentDescription(name) != null)
{
throw new GuanException("Duplicate argument description for {0} in {1}", name, this);
}
if (this.args == null)
{
this.args = new List<KeyValuePair<string, ArgumentDescription>>();
}
this.args.Add(new KeyValuePair<string, ArgumentDescription>(name, arg));
if (arg.Required)
{
if (this.requiredArguments == null)
{
this.requiredArguments = new List<string>();
}
this.requiredArguments.Add(name);
}
}
public override bool Equals(object obj)
{
Functor other = obj as Functor;
return (other != null && this.name == other.name);
}
public override int GetHashCode()
{
return this.name.GetHashCode();
}
public override string ToString()
{
return this.name;
}
internal static Functor Parse(string name)
{
if (PredicateType.GetBuiltInType(name) == null && !NamePattern.IsMatch(name))
{
throw new ArgumentException("Invalid functor name: " + name);
}
return new Functor(name);
}
}
}

Просмотреть файл

@ -0,0 +1,46 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
using System.Collections.Generic;
namespace Guan.Logic
{
/// <summary>
/// Container for functors.
/// </summary>
public class FunctorTable : IFunctorProvider
{
private IFunctorProvider provider_;
private Dictionary<string, Functor> functors_;
public FunctorTable(IFunctorProvider provider = null)
{
provider_ = provider;
functors_ = new Dictionary<string, Functor>();
}
public void Add(Functor functor)
{
lock (functors_)
{
functors_.Add(functor.Name, functor);
}
}
public Functor FindFunctor(string name, Module from)
{
lock (functors_)
{
Functor result;
if (!functors_.TryGetValue(name, out result) && provider_ != null)
{
result = provider_.FindFunctor(name, from);
}
return result;
}
}
}
}

195
guan/Guan/Logic/GetFunc.cs Normal file
Просмотреть файл

@ -0,0 +1,195 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System;
using System.Globalization;
using System.Reflection;
/// <summary>
/// Function to retrieve property from context.
/// </summary>
internal class GetFunc : GuanFunc
{
public static readonly GetFunc Singleton = new GetFunc("get");
private static readonly char[] Delimiters = new char[] { ',', ' ' };
private static readonly Type[] StringType = new Type[] { typeof(string) };
private GetFunc(string name)
: base(name)
{
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Type.InvokeMember", Justification = "Property name specified by user at runtime.")]
public override object Invoke(IPropertyContext context, object[] args)
{
ReleaseAssert.IsTrue(args.Length == 1 && context != null);
return Invoke(context, (string)args[0]);
}
internal static object Invoke(object context, string name)
{
object result = context;
while (result != null && !string.IsNullOrEmpty(name))
{
// Get the next level property name.
string propertyName;
int level = 0;
int index;
for (index = 0; index < name.Length; index++)
{
if (name[index] == '[')
{
level++;
}
else if (name[index] == ']')
{
level--;
// Can't be well-formed [] any more.
if (level < 0)
{
level = int.MinValue;
}
}
else if (name[index] == '.' && level == 0)
{
break;
}
}
if (index < name.Length)
{
propertyName = name.Substring(0, index);
name = name.Substring(index + 1);
}
else
{
propertyName = name;
name = null;
}
IPropertyContext propertyContext = result as IPropertyContext;
if (propertyContext != null)
{
result = propertyContext[propertyName];
}
else
{
// Use reflection if no other option.
Type type = result.GetType();
// Allow access to methods with only string parameters.
// Use [] instead of () to enclose the parameters because
// otherwise it can be interpreted as a GuanFunc.
if (propertyName.EndsWith("]", StringComparison.Ordinal))
{
index = propertyName.LastIndexOf('[');
if (index < 0)
{
throw new ArgumentException("Invalid property name");
}
string method = propertyName.Substring(0, index);
string param = propertyName.Substring(index + 1, propertyName.Length - index - 2);
result = InvokeMethod(type, method, param, result);
}
else
{
result = type.InvokeMember(propertyName, BindingFlags.GetProperty, null, result, null, CultureInfo.InvariantCulture);
}
}
}
return result;
}
private static object InvokeMethod(Type type, string methodName, string parameters, object obj)
{
string[] args = parameters.Split(Delimiters, StringSplitOptions.RemoveEmptyEntries);
foreach (MethodInfo method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance))
{
if (method.Name != methodName)
{
continue;
}
ParameterInfo[] paramInfo = method.GetParameters();
if (paramInfo.Length == args.Length)
{
object[] paramObjects = new object[args.Length];
int i;
for (i = 0; i < args.Length; i++)
{
if (!Convert(paramInfo[i].ParameterType, args[i], out paramObjects[i]))
{
break;
}
}
if (i == args.Length)
{
return type.InvokeMember(
methodName,
BindingFlags.InvokeMethod,
null,
obj,
paramObjects,
CultureInfo.InvariantCulture);
}
}
}
throw new ArgumentException("Unable to find matching method:" + methodName);
}
private static bool Convert(Type type, string value, out object result)
{
if (type == typeof(string))
{
result = value;
return true;
}
try
{
MethodInfo info = type.GetMethod(
"Parse",
BindingFlags.Static | BindingFlags.Public,
null,
StringType,
null);
if (info != null)
{
result = info.Invoke(null, new object[] { value });
return true;
}
ConstructorInfo constructor = type.GetConstructor(
BindingFlags.Public | BindingFlags.Instance,
null,
StringType,
null);
if (constructor != null)
{
result = constructor.Invoke(new object[] { value });
return true;
}
}
catch (TargetInvocationException)
{
}
result = null;
return false;
}
}
}

Просмотреть файл

@ -0,0 +1,62 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System.Threading.Tasks;
/// <summary>
/// Predicate type for getval.
/// </summary>
internal class GetValPredicateType : PredicateType
{
public static readonly GetValPredicateType Singleton = new GetValPredicateType();
private GetValPredicateType()
: base("getval", true, 2, 2)
{
}
public override PredicateResolver CreateResolver(CompoundTerm input, Constraint constraint, QueryContext context)
{
return new Resolver(input, constraint, context);
}
public override void AdjustTerm(CompoundTerm term, Rule rule)
{
string name = term.Arguments[0].Value.GetStringValue();
if (name == null)
{
throw new GuanException("The first argument of getval must be string: {0}", term);
}
if (!(term.Arguments[1].Value is IndexedVariable))
{
throw new GuanException("The second argument of getval must be a variable: {0}", term);
}
}
private class Resolver : GroundPredicateResolver
{
public Resolver(CompoundTerm input, Constraint constraint, QueryContext context)
: base(input, constraint, context, 1)
{
}
protected override Task<Term> GetNextTermAsync()
{
string name = this.GetInputArgumentString(0);
Term value = (Term)this.Context[name];
CompoundTerm result = new CompoundTerm(GetValPredicateType.Singleton, null);
if (value != null)
{
result.AddArgument(value, "1");
}
return Task.FromResult<Term>(result);
}
}
}
}

Просмотреть файл

@ -0,0 +1,53 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System.Threading.Tasks;
/// <summary>
/// Resolver that uses grounded term to unify with the goal.
/// This is typically resolver of external predicate which uses the goal
/// as a query against concrete instances of data.
/// </summary>
public abstract class GroundPredicateResolver : PredicateResolver
{
private VariableBinding binding;
private int count;
protected GroundPredicateResolver(CompoundTerm input, Constraint constraint, QueryContext context, int max = int.MaxValue)
: base(input, constraint, context, max)
{
this.binding = new VariableBinding(VariableTable.Empty, 0, input.Binding.Level + 1);
this.count = 0;
}
public override async Task<UnificationResult> OnGetNextAsync()
{
UnificationResult result = null;
while (result == null && this.count < this.Max && !this.Completed)
{
Term term = await this.GetNextTermAsync();
if (term == null)
{
return null;
}
if (this.binding.Unify(term, this.Input))
{
result = this.binding.CreateOutput();
}
this.count++;
this.binding.ResetOutput();
}
return result;
}
protected abstract Task<Term> GetNextTermAsync();
}
}

Просмотреть файл

@ -0,0 +1,51 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System;
using System.Runtime.Serialization;
/// <summary>
/// Base exception generated from the trace tool.
/// </summary>
[Serializable]
public class GuanException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="GuanException"/> class.
/// Default constructor.
/// </summary>
public GuanException()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="GuanException"/> class.
/// Constructor.
/// </summary>
/// <param name="message">A message that describes the error.</param>
public GuanException(string message, params object[] args)
: base(string.Format(message, args))
{
}
public GuanException(Exception inner, string message, params object[] args)
: base(string.Format(message, args), inner)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="GuanException"/> class.
/// Constructor.
/// </summary>
/// <param name="info">The object that holds the serialized object data.</param>
/// <param name="context">The contextual information about the source
/// or destination.</param>
protected GuanException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
}
}

Просмотреть файл

@ -0,0 +1,850 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
/// <summary>
/// Expression tree for manipulating properties. It is usually
/// generated from a string expression although it is also possible
/// to manually construct the tree.
/// Every expression tree node is basically a GuanFunc and
/// its child nodes are the arguments to the function.
/// Not thread safe.
/// </summary>
public class GuanExpression
{
private static readonly List<GuanExpression> NullChildren = new List<GuanExpression>();
private static readonly Operator Sentinel = new Operator("Dummy", Literal.Empty, 0);
private static readonly Regex EvalPropertyPattern = new Regex(@"(\<[\w#:.()/]+\>)", RegexOptions.Compiled);
private static Tri<Operator> operators = CreateOperators();
private GuanFunc func;
private List<GuanExpression> children;
public GuanExpression(GuanExpression other)
{
this.func = other.func;
if (other.children == NullChildren)
{
this.children = NullChildren;
}
else
{
this.children = new List<GuanExpression>(other.children.Count);
foreach (GuanExpression child in other.children)
{
this.children.Add(new GuanExpression(child));
}
}
}
/// <summary>
/// Initializes a new instance of the <see cref="GuanExpression"/> class.
/// Constructor.
/// </summary>
/// <param name="func">The function associated with this node.</param>
public GuanExpression(GuanFunc func)
{
this.func = func;
this.children = NullChildren;
}
public GuanExpression(GuanFunc func, List<GuanExpression> children)
{
this.func = func;
this.children = children;
}
public GuanExpression(object literalValue)
{
this.func = new Literal(literalValue);
this.children = NullChildren;
}
/// <summary>
/// The function for the expression.
/// </summary>
public GuanFunc Func
{
get
{
return this.func;
}
}
/// <summary>
/// Whether the expression is a literal value.
/// </summary>
public bool IsLiteral
{
get
{
return (this.func is Literal);
}
}
/// <summary>
/// The list of argument expression.
/// </summary>
public IList<GuanExpression> Children
{
get
{
return this.children;
}
}
/// <summary>
/// Build a trace expression from a string.
/// </summary>
/// <param name="exp">The string expression.</param>
/// <returns>The trace expression built from the string.</returns>
public static GuanExpression Build(string exp, IGuanExpressionContext expressionContext = null)
{
if (string.IsNullOrEmpty(exp))
{
return null;
}
if (exp.StartsWith("{", StringComparison.Ordinal) && exp.EndsWith("}", StringComparison.Ordinal))
{
if (exp.StartsWith("{{", StringComparison.Ordinal) && exp.EndsWith("}}", StringComparison.Ordinal))
{
exp = "expression(\"" + EvalPropertyPattern.Replace(exp.Substring(2, exp.Length - 4), EvalProperty) + "\")";
}
else
{
exp = "add(\"" + EvalPropertyPattern.Replace(exp.Substring(1, exp.Length - 2), EvalProperty) + "\")";
}
}
try
{
return PrivateBuild(exp, expressionContext);
}
catch (Exception e)
{
throw new ArgumentException("Invalid expression: " + exp, e);
}
}
public static GuanExpression BuildGetExpression(string variable)
{
List<GuanExpression> child = new List<GuanExpression>(1)
{
new GuanExpression(variable)
};
return new GuanExpression(GetFunc.Singleton, child);
}
public static GuanFunc GetGuanFunc(string name)
{
GuanFunc result = GuanFunc.Get(name, null);
if (result == null)
{
Operator op = operators.Get(name);
if (op != null)
{
result = op.Func;
}
}
return result;
}
public GuanExpression Clone()
{
List<GuanExpression> children = new List<GuanExpression>();
foreach (GuanExpression expression in this.children)
{
children.Add(expression.Clone());
}
return new GuanExpression(this.func, children);
}
/// <summary>
/// Add a child node (argument) to the expression
/// tree node. Note that the order of the children
/// is usually important as it determines the order
/// of the arguments to the function.
/// </summary>
/// <param name="child">The child node.</param>
public void AddChild(GuanExpression child)
{
if (this.children == NullChildren)
{
this.children = new List<GuanExpression>();
}
this.children.Add(child);
return;
}
/// <summary>
/// Evaluate the expression within the given context.
/// </summary>
/// <param name="context">The context object.</param>
/// <returns>Result of the evaluation.</returns>
public object Evaluate(IPropertyContext context)
{
try
{
return this.func.Invoke(context, this.children);
}
catch (Exception e)
{
string message = string.Format(
CultureInfo.InvariantCulture,
"Error evaluating the expression {0}: {1}",
this,
e.Message);
throw new ExpressionException(message, e);
}
}
public GuanExpression EvaluateExpression(IPropertyContext context, string contextName, bool isFinal, IGuanExpressionContext expressionContext = null)
{
try
{
string expression = (string)this.func.Invoke(new NamedContext(context, contextName), this.children);
return GuanExpression.Build(isFinal ? expression : "{" + expression + "}", expressionContext);
}
catch (Exception e)
{
string message = string.Format(
CultureInfo.InvariantCulture,
"Error evaluating the expression {0}: {1}",
this,
e.Message);
throw new ExpressionException(message, e);
}
}
public GuanPredicate EvaluatePredicate(IPropertyContext context, string contextName, IGuanExpressionContext expressionContext = null)
{
GuanExpression expression = this.EvaluateExpression(context, contextName, true, expressionContext);
return new GuanPredicate(expression);
}
public bool IsSimpleGetExpression()
{
return (this.func == GetFunc.Singleton && this.children.Count == 1 && this.children[0].IsLiteral);
}
public List<string> GetContextVariables()
{
List<string> result = new List<string>();
this.GetContextVariable(result);
return result;
}
public void ChangeContextVariable(string oldName, string newName)
{
foreach (GuanExpression child in this.children)
{
child.ChangeContextVariable(oldName, newName);
if (this.func is GetFunc && child.func is Literal && oldName == (string)child.Evaluate(null))
{
child.func = new Literal(newName);
}
}
}
public void ChangeContextVariables(Dictionary<string, string> namePairs)
{
foreach (GuanExpression child in this.children)
{
child.ChangeContextVariables(namePairs);
if (this.func is GetFunc && child.func is Literal)
{
string oldName = (string)child.Evaluate(null);
string newName;
if (namePairs.TryGetValue(oldName, out newName))
{
child.func = new Literal(newName);
}
}
}
}
public void ReplaceContextVariable(string oldVariable, object value)
{
foreach (GuanExpression child in this.children)
{
if (this.func is GetFunc && child.func is Literal && (string)child.Evaluate(null) == oldVariable)
{
this.func = new Literal(value);
this.children = NullChildren;
}
else
{
child.ReplaceContextVariable(oldVariable, value);
}
}
}
public override string ToString()
{
StringBuilder result = new StringBuilder(1024);
_ = result.AppendFormat("{0}", this.func);
if (this.children.Count != 0)
{
_ = result.Append("(");
foreach (GuanExpression exp in this.children)
{
_ = result.AppendFormat("{0},", exp);
}
result.Length--;
_ = result.Append(")");
}
return result.ToString();
}
/// <summary>
/// Optimize the expression by binding literal arguments to
/// functions.
/// </summary>
internal void Bind()
{
bool isLiteral = true;
if (this.children != NullChildren)
{
for (int i = 0; i < this.children.Count; i++)
{
this.children[i].Bind();
isLiteral = isLiteral && (this.children[i].func is Literal);
}
this.func = this.func.Bind(this.children);
}
// Convert to literal if possible.
if (isLiteral && (this.func is StandaloneFunc) && !(this.func is Literal))
{
this.func = new Literal(this.Evaluate(null));
}
if (this.func is Literal)
{
this.children = NullChildren;
}
}
internal bool GetLiteral<T>(out T result)
{
if (this.func is Literal)
{
result = (T)this.Evaluate(null);
return true;
}
else
{
result = default(T);
return false;
}
}
private static Tri<Operator> CreateOperators()
{
Tri<Operator> result = new Tri<Operator>();
AddOperator(result, new Operator("(", Literal.Empty, 1));
AddOperator(result, new Operator(")", Literal.Empty, 1));
AddOperator(result, new Operator("||", OrFunc.Singleton, 2));
AddOperator(result, new Operator("&&", AndFunc.Singleton, 3));
AddOperator(result, new Operator(">", ComparisonFunc.GT, 4));
AddOperator(result, new Operator("<", ComparisonFunc.LT, 4));
AddOperator(result, new Operator(">=", ComparisonFunc.GE, 4));
AddOperator(result, new Operator("<=", ComparisonFunc.LE, 4));
AddOperator(result, new Operator("!", NotFunc.Singleton, 7));
AddOperator(result, new Operator("~~", GuanFunc.MatchFunc.Singleton, 4));
AddOperator(result, new Operator("=", PropertyMatchFunc.Equal, 4));
AddOperator(result, new Operator("==", ComparisonFunc.EQ, 4));
AddOperator(result, new Operator("!=", ComparisonFunc.NE, 4));
AddOperator(result, new Operator("+", AddFunc.Singleton, 5));
AddOperator(result, new Operator("-", MathsFunc.Minus, 5));
AddOperator(result, new Operator("*", MathsFunc.Multiply, 6));
AddOperator(result, new Operator("/", MathsFunc.Divide, 6));
AddOperator(result, new Operator("%", MathsFunc.Mod, 7));
return result;
}
private static void AddOperator(Tri<Operator> operators, Operator op)
{
operators.Add(op.Op, op);
}
private static void SetOperator(List<Operator> table, Operator propertyOperator)
{
int len = propertyOperator.Op.Length;
int i;
for (i = 0; i < table.Count && len <= table[i].Op.Length; i++)
{
if (table[i].Op == propertyOperator.Op)
{
table[i] = propertyOperator;
return;
}
}
table.Insert(i, propertyOperator);
}
private static Operator ReadOperator(string exp, ref int start)
{
return operators.Get(exp, ref start);
}
private static int SkipQuote(string exp, char quote, int index)
{
int i = index;
while (++i < exp.Length)
{
if (exp[i] == quote)
{
return i;
}
else if (exp[i] == '\\')
{
i++;
}
}
return index;
}
private static object ReadLiteral(string exp, char quote)
{
StringBuilder result = new StringBuilder(exp.Length);
for (int i = 0; i < exp.Length; i++)
{
if (exp[i] != quote)
{
if (exp[i] == '\\' && (i + 1) < exp.Length)
{
i++;
_ = result.Append(exp[i]);
}
else
{
_ = result.Append(exp[i]);
}
}
}
string value = result.ToString();
if (quote == '\0')
{
long intValue;
if (long.TryParse(value, out intValue))
{
return intValue;
}
}
return value;
}
private static GuanExpression ReadFunc(string exp, string name, IGuanExpressionContext expressionContext, ref int start)
{
GuanFunc func = GuanFunc.Get(name, expressionContext);
if (func == null)
{
throw new ArgumentException("function not found: " + name);
}
GuanExpression result = new GuanExpression(func);
int level = 0;
for (int i = start; i < exp.Length; i++)
{
char c = exp[i];
if (c == '(')
{
level++;
}
else if (c == '"' || c == '\'')
{
i = SkipQuote(exp, c, i);
}
else if ((c == ',' || c == ')') && level == 0)
{
string argExp = exp.Substring(start, i - start).Trim();
if (argExp.Length > 0)
{
GuanExpression child = Build(argExp, expressionContext);
result.AddChild(child);
}
else if (c == ',' || result.Children.Count > 0)
{
result.AddChild(new GuanExpression(new Literal(null)));
}
start = i + 1;
}
if (c == ')')
{
if (level == 0)
{
start = i + 1;
return result;
}
level--;
}
}
throw new ArgumentException("Invalid function expression: " + exp);
}
private static GuanExpression ReadToken(string exp, IGuanExpressionContext expressionContext, ref int start)
{
if (start >= exp.Length)
{
return null;
}
char quote = '\0';
int i;
for (i = start; i < exp.Length; i++)
{
char c = exp[i];
// Operators are always non-alphanumerical
if (!char.IsLetterOrDigit(c))
{
if (c == '"' || c == '\'')
{
quote = c;
i = SkipQuote(exp, c, i);
}
else
{
if (c == '(')
{
// A '(' without leading characters is not a function
// but an operator.
string name = exp.Substring(start, i - start).Trim();
if (name.Length > 0)
{
start = i + 1;
return ReadFunc(exp, name, expressionContext, ref start);
}
}
if (c == '-')
{
continue;
}
int j = i;
if (ReadOperator(exp, ref j) != null)
{
break;
}
}
}
}
string token = exp.Substring(start, i - start).Trim();
start = i;
if (token.Length == 0)
{
return null;
}
return new GuanExpression(ReadLiteral(token, quote));
}
private static void Process(
Stack<Operator> operators,
Stack<GuanExpression> tokens)
{
Operator op = operators.Pop();
GuanExpression result = new GuanExpression(op.Func);
if (tokens.Count < 2)
{
throw new ArgumentException("Expression not well formed");
}
GuanExpression token2 = tokens.Pop();
GuanExpression token1 = tokens.Pop();
if (token1 != null)
{
result.AddChild(token1);
}
if (token2 != null)
{
result.AddChild(token2);
}
tokens.Push(result);
}
private static GuanExpression PrivateBuild(string exp, IGuanExpressionContext expressionContext)
{
Stack<Operator> operators = new Stack<Operator>();
Stack<GuanExpression> tokens = new Stack<GuanExpression>();
operators.Push(Sentinel);
// We consider the expression to be made of token and operators
// alternatively. For unary operator, we will consider there
// to be a null token between it and the other token. Similarly
// for operator that does not have any argument, we consider it
// to have two null tokens around it.
// "(" is a special case where we will remove the preceding null
// token immediately before we push it on operator stack.
// Also when ")" is processed, we look for a next operator instead
// of null token. It can be considered that the null token after
// ")" is consumed immediately.
int start = 0;
bool isToken = true;
// The expression is considered to end with a token (can be null)
// so the loop always exit when searching for an operator while
// reaching the end of the expression.
while (isToken || start < exp.Length)
{
if (isToken)
{
GuanExpression token = ReadToken(exp, expressionContext, ref start);
tokens.Push(token);
}
else
{
// Filter white space when matching operators.
while (start < exp.Length && char.IsWhiteSpace(exp[start]))
{
start++;
}
Operator op = ReadOperator(exp, ref start);
if (op == null)
{
string msg = string.Format(
CultureInfo.InvariantCulture,
"Expression {0} not well formed. Unrecognized operator: {1}",
exp,
exp.Substring(start));
throw new ArgumentException(msg);
}
if (op.Op == "(")
{
GuanExpression dummy = tokens.Pop();
ReleaseAssert.IsTrue(
dummy == null,
"Expression: {0}",
exp);
operators.Push(op);
}
else if (op.Op == ")")
{
while (operators.Peek().Op != "(")
{
Process(operators, tokens);
}
_ = operators.Pop();
isToken = true;
}
else
{
// Process the higher priority operators on the stack.
while (operators.Peek().Level >= op.Level)
{
Process(operators, tokens);
}
operators.Push(op);
}
}
isToken = !isToken;
}
// Leave the sential
while (operators.Count > 1)
{
Process(operators, tokens);
}
ReleaseAssert.IsTrue(
tokens.Count == 1,
"Expression: {0}",
exp);
GuanExpression result = tokens.Pop();
result.Bind();
return result;
}
private static string EvalProperty(Match match)
{
string name = match.Groups[1].Value;
name = name.Substring(1, name.Length - 2);
if (name.Contains("(") && name.Contains(")"))
{
return "\"," + name + ",\"";
}
return "\",get(" + name + "),\"";
}
private void GetContextVariable(List<string> result)
{
for (int i = 0; i < this.children.Count; i++)
{
GuanExpression child = this.children[i];
if (this.func is GetFunc && i == 0 && child.func is Literal)
{
string variable = (string)child.Evaluate(null);
if (!result.Contains(variable))
{
result.Add(variable);
}
}
else
{
child.GetContextVariable(result);
}
}
}
/// <summary>
/// Operators that can appear in the string expression.
/// </summary>
internal class Operator
{
private string op;
private GuanFunc func;
private int level;
public Operator(string op, GuanFunc func, int level)
{
this.op = op;
this.func = func;
this.level = level;
}
internal string Op
{
get
{
return this.op;
}
}
internal GuanFunc Func
{
get
{
return this.func;
}
}
internal int Level
{
get
{
return this.level;
}
}
public override string ToString()
{
return this.op;
}
internal bool Match(string exp, ref int start)
{
if (start + this.op.Length > exp.Length)
{
return false;
}
for (int i = 0; i < this.op.Length; i++)
{
if (this.op[i] != exp[start + i])
{
return false;
}
}
start += this.op.Length;
return true;
}
}
[Serializable]
internal class ExpressionException : GuanException
{
public ExpressionException(string message)
: base(message)
{
}
public ExpressionException(string message, Exception inner)
: base(message, inner)
{
}
}
private class NamedContext : IPropertyContext
{
private IPropertyContext original;
private string name;
public NamedContext(IPropertyContext context, string name)
{
this.original = context;
this.name = name + ":";
}
public object this[string name]
{
get
{
if (!name.StartsWith(this.name))
{
return "<" + name + ">";
}
return this.original[name.Substring(this.name.Length)];
}
}
}
}
}

572
guan/Guan/Logic/GuanFunc.cs Normal file
Просмотреть файл

@ -0,0 +1,572 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text.RegularExpressions;
/// <summary>
/// The abstract function that must be inherited from for
/// all property functions.
/// A property function calculates a value based on arguments
/// and a context, which exposes properties.
/// </summary>
public abstract class GuanFunc
{
private static object tableLock = new object();
private static Dictionary<string, GuanFunc> funcTable = new Dictionary<string, GuanFunc>();
private static bool externalResourceLoaded = false;
private readonly string name;
protected GuanFunc(string name)
{
this.name = name;
}
/// <summary>
/// Function name.
/// </summary>
public string Name
{
get
{
return this.name;
}
}
/// <summary>
/// Set an external property function.
/// This allows the user to supply their own functions.
/// </summary>
/// <param name="func">The function to be set.</param>
public static void Add(GuanFunc func)
{
if (func == null)
{
throw new ArgumentNullException("func");
}
lock (tableLock)
{
Dictionary<string, GuanFunc> table = new Dictionary<string, GuanFunc>(funcTable);
table[func.ToString()] = func;
funcTable = table;
}
}
/// <summary>
/// Get a function object based on its name.
/// </summary>
/// <param name="name">Name of the function.</param>
/// <returns>The function object.</returns>
public static GuanFunc Get(string name, IGuanExpressionContext context)
{
GuanFunc result = (context != null ? context.GetFunc(name) : null);
if (result == null)
{
if (!externalResourceLoaded)
{
lock (tableLock)
{
if (!externalResourceLoaded)
{
AutoLoadResource.LoadResources(typeof(GuanFunc));
externalResourceLoaded = true;
}
}
}
_ = funcTable.TryGetValue(name, out result);
}
return result;
}
/// <summary>
/// The main method to be overridden by implementing class.
/// Result object should be calculated based on the context
/// and the arguments.
/// </summary>
/// <param name="context">The context class.</param>
/// <param name="args">The array of arguments to the function.</param>
/// <returns>The function result.</returns>
public abstract object Invoke(IPropertyContext context, object[] args);
public override string ToString()
{
return this.name;
}
/// <summary>
/// This allows the possibility to invoke a function
/// without fully evaluate all of the arguments (e.g. And).
/// Most implementing classes do not need to override this.
/// </summary>
/// <param name="context">The context class.</param>
/// <param name="args">The collection of arguments to the function.</param>
/// <returns>The function result.</returns>
internal virtual object Invoke(IPropertyContext context, List<GuanExpression> args)
{
object[] evaluatedArgs;
if (args != null)
{
evaluatedArgs = new object[args.Count];
for (int i = 0; i < args.Count; i++)
{
evaluatedArgs[i] = args[i].Evaluate(context);
}
}
else
{
evaluatedArgs = null;
}
return this.Invoke(context, evaluatedArgs);
}
/// <summary>
/// Optimize the function by binding some arguments to literal
/// values.
/// </summary>
/// <param name="args">The arguments to the function.</param>
/// <returns>The optimized function object. If no binding
/// optimization can be made, the function object itself
/// should be returned.
/// </returns>
internal virtual GuanFunc Bind(List<GuanExpression> args)
{
return this;
}
/// <summary>
/// Function for regular expression matching.
/// The first parameter is string in question and the 2nd
/// is the regular expression.
/// </summary>
internal class MatchFunc : BinaryFunc
{
public static readonly MatchFunc Singleton = new MatchFunc("match", false);
public static readonly MatchFunc Not = new MatchFunc("notmatch", true);
private readonly bool negative;
private MatchFunc(string name, bool negative)
: base(name)
{
this.negative = negative;
}
internal override GuanFunc Bind(List<GuanExpression> args)
{
if (args.Count == 2)
{
string pattern;
if (args[1].GetLiteral(out pattern))
{
args.RemoveAt(1);
return new PatternFunc(pattern, this.negative);
}
}
return this;
}
protected override object InvokeBinary(object arg1, object arg2)
{
string s2 = arg2 as string;
if (s2 == null)
{
throw new ArgumentException("arg2 must be a regular expression");
}
string s1 = arg1 as string;
if (s1 == null)
{
if (arg1 == null)
{
return false;
}
s1 = arg1.ToString();
}
return (new Regex(s2).IsMatch(s1) != this.negative);
}
}
internal class GetFromObjectFunc : StandaloneFunc
{
public static readonly GetFromObjectFunc Singleton = new GetFromObjectFunc("getobject");
private GetFromObjectFunc(string name)
: base(name)
{
}
public override object Invoke(object[] args)
{
ReleaseAssert.IsTrue(args.Length == 2);
Term term0 = args[0] as Term;
return GetFunc.Invoke(term0 != null ? term0.GetObjectValue() : args[0], (string)args[1]);
}
}
/// <summary>
/// Function for regular expression matching with given regular expression.
/// </summary>
internal class PatternFunc : UnaryFunc
{
private readonly Regex pattern;
private readonly bool negative;
public PatternFunc(string pattern, bool negative)
: base("pattern:" + pattern)
{
this.pattern = new Regex(pattern, RegexOptions.Compiled);
this.negative = negative;
}
public override object UnaryInvoke(object arg)
{
string text = arg as string;
if (text == null)
{
if (arg == null)
{
return false;
}
text = arg.ToString();
}
return (this.pattern.IsMatch(text) != this.negative);
}
}
internal class ToLowerFunc : UnaryFunc
{
public static readonly ToLowerFunc Singleton = new ToLowerFunc();
protected ToLowerFunc()
: base("ToLower")
{
}
public override object UnaryInvoke(object arg)
{
if (arg == null)
{
return string.Empty;
}
string result = arg.ToString();
return result?.ToLower(CultureInfo.InvariantCulture);
}
}
internal class ToUpperFunc : UnaryFunc
{
public static readonly ToUpperFunc Singleton = new ToUpperFunc();
protected ToUpperFunc()
: base("ToUpper")
{
}
public override object UnaryInvoke(object arg)
{
if (arg == null)
{
return string.Empty;
}
string result = arg.ToString();
return result?.ToUpper(CultureInfo.InvariantCulture);
}
}
internal class ExistsFunc : GuanFunc
{
public static readonly ExistsFunc Singleton = new ExistsFunc();
private ExistsFunc()
: base("Exists")
{
}
public override object Invoke(IPropertyContext context, object[] args)
{
object result = GetFunc.Singleton.Invoke(context, args);
return (result != null);
}
}
internal class NotExistsFunc : GuanFunc
{
public static readonly NotExistsFunc Singleton = new NotExistsFunc();
private NotExistsFunc()
: base("NotExists")
{
}
public override object Invoke(IPropertyContext context, object[] args)
{
object result = GetFunc.Singleton.Invoke(context, args);
return (result == null);
}
}
internal class NotEmptyFunc : GuanFunc
{
public static readonly NotEmptyFunc Singleton = new NotEmptyFunc();
private NotEmptyFunc()
: base("NotEmpty")
{
}
public override object Invoke(IPropertyContext context, object[] args)
{
foreach (object arg in args)
{
if (arg == null)
{
return false;
}
string stringArg = arg as string;
if (stringArg != null && stringArg.Length == 0)
{
return false;
}
}
return true;
}
}
internal class EmptyFunc : GuanFunc
{
public static readonly EmptyFunc Singleton = new EmptyFunc();
private EmptyFunc()
: base("Empty")
{
}
public override object Invoke(IPropertyContext context, object[] args)
{
foreach (object arg in args)
{
if (arg == null)
{
return true;
}
string stringArg = arg as string;
if (string.IsNullOrEmpty(stringArg))
{
return true;
}
}
return false;
}
}
internal class ExpressionFunc : GuanFunc
{
public static readonly ExpressionFunc Singleton = new ExpressionFunc();
private ExpressionFunc()
: base("expression")
{
}
public override object Invoke(IPropertyContext context, object[] args)
{
return AddFunc.Singleton.Invoke(args);
}
}
internal class DateTimeFunc : UnaryFunc
{
public static readonly DateTimeFunc Singleton = new DateTimeFunc();
private DateTimeFunc()
: base("DateTime")
{
}
public override object UnaryInvoke(object arg)
{
return DateTime.Parse((string)arg);
}
}
internal class TimeSpanFunc : UnaryFunc
{
public static readonly TimeSpanFunc Singleton = new TimeSpanFunc();
private TimeSpanFunc()
: base("TimeSpan")
{
}
public override object UnaryInvoke(object arg)
{
return TimeSpan.Parse((string)arg);
}
}
internal class IntFunc : UnaryFunc
{
public static readonly IntFunc Singleton = new IntFunc();
private IntFunc()
: base("int")
{
}
public override object UnaryInvoke(object arg)
{
return Utility.Convert<int>(arg);
}
}
internal class LongFunc : UnaryFunc
{
public static readonly LongFunc Singleton = new LongFunc();
private LongFunc()
: base("long")
{
}
public override object UnaryInvoke(object arg)
{
return Utility.Convert<long>(arg);
}
}
internal class DoubleFunc : UnaryFunc
{
public static readonly DoubleFunc Singleton = new DoubleFunc();
private DoubleFunc()
: base("double")
{
}
public override object UnaryInvoke(object arg)
{
return Utility.Convert<double>(arg);
}
}
internal class ListFunc : StandaloneFunc
{
public static readonly ListFunc Singleton = new ListFunc();
private ListFunc()
: base("List")
{
}
public override object Invoke(object[] args)
{
List<object> result = new List<object>(args.Length);
foreach (object arg in args)
{
result.Add(arg);
}
return result;
}
}
internal class MinMaxFunc : StandaloneFunc
{
public static readonly MinMaxFunc Min = new MinMaxFunc("min");
public static readonly MinMaxFunc Max = new MinMaxFunc("max");
private ComparisonFunc func;
private MinMaxFunc(string name)
: base(name)
{
this.func = (name == "min" ? ComparisonFunc.LT : ComparisonFunc.GT);
}
public override object Invoke(object[] args)
{
object result = args[0];
for (int i = 1; i < args.Length; i++)
{
if (this.func.Invoke(args[i], result))
{
result = args[i];
}
}
return result;
}
}
internal class TimeFunc : StandaloneFunc
{
public static readonly TimeFunc Singleton = new TimeFunc();
private TimeFunc()
: base("time")
{
}
public override object Invoke(object[] args)
{
if (args.Length > 1)
{
throw new ArgumentException("Time() does not take more than one argument");
}
if (args.Length == 0)
{
return DateTime.UtcNow;
}
return DateTime.UtcNow + (TimeSpan)args[0];
}
}
internal class GuidFunc : StandaloneFunc
{
public static readonly GuidFunc Singleton = new GuidFunc();
private GuidFunc()
: base("guid")
{
}
public override object Invoke(object[] args)
{
if (args.Length > 0)
{
throw new ArgumentException("guid() does not take argument");
}
return Guid.NewGuid().ToString();
}
}
}
}

Просмотреть файл

@ -0,0 +1,485 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
public class GuanObject : IPropertyContext, IWritablePropertyContext
{
private static readonly char[] PathDelimit = new char[] { '/' };
private static readonly List<string> EmptyList = new List<string>();
private object value;
private GuanObject parent;
private SortedDictionary<string, GuanObject> children;
private Dictionary<string, string> aliases;
public GuanObject()
{
this.children = new SortedDictionary<string, GuanObject>();
}
public GuanObject(GuanObject other, GuanObject parent = null)
{
this.parent = parent;
this.aliases = other.aliases;
this.children = new SortedDictionary<string, GuanObject>();
this.CopyFrom(other);
}
public SortedDictionary<string, GuanObject> Children
{
get
{
return this.children;
}
}
protected Dictionary<string, string> Aliases
{
get
{
return this.aliases;
}
set
{
this.aliases = value;
}
}
public virtual object this[string name]
{
get
{
string path = name;
GuanObject obj = this.GetNode(ref path, false);
if (obj == null)
{
if (this.aliases != null && this.aliases.TryGetValue(name, out path))
{
obj = this.GetNode(ref path, false);
}
if (obj == null)
{
return null;
}
}
return obj.GetValue(path);
}
set
{
this.SetValue(name, value, null);
}
}
public IEnumerator GetEnumerator()
{
return this.children.GetEnumerator();
}
public void CopyFrom(GuanObject other)
{
this.value = other.value;
this.CopyChildren(other);
}
public void CopyChildren(GuanObject other, Dictionary<string, string> children = null, bool overwrite = true)
{
foreach (KeyValuePair<string, GuanObject> entry in other.children)
{
if (children == null)
{
if (overwrite || !this.children.ContainsKey(entry.Key))
{
this.children[entry.Key] = new GuanObject(entry.Value, this);
}
}
else
{
string newKey;
if (children.TryGetValue(entry.Key, out newKey))
{
this.children.Add(newKey, new GuanObject(entry.Value, this));
}
}
}
}
public GuanObject GetObject(string name)
{
string path = name;
return this.GetNode(ref path, false);
}
public bool ContainsKey(string key)
{
GuanObject obj = this.GetNode(ref key, false);
return (obj != null);
}
public T GetValue<T>(string name)
{
object value = this[name];
return Utility.Convert<T>(value);
}
public virtual void SetValue(string name, object value, string operation)
{
if (operation == "delete")
{
_ = this.Delete(name);
return;
}
string path = name;
GuanObject obj = this.GetNode(ref path, true);
GuanObject child = value as GuanObject;
if (operation == "Children")
{
if (child != null)
{
obj.CopyChildren(child);
}
}
else if (child == null)
{
obj.SetValue(value, operation);
}
else
{
foreach (KeyValuePair<string, GuanObject> entry in obj.parent.children)
{
if (entry.Value == obj)
{
obj.parent.children[entry.Key] = child;
child.parent = obj.parent;
return;
}
}
ReleaseAssert.Fail("Child not found");
}
}
public void AddKey(string name)
{
this.SetValue(name, null, null);
}
public bool Delete(string name)
{
string path = name;
GuanObject obj = this.GetNode(ref path, false);
if (obj == null)
{
return false;
}
if (path.Length != 0)
{
throw new ArgumentException("name");
}
return obj.parent.children.Remove(this.GetLeafName(name));
}
public object GetValue()
{
return this.value;
}
public override string ToString()
{
StringBuilder result = new StringBuilder();
if (this.children.Count > 0)
{
_ = result.Append("{");
}
if (this.value != null)
{
IEnumerable valueCollection = this.value as IEnumerable;
if (valueCollection != null && this.value.GetType() != typeof(string))
{
_ = result.Append("(");
int i = 0;
foreach (object entry in valueCollection)
{
if (entry != null)
{
if (i > 0)
{
_ = result.Append(",");
}
_ = result.Append(entry.ToString());
i++;
}
}
_ = result.Append(")");
}
else
{
_ = result.Append(this.value);
}
}
if (this.children.Count > 0)
{
if (this.children.Count > 12)
{
if (result.Length > 1)
{
_ = result.Append(",");
}
_ = result.AppendFormat("#Count:{0}", this.children.Count);
}
else
{
foreach (KeyValuePair<string, GuanObject> entry in this.children)
{
if (!entry.Key.StartsWith("_"))
{
if (result.Length > 1)
{
_ = result.Append(",");
}
_ = result.AppendFormat("{0}:{1}", entry.Key, entry.Value);
}
}
}
if (result.Length > 1)
{
_ = result.Append("}");
}
else
{
result.Length = 0;
}
}
return result.ToString();
}
private string GetLeafName(string name)
{
int index = name.LastIndexOf('/');
if (index < 0)
{
return name;
}
return name.Substring(index + 1);
}
private object GetValue(string name)
{
if (name == "#Count")
{
return this.children.Count;
}
if (this.value != null)
{
return this.value;
}
return this;
}
private void SetValue(object value, string operation)
{
if (operation == null)
{
this.value = value;
}
else if (value != null)
{
if (operation == "+")
{
this.Add(value);
}
else if (operation == "count")
{
if (value != null)
{
this.Count(value);
}
}
else
{
ReleaseAssert.IsTrue(operation == "Create", "Unknown operation: " + operation);
}
}
}
private void Count(object value)
{
string key = value.ToString();
GuanObject obj = this.GetNode(ref key, true);
obj.Add(1);
}
private GuanObject GetNode(ref string name, bool create)
{
if (string.IsNullOrEmpty(name))
{
return this;
}
GuanObject result = this;
string[] path = name.Split(PathDelimit, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < path.Length; i++)
{
if (path[i] == "#append")
{
path[i] = result.children.Count.ToString();
}
if (path[i].StartsWith("#", StringComparison.InvariantCulture))
{
if (i < path.Length - 1)
{
throw new ArgumentException("Invalid name: " + name);
}
name = path[i];
return result;
}
GuanObject child;
if (!result.children.TryGetValue(path[i], out child))
{
if (!create)
{
return null;
}
child = new GuanObject();
child.parent = result;
result.children.Add(path[i], child);
}
result = child;
}
name = string.Empty;
return result;
}
private void Add(object value)
{
this.value = (Utility.Convert<long>(this.value) + Utility.Convert<long>(value)).ToString();
}
private class EncodePathFunc : UnaryFunc
{
public static readonly EncodePathFunc Singleton = new EncodePathFunc();
private EncodePathFunc()
: base("EncodePath")
{
}
public override object UnaryInvoke(object arg)
{
string input = (string)arg;
return input.Replace("/", "%2F");
}
}
private class DecodePathFunc : UnaryFunc
{
public static readonly DecodePathFunc Singleton = new DecodePathFunc();
private DecodePathFunc()
: base("DecodePath")
{
}
public override object UnaryInvoke(object arg)
{
string input = (string)arg;
return input.Replace("%2F", "/");
}
}
private class ListObjFunc : GuanFunc
{
public static readonly ListObjFunc Singleton = new ListObjFunc();
private ListObjFunc()
: base("ListObj")
{
}
public override object Invoke(IPropertyContext context, object[] args)
{
if (args.Length != 1 && args.Length != 2)
{
throw new ArgumentException("Need 1 or 2 arguments");
}
GuanObject result = new GuanObject();
IEnumerable arg1 = this.ConvertArg(args[0]);
if (arg1 == null)
{
throw new ArgumentException("Invalid argument in ListObj");
}
if (args.Length == 1)
{
foreach (object obj in arg1)
{
result["#append"] = obj;
}
}
else
{
IEnumerable arg2 = this.ConvertArg(args[1]);
if (arg1 == null)
{
throw new ArgumentException("Invalid argument ListObj");
}
IEnumerator enumerator1 = arg1.GetEnumerator();
IEnumerator enumerator2 = arg2.GetEnumerator();
while (enumerator1.MoveNext() && enumerator2.MoveNext())
{
if (enumerator1.Current != null)
{
result[enumerator1.Current.ToString()] = enumerator2.Current;
}
}
}
return result;
}
private IEnumerable ConvertArg(object arg)
{
if (arg is string)
{
return null;
}
return arg as IEnumerable;
}
}
}
}

Просмотреть файл

@ -0,0 +1,98 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
public class GuanPredicate
{
public static readonly GuanPredicate MatchAll = new GuanPredicate(true);
public static readonly GuanPredicate MatchNothing = new GuanPredicate(false);
private GuanExpression exp;
public GuanPredicate(GuanExpression exp)
{
this.exp = exp;
}
private GuanPredicate(bool matchAll)
{
this.exp = new GuanExpression(matchAll ? Literal.True : Literal.False);
}
public GuanExpression Expression
{
get
{
return this.exp;
}
}
public static GuanPredicate Build(string exp, IGuanExpressionContext expressionContext = null)
{
GuanExpression expression = GuanExpression.Build(exp, expressionContext);
GuanPredicate result = new GuanPredicate(expression);
if (expression.IsLiteral)
{
result = (result.Match(null) ? MatchAll : MatchNothing);
}
return result;
}
public GuanPredicate Clone()
{
return new GuanPredicate(this.exp.Clone());
}
public bool Match(IPropertyContext context)
{
if (this.exp == null)
{
return true;
}
object result = this.exp.Evaluate(context);
if (result == null)
{
return false;
}
if (result is bool)
{
return (bool)result;
}
string s = result as string;
if (s != null)
{
return s.Length > 0;
}
return true;
}
public bool SafeMatch(IPropertyContext context)
{
try
{
return this.Match(context);
}
catch (GuanExpression.ExpressionException)
{
return false;
}
}
public override string ToString()
{
if (this.exp == null)
{
return "MatchAll";
}
return this.exp.ToString();
}
}
}

Просмотреть файл

@ -0,0 +1,14 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
/// <summary>
/// Interface providing functor (including predicate type) implementations.
/// </summary>
public interface IFunctorProvider
{
Functor FindFunctor(string name, Module from);
}
}

Просмотреть файл

@ -0,0 +1,11 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
public interface IGuanExpressionContext
{
GuanFunc GetFunc(string name);
}
}

Просмотреть файл

@ -0,0 +1,42 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
/// <summary>
/// Interface implemented by classes that can expose named properties.
/// The implementing class is free to define any property it wants
/// to expose. In the advanced scenario, the object can even define
/// an infinite set of properties.
/// </summary>
public interface IPropertyContext
{
/// <summary>
/// Property with a string name.
/// </summary>
/// <param name="name">Property name.</param>
/// <returns>Property value, null if not found or the value
/// is explicitly set to null.</returns>
object this[string name]
{
get;
}
}
public interface IWritablePropertyContext
{
/// <summary>
/// Property with a string name.
/// </summary>
/// <param name="name">Property name.</param>
/// <returns>Property value, null if not found or the value
/// is explicitly set to null.</returns>
#pragma warning disable CA1044 // Properties should not be write only
object this[string name]
#pragma warning restore CA1044 // Properties should not be write only
{
set;
}
}
}

Просмотреть файл

@ -0,0 +1,11 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
public interface IWaitingTask
{
void Start();
}
}

Просмотреть файл

@ -0,0 +1,47 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
/// <summary>
/// Variable term used in rules. At runtime they will be converted to Variable objects.
/// </summary>
internal class IndexedVariable : Term
{
private int index;
private string name;
public IndexedVariable(int index, string name)
{
this.index = index;
this.name = name;
}
public int Index
{
get
{
return this.index;
}
}
public string Name
{
get
{
return this.name;
}
}
public override bool IsGround()
{
return false;
}
public override string ToString()
{
return "?" + this.name + "_" + this.index.ToString();
}
}
}

Просмотреть файл

@ -0,0 +1,43 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System.Threading.Tasks;
/// <summary>
/// Predicate type for "is".
/// </summary>
internal class IsPredicateType : PredicateType
{
public static readonly IsPredicateType Singleton = new IsPredicateType();
private IsPredicateType()
: base("is", true, 2, 2)
{
}
public override PredicateResolver CreateResolver(CompoundTerm input, Constraint constraint, QueryContext context)
{
return new Resolver(input, constraint, context);
}
private class Resolver : GroundPredicateResolver
{
public Resolver(CompoundTerm input, Constraint constraint, QueryContext context)
: base(input, constraint, context, 1)
{
}
protected override Task<Term> GetNextTermAsync()
{
Term term = this.Input.Arguments[1].Value.ForceEvaluate(this.Context);
CompoundTerm result = new CompoundTerm(Singleton, null);
result.AddArgument(term, "0");
return Task.FromResult<Term>(result);
}
}
}
}

Просмотреть файл

@ -0,0 +1,91 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System;
using System.Text.Json;
internal sealed class JsonPropertyContext : IPropertyContext
{
private JsonElement json;
public JsonPropertyContext(JsonElement json)
{
this.json = json;
}
public object this[string name]
{
get
{
string nextName = null;
int index = name.IndexOf('.');
if (index > 0)
{
nextName = name.Substring(index + 1);
name = name.Substring(0, index);
}
index = -1;
if (name.EndsWith(']'))
{
index = name.LastIndexOf('[');
if (index > 0)
{
string indexString = name.Substring(index + 1, name.Length - index - 2);
name = name.Substring(0, index);
if (!int.TryParse(indexString, out index))
{
index = -1;
}
}
}
JsonElement result;
if (!this.json.TryGetProperty(name, out result))
{
return null;
}
if (index >= 0)
{
if (result.ValueKind != JsonValueKind.Array)
{
throw new ArgumentException("Property not an array: " + name);
}
result = result[index];
}
if (nextName != null)
{
return new JsonPropertyContext(result)[nextName];
}
switch (result.ValueKind)
{
case JsonValueKind.Null:
return null;
case JsonValueKind.True:
return true;
case JsonValueKind.False:
return false;
case JsonValueKind.Number:
return result.GetInt64();
case JsonValueKind.String:
return result.GetString();
default:
return new JsonPropertyContext(result);
}
}
}
public override string ToString()
{
return this.json.ToString();
}
}
}

Просмотреть файл

@ -0,0 +1,39 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
/// <summary>
/// A variable bound to another variable.
/// </summary>
internal class LinkedVariable : Variable
{
private Variable original;
public LinkedVariable(VariableBinding binding, Variable original, string name)
: base(name, binding)
{
this.original = original;
}
public LinkedVariable(LinkedVariable other, VariableBinding binding)
: base(other, binding)
{
this.original = other.original;
}
public Variable Original
{
get
{
return this.original;
}
set
{
this.original = value;
}
}
}
}

148
guan/Guan/Logic/ListTerm.cs Normal file
Просмотреть файл

@ -0,0 +1,148 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System.Collections;
using System.Text;
/// <summary>
/// Logic specific to list.
/// </summary>
public static class ListTerm
{
private static Functor listFunctor = new Functor(".");
public static Functor ListFunctor
{
get
{
return listFunctor;
}
}
public static CompoundTerm Add(CompoundTerm current, Term child)
{
if (current.Arguments.Count == 0)
{
current.AddArgument(child, "0");
return current;
}
ReleaseAssert.IsTrue(current.Arguments.Count == 1);
CompoundTerm tail = new CompoundTerm(ListFunctor, null);
current.AddArgument(tail, "1");
tail.AddArgument(child, "0");
return tail;
}
public static string ToString(CompoundTerm term)
{
StringBuilder result = new StringBuilder();
_ = result.Append("[");
CompoundTerm current = term;
Term next;
do
{
_ = result.Append(current.Arguments[0].Value).Append(',');
next = current.Arguments[1].Value.GetEffectiveTerm();
current = next as CompoundTerm;
}
while (current != null && current.Functor.Name == ".");
result.Length--;
if (next == Constant.Nil)
{
_ = result.Append("]");
}
else
{
_ = result.AppendFormat("|{0}]", next);
}
return result.ToString();
}
public static Term Parse(CompoundTerm term)
{
if (term.Arguments.Count == 0)
{
return Constant.Nil;
}
Term head = term.Arguments[0].Value;
Term tail;
ReleaseAssert.IsTrue(term.Arguments.Count == 1);
CompoundTerm compound = head as CompoundTerm;
if (compound != null && compound.Functor.Name == "|")
{
ReleaseAssert.IsTrue(compound.Arguments.Count == 2);
head = compound.Arguments[0].Value;
tail = compound.Arguments[1].Value;
}
else
{
tail = Constant.Nil;
}
CompoundTerm result = new CompoundTerm(ListTerm.ListFunctor, VariableBinding.Ground);
CompoundTerm current = result;
while (head != null)
{
compound = head as CompoundTerm;
if (compound != null && compound.Functor.Name == ",")
{
ReleaseAssert.IsTrue(compound.Arguments.Count == 2);
current.AddArgument(compound.Arguments[0].Value, "0");
head = compound.Arguments[1].Value;
CompoundTerm next = new CompoundTerm(ListTerm.ListFunctor, VariableBinding.Ground);
current.AddArgument(next, "1");
current = next;
}
else
{
current.AddArgument(head, "0");
head = null;
}
}
current.AddArgument(tail, "1");
return result;
}
public static Term FromEnumerable(IEnumerable collection)
{
Term result = Constant.Nil;
CompoundTerm current = null;
int count = 0;
foreach (object member in collection)
{
CompoundTerm next = new CompoundTerm(ListFunctor);
if (current != null)
{
current.AddArgument(next, "1");
count++;
}
else
{
result = next;
}
current = next;
current.AddArgument(Term.FromObject(member), "0");
}
if (current != null)
{
current.AddArgument(Constant.Nil, "1");
count++;
}
return result;
}
}
}

Просмотреть файл

@ -0,0 +1,34 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
/// <summary>
/// Literal "function" which always returns the literal value itself.
/// </summary>
public class Literal : StandaloneFunc
{
internal static readonly Literal Empty = new Literal(null);
internal static readonly Literal True = new Literal(true);
internal static readonly Literal False = new Literal(false);
private readonly object value;
/// <summary>
/// Initializes a new instance of the <see cref="Literal"/> class.
/// Constructor.
/// </summary>
/// <param name="value">The value of the literal.</param>
public Literal(object value)
: base(value != null ? value.ToString() : string.Empty)
{
this.value = value;
}
public override object Invoke(object[] args)
{
return this.value;
}
}
}

Просмотреть файл

@ -0,0 +1,64 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System.Threading.Tasks;
/// <summary>
/// Predicate type for writing a line to log.
/// </summary>
internal class LogPredicateType : PredicateType
{
public static readonly LogPredicateType LogInfo = new LogPredicateType("LogInfo");
public static readonly LogPredicateType LogWarning = new LogPredicateType("LogWarning");
public static readonly LogPredicateType LogError = new LogPredicateType("LogError");
public LogPredicateType(string name)
: base(name, true, 1)
{
}
public override PredicateResolver CreateResolver(CompoundTerm input, Constraint constraint, QueryContext context)
{
return new Resolver(this, input, constraint, context);
}
private class Resolver : BooleanPredicateResolver
{
private LogPredicateType type;
public Resolver(LogPredicateType type, CompoundTerm input, Constraint constraint, QueryContext context)
: base(input, constraint, context)
{
this.type = type;
}
protected override Task<bool> CheckAsync()
{
string format = this.GetInputArgumentString(0);
object[] args = new object[this.Input.Arguments.Count - 1];
for (int i = 1; i < this.Input.Arguments.Count; i++)
{
args[i - 1] = this.GetInputArgument(i);
}
if (this.type == LogError)
{
EventLogWriter.WriteError(format, args);
}
else if (this.type == LogWarning)
{
EventLogWriter.WriteWarning(format, args);
}
else
{
EventLogWriter.WriteInfo(format, args);
}
return Task.FromResult(true);
}
}
}
}

Просмотреть файл

@ -0,0 +1,234 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System;
internal class MathsFunc : StandaloneFunc
{
private static MathsFunc minus = new MathsFunc("minus", '-');
private static MathsFunc multiply = new MathsFunc("multiply", '*');
private static MathsFunc divide = new MathsFunc("divide", '/');
private static MathsFunc mod = new MathsFunc("mod", '%');
private char op;
public MathsFunc(string name, char op)
: base(name)
{
this.op = op;
}
public static MathsFunc Minus
{
get
{
return minus;
}
}
public static MathsFunc Multiply
{
get
{
return multiply;
}
}
public static MathsFunc Divide
{
get
{
return divide;
}
}
public static MathsFunc Mod
{
get
{
return mod;
}
}
public override object Invoke(object[] args)
{
if (args.Length == 1 && this == Minus)
{
return this.UnaryMinus(args[0]);
}
if (args.Length != 2)
{
throw new ArgumentException("Invalid number of args");
}
object result = this.Calculate(args[0], args[1]);
ReleaseAssert.IsTrue(result != null, "Incompatible type for arithemetic operation: {0}, {1}", args[0], args[1]);
return result;
}
internal object TryInvoke(object[] args)
{
if (args.Length == 0)
{
return null;
}
Type type = args[0].GetType();
if (type != typeof(int) && type != typeof(long) && type != typeof(ulong) && type != typeof(double))
{
return null;
}
return this.Invoke(args);
}
private object UnaryMinus(object arg)
{
Type type = arg.GetType();
if (type == typeof(int))
{
int value = (int)arg;
return -value;
}
if (type == typeof(long))
{
long value = (long)arg;
return -value;
}
if (type == typeof(ulong))
{
ulong value = (ulong)arg;
return -((long)value);
}
if (type == typeof(double))
{
double value = (double)arg;
return -((double)value);
}
if (type == typeof(TimeSpan))
{
TimeSpan value = (TimeSpan)arg;
return -((TimeSpan)value);
}
throw new GuanException("Invalid type for unary minus function: ", type);
}
private object Calculate(object arg1, object arg2)
{
if (arg1 == null || arg2 == null)
{
return null;
}
Type type = arg1.GetType();
if (type != arg2.GetType())
{
return null;
}
if (type == typeof(long))
{
long v1 = (long)arg1;
long v2 = (long)arg2;
switch (this.op)
{
case '+':
return v1 + v2;
case '-':
return v1 - v2;
case '*':
return v1 * v2;
case '/':
return v1 / v2;
case '%':
return v1 % v2;
}
}
else if (type == typeof(ulong))
{
ulong v1 = (ulong)arg1;
ulong v2 = (ulong)arg2;
switch (this.op)
{
case '+':
return v1 + v2;
case '-':
return v1 - v2;
case '*':
return v1 * v2;
case '/':
return v1 / v2;
case '%':
return v1 % v2;
}
}
else if (type == typeof(int))
{
int v1 = (int)arg1;
int v2 = (int)arg2;
switch (this.op)
{
case '+':
return v1 + v2;
case '-':
return v1 - v2;
case '*':
return v1 * v2;
case '/':
return v1 / v2;
case '%':
return v1 % v2;
}
}
else if (type == typeof(double))
{
double v1 = (double)arg1;
double v2 = (double)arg2;
switch (this.op)
{
case '+':
return v1 + v2;
case '-':
return v1 - v2;
case '*':
return v1 * v2;
case '/':
return v1 / v2;
}
}
else if (type == typeof(TimeSpan))
{
TimeSpan v1 = (TimeSpan)arg1;
TimeSpan v2 = (TimeSpan)arg2;
switch (this.op)
{
case '+':
return v1 + v2;
case '-':
return v1 - v2;
}
}
else if (type == typeof(DateTime))
{
DateTime v1 = (DateTime)arg1;
DateTime v2 = (DateTime)arg2;
switch (this.op)
{
case '-':
return v1 - v2;
}
}
return null;
}
}
}

403
guan/Guan/Logic/Module.cs Normal file
Просмотреть файл

@ -0,0 +1,403 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
/// <summary>
/// A module is a collection of predicate types.
/// </summary>
public class Module
{
private static readonly Module SystemModule = CreateSystemModule();
private string name;
private bool dynamic;
private Dictionary<string, PredicateType> types;
internal Module(string name)
{
this.name = name;
this.types = new Dictionary<string, PredicateType>();
this.dynamic = true;
}
private Module(string name, Dictionary<string, PredicateType> types)
{
this.name = name;
this.types = types;
this.dynamic = false;
}
public string Name
{
get
{
return this.name;
}
}
public static async Task<Module> ParseAsync(string path, IFunctorProvider provider, List<string> publicTypes = null)
{
List<string> ruleExpressions = new List<string>();
string current = null;
using (StreamReader reader = new StreamReader(path))
{
string line;
while ((line = await reader.ReadLineAsync()) != null)
{
line = line.Trim();
if (line.Length > 0 && !line.StartsWith("#"))
{
if (current != null)
{
line = current + line;
}
if (line.EndsWith("\\"))
{
line = line.TrimEnd('\\');
current = line;
}
else
{
ruleExpressions.Add(line);
current = null;
}
}
}
ReleaseAssert.IsTrue(current == null, "Last line not completed for {0}", path);
}
return Parse(path, ruleExpressions, provider, publicTypes);
}
public static Module Parse(string name, List<string> ruleExpressions, IFunctorProvider provider, List<string> publicTypes = null)
{
List<Rule> rules = new List<Rule>(ruleExpressions.Count);
foreach (string ruleExpression in ruleExpressions)
{
Rule rule;
try
{
rule = Rule.Parse(ruleExpression);
rules.Add(rule);
}
catch (GuanException e)
{
throw new GuanException(e, "Fail to parse rule {0} in module {1}", ruleExpression, name);
}
}
return Parse(name, rules, provider, publicTypes);
}
/// <summary>
/// Add a dynamic predicate (assert).
/// </summary>
/// <param name="term">The predicate.</param>
/// <param name="append">Whether to add the predicate in append mode.</param>
public void Add(CompoundTerm term, bool append)
{
if (!this.dynamic)
{
throw new GuanException("Not a dynamic module");
}
lock (this.types)
{
PredicateType type;
if (!this.types.TryGetValue(term.Functor.Name, out type))
{
type = new PredicateType(term.Functor.Name, true);
this.types.Add(term.Functor.Name, type);
}
CompoundTerm head = new CompoundTerm(type, VariableBinding.Ground, term.Arguments);
Rule rule = new Rule(term.ToString(), head, new List<CompoundTerm>(), VariableTable.Empty);
type.AddRule(this, rule, append);
}
}
public void Add(PredicateType predicateType)
{
lock (this.types)
{
this.types[predicateType.Name] = predicateType;
}
}
public IEnumerable<PredicateType> GetPublicTypes()
{
List<PredicateType> result = new List<PredicateType>();
foreach (KeyValuePair<string, PredicateType> type in this.types)
{
if (type.Value.IsPublic)
{
result.Add(type.Value);
}
}
return result;
}
public PredicateType GetPredicateType(string name)
{
PredicateType result;
if (this.types.TryGetValue(name, out result) && result.IsPublic)
{
return result;
}
return null;
}
public override string ToString()
{
return this.name;
}
internal static Module Parse(string name, List<Rule> rules, IFunctorProvider provider, List<string> publicTypes)
{
Dictionary<string, PredicateType> types = new Dictionary<string, PredicateType>();
Module result = new Module(name, types);
for (int i = 0; i < rules.Count;)
{
string typeName = rules[i].Head.Functor.Name;
if (typeName != "desc")
{
if (!types.TryGetValue(typeName, out PredicateType type))
{
if (provider != null)
{
type = provider.FindFunctor(typeName, result) as PredicateType;
}
if (type == null)
{
bool isPublic = (publicTypes != null ? publicTypes.Contains(typeName) : !typeName.StartsWith("_"));
type = new PredicateType(typeName, isPublic);
}
types.Add(typeName, type);
}
rules[i].Head.Functor = type;
UpdateFunctor(rules[i].Head, types, provider, result);
i++;
}
else
{
ProcessDesc(rules[i], types);
rules.RemoveAt(i);
}
}
for (int i = 0; i < rules.Count; i++)
{
bool remove = false;
foreach (CompoundTerm goal in rules[i].Goals)
{
string typeName = goal.Functor.Name;
PredicateType type;
if (!types.TryGetValue(typeName, out type))
{
type = GetGoalPredicateType(typeName, provider, result);
if (type == FailPredicateType.NotApplicable)
{
remove = true;
}
else if (type == null)
{
throw new GuanException("Predicate type {0} not defined in rule {1}", typeName, rules[i]);
}
}
goal.Functor = type;
UpdateFunctor(goal, types, provider, result);
}
if (!remove)
{
try
{
rules[i].PostProcessing();
}
catch (GuanException e)
{
throw new GuanException(e, "Fail to process rule {0}", rules[i]);
}
rules[i].Head.PredicateType.AddRule(result, rules[i], true);
}
}
return result;
}
private static void ProcessDesc(Rule rule, Dictionary<string, PredicateType> types)
{
if (rule.Goals.Count > 0 || rule.Head.Arguments.Count < 2 || !rule.Head.IsGround() || !(rule.Head.Arguments[0].Value is Constant))
{
throw new GuanException("Invalid desc predicate: {0}", rule);
}
rule.ProcessMetaDataHead();
Constant constant = (Constant)rule.Head.Arguments[0].Value;
PredicateType type;
string typeName = constant.GetStringValue();
if (typeName == null || !types.TryGetValue(typeName, out type))
{
if (rule.Head.Arguments.Count == 2)
{
Constant arg = rule.Head.Arguments[1].Value as Constant;
if (arg != null && arg.GetStringValue() == "dynamic")
{
types.Add(typeName, new DynamicPredicateType(typeName));
return;
}
}
throw new GuanException("Type {0} in desc predicate not defined", constant);
}
for (int i = 1; i < rule.Head.Arguments.Count; i++)
{
string name = rule.Head.Arguments[i].Name;
if (name == i.ToString())
{
name = null;
}
try
{
type.ProcessMetaData(name, rule.Head.Arguments[i].Value);
}
catch (GuanException e)
{
throw new GuanException(e, "Meta-data {0} {1} can't be applied to {2}", name, rule.Head.Arguments[i].Value, type);
}
}
}
private static Functor GetFunctor(string name, IFunctorProvider provider, Module from)
{
Functor result;
if (provider != null)
{
result = provider.FindFunctor(name, from);
if (result != null)
{
return result;
}
}
result = PredicateType.GetBuiltInType(name);
if (result != null)
{
return result;
}
if (name == Functor.ClassObject.Name)
{
return Functor.ClassObject;
}
if (SystemModule != null)
{
result = SystemModule.GetPredicateType(name);
if (result != null)
{
return result;
}
}
GuanFunc func = GuanExpression.GetGuanFunc(name);
if (func != null)
{
return new EvaluatedFunctor(func);
}
return null;
}
private static PredicateType GetGoalPredicateType(string typeName, IFunctorProvider provider, Module from)
{
Functor functor = GetFunctor(typeName, provider, from);
EvaluatedFunctor evaluatedFunctor = functor as EvaluatedFunctor;
if (evaluatedFunctor != null)
{
return evaluatedFunctor.ConstraintType;
}
else
{
return functor as PredicateType;
}
}
private static void UpdateFunctor(CompoundTerm term, Dictionary<string, PredicateType> types, IFunctorProvider provider, Module from)
{
foreach (TermArgument arg in term.Arguments)
{
CompoundTerm compound = arg.Value as CompoundTerm;
if (compound != null)
{
if (!(compound.Functor is PredicateType))
{
PredicateType type;
if (types.TryGetValue(compound.Functor.Name, out type))
{
compound.Functor = type;
}
else
{
Functor functor = GetFunctor(compound.Functor.Name, provider, from);
if (functor != null)
{
compound.Functor = functor;
}
}
}
UpdateFunctor(compound, types, provider, from);
}
}
}
private static Module CreateSystemModule()
{
List<string> rules = new List<string>()
{
"repeat",
"repeat :- repeat",
"append([], ?Ys, ?Ys)",
"append([?X|?Xs], ?Ys, [?X|?Zs]) :- append(?Xs, ?Ys, ?Zs)",
"member(?X, [?X|_]",
"member(?X, [_|?Xs]) :- member(?X, ?Xs)",
"length([], 0)",
"length([_|?Xs], ?Y) :- length(?Xs, ?Z), ?Y is ?Z + 1",
"reverse(?Xs, ?Ys) :- _reverse(?Xs, [], ?Ys)",
"_reverse([], ?Ys, ?Ys",
"_reverse([?X|?Xs], ?A, ?Ys) :- _reverse(?Xs, [?X|?A], ?Ys)",
"AddToList(?X, [], [?X])",
"AddToList(?X, [?X|?Ys], [?X|?Ys]) :- !",
"AddToList(?X, [?Y|?Ys], [?X,?Y|?Ys]) :- ?X < ?Y, !",
"AddToList(?X, [?Y|?Ys], [?Y|?Zs]) :- AddToList(?X, ?Ys, ?Zs)",
"AddToMap(kv(?K,?V), [], [kv(?K,?V)])",
"AddToMap(kv(?K,?V), [kv(?K,_)|?Ys], [kv(?K,?V)|?Ys]) :- !",
"AddToMap(kv(?K,?V), [kv(?K1,V1)|?Ys], [kv(?K,?V),kv(?K1,V1)|?Ys]) :- ?K < ?K1, !",
"AddToMap(kv(?K,?V), [kv(?K1,V1)|?Ys], [kv(?K1,V1)|?Zs]) :- AddToMap(kv(?K,?V), ?Ys, ?Zs)"
};
return Module.Parse("System", rules, null);
}
}
}

Просмотреть файл

@ -0,0 +1,136 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System.Collections.Generic;
/// <summary>
/// Class maintaining predicate types from multiple modules to provide
/// a lookup facility.
/// </summary>
public class ModuleProvider : IFunctorProvider
{
private Dictionary<string, PredicateFunctorList> types;
private List<IFunctorProvider> providers;
public ModuleProvider()
{
this.types = new Dictionary<string, PredicateFunctorList>();
this.providers = new List<IFunctorProvider>();
}
public void Add(Module module)
{
foreach (PredicateType type in module.GetPublicTypes())
{
this.AddType(type);
}
}
public void Add(IFunctorProvider provider)
{
if (!this.providers.Contains(provider))
{
this.providers.Add(provider);
}
}
public Functor FindFunctor(string name, Module from)
{
PredicateFunctorList entry;
if (!this.types.TryGetValue(name, out entry))
{
foreach (IFunctorProvider provider in this.providers)
{
Functor result = provider.FindFunctor(name, from);
if (result != null)
{
return result;
}
}
return null;
}
return entry.Find(from);
}
private void AddType(PredicateType type)
{
PredicateFunctorList entry;
if (!this.types.TryGetValue(type.Name, out entry))
{
entry = new PredicateFunctorList();
this.types.Add(type.Name, entry);
}
if (!entry.Contains(type))
{
entry.Add(type);
}
}
/// <summary>
/// Predicate types with the same name but different modules.
/// </summary>
private class PredicateFunctorList : List<PredicateType>
{
public PredicateType Find(Module from)
{
if (this.Count == 1)
{
return this[0];
}
List<PredicateType> result = new List<PredicateType>();
int score = -1;
foreach (PredicateType type in this)
{
int newScore = this.GetScore(type, from);
if (newScore > score)
{
result.Clear();
result.Add(type);
score = newScore;
}
else if (newScore == score)
{
result.Add(type);
}
}
if (result.Count == 0)
{
return null;
}
if (result.Count > 1)
{
throw new GuanException(
"Conflicting types {0} of module {1} and {2} referenced in {3}",
result[0].Name,
result[0].Module,
result[1].Module,
from);
}
return result[0];
}
private int GetScore(PredicateType type, Module from)
{
string[] parts1 = type.Module.Name.Split('.');
string[] parts2 = from.Name.Split('.');
int i;
for (i = 0; i < parts1.Length && i < parts2.Length && parts1[i] == parts2[i]; i++)
{
}
return i;
}
}
}
}

Просмотреть файл

@ -0,0 +1,24 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
/// <summary>
/// Not function.
/// </summary>
public class NotFunc : UnaryFunc
{
public static readonly NotFunc Singleton = new NotFunc();
private NotFunc()
: base("not")
{
}
public override object UnaryInvoke(object arg)
{
return (arg == null || !(bool)arg);
}
}
}

Просмотреть файл

@ -0,0 +1,69 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System.Threading.Tasks;
/// <summary>
/// Predicate type for "not".
/// </summary>
internal class NotPredicateType : PredicateType
{
public static readonly NotPredicateType Singleton = new NotPredicateType();
private static readonly EvaluatedFunctor NotConstraint = new EvaluatedFunctor(NotFunc.Singleton);
private NotPredicateType()
: base("not", true, 1, 1)
{
}
public override PredicateResolver CreateResolver(CompoundTerm input, Constraint constraint, QueryContext context)
{
return new Resolver(input, constraint, context);
}
public override void AdjustTerm(CompoundTerm term, Rule rule)
{
base.AdjustTerm(term, rule);
if (!(term.Arguments[0].Value is CompoundTerm))
{
throw new GuanException("Invalid use of not predicate");
}
CompoundTerm goal = (CompoundTerm)term.Arguments[0].Value;
if (goal.Functor is EvaluatedFunctor)
{
term.Functor = NotConstraint.ConstraintType;
}
else if (!(goal.Functor is PredicateType))
{
throw new GuanException("Predicate type {0} in {1} is not defined", goal.Functor.Name, term);
}
}
private class Resolver : PredicateResolver
{
public Resolver(CompoundTerm input, Constraint constraint, QueryContext context)
: base(input, constraint, context, 1)
{
}
public override async Task<UnificationResult> OnGetNextAsync()
{
CompoundTerm goal = (CompoundTerm)this.Input.Arguments[0].Value;
PredicateResolver resolver = goal.PredicateType.CreateResolver(goal, this.Constraint, this.Context);
UnificationResult result = await resolver.GetNextAsync();
if (result == null)
{
return UnificationResult.Empty;
}
return null;
}
}
}
}

Просмотреть файл

@ -0,0 +1,96 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System;
using System.Globalization;
using System.Reflection;
using Newtonsoft.Json;
/// <summary>
/// Adapater to expose object properties as a compund term using reflection.
/// </summary>
internal class ObjectCompundTerm : CompoundTerm
{
private static readonly Type[] EmptyTypes = new Type[0];
private object value;
private Type type;
public ObjectCompundTerm(object value, bool ignoreType = false)
: base(new Functor(ignoreType ? typeof(ObjectCompundTerm) : value.GetType()))
{
this.value = value;
this.type = value.GetType();
}
public Type ObjectType
{
get
{
return this.type;
}
}
public object Value
{
get
{
return this.value;
}
}
public static ObjectCompundTerm Create(object value)
{
if (value == null || value.GetType().IsPrimitive)
{
return null;
}
return new ObjectCompundTerm(value);
}
public override Term GetExtendedArgument(string name)
{
object result;
IPropertyContext context = this.value as IPropertyContext;
if (context != null)
{
result = context[name];
if (result == null)
{
return null;
}
}
else
{
if (this.type.GetProperty(name) == null)
{
return null;
}
result = this.type.InvokeMember(name, BindingFlags.GetProperty, null, this.value, null, CultureInfo.InvariantCulture);
}
GuanObject guanObject = result as GuanObject;
if (guanObject != null && guanObject.GetValue() != null)
{
result = guanObject.GetValue();
}
return Term.FromObject(result);
}
public override string ToString()
{
if (this.value == null)
{
return "null";
}
return JsonConvert.SerializeObject(this.value, Formatting.Indented);
}
}
}

67
guan/Guan/Logic/OrFunc.cs Normal file
Просмотреть файл

@ -0,0 +1,67 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System.Collections.Generic;
/// <summary>
/// Or function.
/// </summary>
public class OrFunc : StandaloneFunc
{
public static readonly OrFunc Singleton = new OrFunc();
private OrFunc()
: base("or")
{
}
public override object Invoke(object[] args)
{
foreach (object arg in args)
{
if ((arg != null) && (bool)arg)
{
return true;
}
}
return false;
}
internal override object Invoke(IPropertyContext context, List<GuanExpression> args)
{
foreach (GuanExpression exp in args)
{
bool arg = (bool)exp.Evaluate(context);
if (arg)
{
return true;
}
}
return false;
}
internal override GuanFunc Bind(List<GuanExpression> args)
{
for (int i = args.Count - 1; i >= 0; i--)
{
bool arg;
if (args[i].GetLiteral(out arg))
{
if (arg)
{
return Literal.True;
}
args.RemoveAt(i);
}
}
return this.Collapse(args);
}
}
}

Просмотреть файл

@ -0,0 +1,22 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
/// <summary>
/// Variable output from a goal to the rule.
/// </summary>
internal class OutputVariable : LinkedVariable
{
public OutputVariable(VariableBinding binding, Variable original, string name)
: base(binding, original, name)
{
}
public OutputVariable(OutputVariable other, VariableBinding binding)
: base(other, binding)
{
}
}
}

Просмотреть файл

@ -0,0 +1,100 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System.Collections.Generic;
using System.Threading.Tasks;
/// <summary>
/// Resolver that executes rules for the same predicate type in parallel.
/// </summary>
internal class ParallelRulePredicateResolver : PredicateResolver
{
private Module module;
private List<Rule> rules;
private List<RulePredicateResolver> resolvers;
private List<Task<UnificationResult>> parallelTasks;
public ParallelRulePredicateResolver(Module module, List<Rule> rules, CompoundTerm input, Constraint constraint, QueryContext context)
: base(input, constraint, context)
{
this.module = module;
this.rules = rules;
}
public override async Task<UnificationResult> OnGetNextAsync()
{
this.Context.IsStable = false;
// Initialize resolvers, contexts and tasks.
if (this.resolvers == null)
{
this.resolvers = new List<RulePredicateResolver>(this.rules.Count);
this.parallelTasks = new List<Task<UnificationResult>>(this.rules.Count);
foreach (Rule rule in this.rules)
{
List<Rule> rules = new List<Rule>()
{
rule
};
RulePredicateResolver resolver = new RulePredicateResolver(this.module, rules, false, this.Input, this.Constraint, this.Context.CreateChild());
this.resolvers.Add(resolver);
this.parallelTasks.Add(resolver.GetNextAsync());
}
}
for (int i = 0; i < this.resolvers.Count; i++)
{
if (this.parallelTasks[i] != null)
{
this.resolvers[i].Context.IsSuspended = false;
}
else
{
this.parallelTasks[i] = this.resolvers[i].GetNextAsync();
}
}
this.Context.IsStable = true;
UnificationResult result = null;
while (this.parallelTasks.Count > 0 && result == null)
{
Task<UnificationResult> task = await Task.WhenAny(this.parallelTasks);
result = await task;
int index = this.parallelTasks.IndexOf(task);
if (result != null)
{
this.parallelTasks[index] = null;
// Suspend all unfinished ones
for (int i = 0; i < this.resolvers.Count; i++)
{
if (i != index)
{
this.resolvers[i].Context.IsSuspended = true;
}
}
}
else
{
this.parallelTasks.RemoveAt(index);
this.resolvers.RemoveAt(index);
}
}
return result;
}
protected override void OnCancel()
{
foreach (RulePredicateResolver resolver in this.resolvers)
{
resolver.Context.IsCancelled = true;
}
}
}
}

Просмотреть файл

@ -0,0 +1,345 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
/// <summary>
/// Base class for resolving a goal.
/// </summary>
public abstract class PredicateResolver
{
private CompoundTerm input;
private Constraint constraint;
private QueryContext context;
private int max;
private int iteration;
private bool completed;
protected PredicateResolver(CompoundTerm input, Constraint constraint, QueryContext context, int max = int.MaxValue)
{
this.input = input;
this.constraint = constraint;
this.context = context;
this.max = max;
this.iteration = 0;
this.completed = false;
if (input != null && input.Option.Max != 0)
{
this.max = input.Option.Max;
}
}
public CompoundTerm Input
{
get
{
return this.input;
}
internal set
{
this.input = value;
}
}
public QueryContext Context
{
get
{
return this.context;
}
}
public int Iteration
{
get
{
return this.iteration;
}
internal set
{
this.iteration = value;
}
}
public bool Completed
{
get
{
return this.completed || this.context.IsCancelled;
}
}
internal Constraint Constraint
{
get
{
return this.constraint;
}
set
{
this.constraint = value;
}
}
internal int Max
{
get
{
return this.max;
}
set
{
this.max = value;
}
}
public async Task<UnificationResult> GetNextAsync()
{
if (this.completed)
{
return null;
}
UnificationResult result;
try
{
result = await this.OnGetNextAsync();
}
catch (Exception e)
{
if (!this.Input.Option.CatchException)
{
throw;
}
EventLogWriter.WriteError("Fail\t[{0},{1}]: {2} with Exception\n{3}", this.Input.Binding.Level, this.iteration, this.Input, e);
result = null;
}
if (result != null)
{
this.iteration++;
if (this.iteration >= this.max)
{
this.completed = true;
}
}
else
{
this.completed = true;
}
return result;
}
public Term GetInputArgument(string name, bool includeContext = false)
{
Term arg = this.Input.GetArgument(name);
if (arg == null && includeContext)
{
arg = this.context[name] as Term;
}
return arg?.GetEffectiveTerm();
}
public Term GetInputArgument(int index, bool optional = false)
{
if (index < 0 || index >= this.Input.Arguments.Count)
{
if (optional)
{
return null;
}
throw new ArgumentException($"{this.input} does not have argument with index {index}");
}
TermArgument arg = this.Input.Arguments[index];
return arg.Value.GetEffectiveTerm();
}
public object GetInputArgumentObject(string name, bool includeContext = false)
{
Term input = this.GetInputArgument(name);
if (input == null && includeContext)
{
object result = this.context[name];
input = result as Term;
if (input == null)
{
return result;
}
}
return input?.GetObjectValue();
}
public object GetInputArgumentObject(int index, bool includeContext = false)
{
Term input = this.GetInputArgument(index, includeContext);
return input?.GetObjectValue();
}
public string GetInputArgumentString(string name, bool includeContext = false)
{
return (string)this.GetInputArgumentObject(name, includeContext);
}
public string GetInputArgumentString(int index, bool includeContext = false)
{
return (string)this.GetInputArgumentObject(index, includeContext);
}
public bool GetInputArgumentFlag(string name, bool includeContext = false)
{
object value = this.GetInputArgumentObject(name);
return Utility.Convert<bool>(value);
}
public T GetInputArgument<T>(string name)
{
object value = this.GetInputArgumentObject(name);
return Utility.Convert<T>(value);
}
public object GetBoundValue(string name, bool remove)
{
List<object> result = this.GetBoundValues(name, remove);
if (result == null)
{
return null;
}
ReleaseAssert.IsTrue(result.Count == 1);
return result[0];
}
public List<object> GetBoundValues(string name, bool remove)
{
Term term = this.GetInputArgument(name);
if (term == null)
{
return null;
}
if (term.IsGround())
{
List<object> result = new List<object>(1)
{
term.GetObjectValue()
};
return result;
}
Variable variable = term as Variable;
if (variable == null)
{
return null;
}
return this.constraint.GetValues(variable, remove);
}
public object GetLowerBound(string name, bool remove, out bool isInclusive)
{
isInclusive = false;
Variable variable = this.GetVariable(name);
if (variable == null)
{
return null;
}
return this.constraint.GetLowerBound(variable, remove, out isInclusive);
}
public object GetUpperBound(string name, bool remove, out bool isInclusive)
{
isInclusive = false;
Variable variable = this.GetVariable(name);
if (variable == null)
{
return null;
}
return this.constraint.GetUpperBound(variable, remove, out isInclusive);
}
public void Cancel()
{
if (!this.completed)
{
this.OnCancel();
this.completed = true;
}
}
public virtual void OnBacktrack()
{
}
public abstract Task<UnificationResult> OnGetNextAsync();
internal void WriteTrace(string type)
{
if (this.Input != null && !(this.Input.Functor is ConstraintPredicateType))
{
bool enabled;
bool highlight;
if (this.iteration == 0)
{
(enabled, highlight) = this.Input.Option.IsTraceEnabled(type);
}
else
{
enabled = highlight = false;
}
if (enabled || this.Context.EnableTrace)
{
if (highlight)
{
EventLogWriter.WriteError("{0}\t[{1},{2}]: {3}", type, this.Input.Binding.Level, this.iteration, this.Input);
}
else
{
EventLogWriter.WriteInfo("{0}\t[{1},{2}]: {3}", type, this.Input.Binding.Level, this.iteration, this.Input);
}
}
}
}
protected virtual void OnCancel()
{
}
protected void Complete()
{
this.completed = true;
}
private Variable GetVariable(string name)
{
Term term = this.GetInputArgument(name);
if (term == null)
{
return null;
}
return term as Variable;
}
}
}

Просмотреть файл

@ -0,0 +1,226 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System.Collections.Generic;
/// <summary>
/// Functor for a predicate goal.
/// </summary>
public class PredicateType : Functor
{
private static object builtInTypesLock = new object();
private static Dictionary<string, PredicateType> builtInTypes = null;
private Module module;
private bool isPublic;
private int minPositionalArgument;
private int maxPositionalArgument;
private List<Rule> rules;
private bool parallel;
private bool singleActivation;
private TermOption option;
public PredicateType(string name, bool isPublic = false, int minPositionalArgument = 0, int maxPositionalArgument = int.MaxValue)
: base(name)
{
this.isPublic = isPublic;
this.minPositionalArgument = minPositionalArgument;
this.maxPositionalArgument = maxPositionalArgument;
this.parallel = false;
this.singleActivation = false;
this.option = TermOption.Default;
}
public bool IsPublic
{
get
{
return this.isPublic;
}
}
public Module Module
{
get
{
return this.module;
}
}
internal List<Rule> Rules
{
get
{
return this.rules;
}
}
internal int MinPositionalArgument
{
get
{
return this.minPositionalArgument;
}
}
internal int MaxPositionalArgument
{
get
{
return this.maxPositionalArgument;
}
}
internal TermOption Option
{
get
{
return this.option;
}
}
public static PredicateType GetBuiltInType(string name)
{
if (builtInTypes == null)
{
lock (builtInTypesLock)
{
if (builtInTypes == null)
{
builtInTypes = CreateBuiltInTypes();
}
}
}
PredicateType result;
_ = builtInTypes.TryGetValue(name, out result);
return result;
}
public virtual PredicateResolver CreateResolver(CompoundTerm input, Constraint constraint, QueryContext context)
{
if (this.rules == null)
{
return null;
}
if (this.parallel)
{
return new ParallelRulePredicateResolver(this.module, this.rules, input, constraint, context);
}
return new RulePredicateResolver(this.module, this.rules, this.singleActivation, input, constraint, context);
}
public virtual void AdjustTerm(CompoundTerm term, Rule rule)
{
}
internal void LoadRules(List<string> rules, IFunctorProvider provider)
{
if (rules.Count == 0)
{
return;
}
List<string> publicTypes = new List<string>()
{
this.Name
};
Module module = Module.Parse(this.Name, rules, provider, publicTypes);
ReleaseAssert.IsTrue(this.module == null || this.module == module);
this.module = module;
}
internal void AddRule(Module module, Rule rule, bool append)
{
if (this.module != null && this.module != module)
{
throw new GuanException("Rules for {0} set from both modules {1} and {2}", this, this.module, module);
}
this.module = module;
if (this.rules == null)
{
this.rules = new List<Rule>();
}
if (append)
{
this.rules.Add(rule);
}
else
{
this.rules.Insert(0, rule);
}
}
internal void ProcessMetaData(string name, Term term)
{
if (name == null && term.GetStringValue() == "parallel")
{
this.parallel = true;
}
else if (name == null && term.GetStringValue() == "singleActivation")
{
this.singleActivation = true;
}
else
{
CompoundTerm compound = term as CompoundTerm;
if (compound != null && compound.Functor.Name == "_option")
{
this.option = new TermOption(compound);
}
}
}
private static Dictionary<string, PredicateType> CreateBuiltInTypes()
{
Dictionary<string, PredicateType> result = new Dictionary<string, PredicateType>();
AddType(result, CutPredicateType.Singleton);
AddType(result, ForwardCutPredicateType.Singleton);
AddType(result, FailPredicateType.Singleton);
AddType(result, UnifyPredicateType.Regular);
AddType(result, NotPredicateType.Singleton);
AddType(result, TermPropertyPredicateType.Var);
AddType(result, TermPropertyPredicateType.NonVar);
AddType(result, TermPropertyPredicateType.Atom);
AddType(result, TermPropertyPredicateType.Compound);
AddType(result, TermPropertyPredicateType.Ground);
AddType(result, TracePredicateType.Enable);
AddType(result, TracePredicateType.Disable);
AddType(result, GetValPredicateType.Singleton);
AddType(result, SetValPredicateType.Backtrack);
AddType(result, SetValPredicateType.NoBacktrack);
AddType(result, EnumerablePredicateType.Singleton);
AddType(result, AssertPredicateType.Assert);
AddType(result, AssertPredicateType.Asserta);
AddType(result, AssertPredicateType.Assertz);
AddType(result, UpdateObjectPredicateType.Singleton);
AddType(result, IsPredicateType.Singleton);
AddType(result, WriteLinePredicateType.WriteInfo);
AddType(result, WriteLinePredicateType.WriteWarning);
AddType(result, WriteLinePredicateType.WriteError);
AddType(result, LogPredicateType.LogInfo);
AddType(result, LogPredicateType.LogWarning);
AddType(result, LogPredicateType.LogError);
AddType(result, SleepPredicateType.Singleton);
AddType(result, RegexPredicateType.Singleton);
AddType(result, QueryToListPredicateType.Singleton);
return result;
}
private static void AddType(Dictionary<string, PredicateType> result, PredicateType type)
{
result.Add(type.Name, type);
}
}
}

Просмотреть файл

@ -0,0 +1,112 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text.RegularExpressions;
public class PropertyMatchFunc : GuanFunc
{
public static readonly PropertyMatchFunc Equal = new PropertyMatchFunc("propequal", true, false);
public static readonly PropertyMatchFunc OptionalEqual = new PropertyMatchFunc("propeq", true, false, true);
public static readonly PropertyMatchFunc Match = new PropertyMatchFunc("propmatch", false, false);
public static readonly PropertyMatchFunc NotEqual = new PropertyMatchFunc("propnotequal", true, true);
public static readonly PropertyMatchFunc NotMatch = new PropertyMatchFunc("propnotmatch", false, true);
private bool exactMatch;
private bool negative;
private bool optional;
private PropertyMatchFunc(string name, bool exactMatch, bool negative, bool optional = false)
: base(name)
{
this.exactMatch = exactMatch;
this.negative = negative;
this.optional = optional;
}
public override object Invoke(IPropertyContext context, object[] args)
{
int argCount = args.Length;
if (argCount == 0)
{
throw new ArgumentException("Invalid number of arguments for Match()");
}
string propertyName = (string)args[0];
string pattern = (argCount > 1 && args[1] != null ? args[1].ToString() : string.Empty);
if (pattern == null && this.optional)
{
return true;
}
object propertyValue = GetFunc.Invoke(context, propertyName);
string property = (propertyValue != null ? propertyValue.ToString() : string.Empty);
bool result;
if (this.exactMatch)
{
result = (property == pattern);
}
else
{
result = new Regex(pattern).IsMatch(property);
}
return (result != this.negative);
}
internal override GuanFunc Bind(List<GuanExpression> args)
{
string pattern;
string propertyName;
int argCount = args.Count;
if (!this.exactMatch && args[argCount - 1].GetLiteral(out pattern))
{
if (!args[0].GetLiteral(out propertyName))
{
return this;
}
args.Clear();
return new BoundPropertyMatch(this.ToString(), propertyName, pattern, this.negative);
}
return this;
}
private class BoundPropertyMatch : GuanFunc
{
private string propertyName;
private Regex pattern;
private bool negative;
public BoundPropertyMatch(string name, string propertyName, string pattern, bool negative)
: base(name)
{
this.propertyName = propertyName;
this.pattern = new Regex(pattern, RegexOptions.Compiled);
this.negative = negative;
}
public override object Invoke(IPropertyContext context, object[] args)
{
object propertyValue = context[this.propertyName];
string property = (propertyValue != null ? propertyValue.ToString() : string.Empty);
return (this.pattern.IsMatch(property) != this.negative);
}
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "{0}({1},{2})", this.Name, this.propertyName, this.pattern);
}
}
}
}

189
guan/Guan/Logic/Query.cs Normal file
Просмотреть файл

@ -0,0 +1,189 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
/// <summary>
/// Class for a query on some pre-defined predicate type.
/// </summary>
public class Query
{
private const string QueryTypeName = "__query";
private CompoundTerm input;
private PredicateResolver resolver;
private Query(QueryPredicateType queryType, QueryContext queryContext)
{
this.input = queryType.Input;
this.input.Binding.MoveNext();
this.resolver = queryType.CreateResolver(this.input, Constraint.Empty, queryContext);
}
public static Query Create(string text, QueryContext queryContext, IFunctorProvider provider)
{
if (text.Contains(":-"))
{
throw new ArgumentException("Query can't contain head: " + text);
}
QueryPredicateType queryType = new QueryPredicateType();
List<string> types = new List<string>();
types.Add(QueryTypeName);
List<string> rules = new List<string>();
rules.Add(QueryTypeName + " :- " + text);
Module module = Module.Parse("query", rules, new Provider(provider, queryType), types);
return new Query(queryType, queryContext);
}
public static Query Create(List<CompoundTerm> terms, QueryContext queryContext, IFunctorProvider provider)
{
if (terms == null || terms.Count == 0)
{
throw new ArgumentException("terms");
}
CompoundTerm body = terms[terms.Count - 1];
for (int i = terms.Count - 2; i >= 0; i--)
{
CompoundTerm current = body;
body = new CompoundTerm(",");
body.AddArgument(terms[i], "0");
body.AddArgument(current, "1");
}
QueryPredicateType queryType = new QueryPredicateType();
CompoundTerm ruleTerm = new CompoundTerm(":-");
ruleTerm.AddArgument(new CompoundTerm(QueryTypeName), "0");
ruleTerm.AddArgument(body, "1");
List<string> types = new List<string>()
{
QueryTypeName
};
List<Rule> rules = new List<Rule>()
{
Rule.Parse(ruleTerm, ruleTerm.ToString())
};
_ = Module.Parse("query", rules, new Provider(provider, queryType), types);
return new Query(queryType, queryContext);
}
public bool SetLocalVariables(string name, Term value)
{
return this.input.Binding.SetLocalVariableValue(name, value);
}
public async Task<List<Term>> GetResultsAsync(int maxCount)
{
List<Term> result = new List<Term>();
while (result.Count < maxCount)
{
Term term = await this.GetNextAsync();
if (term == null)
{
return result;
}
result.Add(term.GetGroundedCopy());
}
return result;
}
public async Task<Term> GetNextAsync()
{
UnificationResult result = await this.GetNextResultAsync();
if (result == null)
{
return null;
}
result.Apply(this.input.Binding);
this.input.Binding.MoveNext();
return this.input;
}
public Task<UnificationResult> GetNextResultAsync()
{
if (this.resolver.Iteration > 0)
{
_ = this.input.Binding.MovePrev();
}
return this.resolver.GetNextAsync();
}
private class QueryPredicateType : PredicateType
{
private CompoundTerm input;
public QueryPredicateType()
: base(QueryTypeName)
{
}
public CompoundTerm Input
{
get
{
return this.input;
}
}
public override void AdjustTerm(CompoundTerm term, Rule rule)
{
ReleaseAssert.IsTrue(term == rule.Head && this.input == null);
foreach (string name in rule.VariableTable)
{
rule.AddArgument(term, "?" + name, name);
}
VariableBinding binding = rule.CreateBinding(0);
this.input = term.DuplicateGoal(binding);
}
public override string ToString()
{
return string.Empty;
}
}
private class Provider : IFunctorProvider
{
private IFunctorProvider provider;
private QueryPredicateType type;
public Provider(IFunctorProvider provider, QueryPredicateType type)
{
this.provider = provider;
this.type = type;
}
public Functor FindFunctor(string name, Module from)
{
if (name == QueryTypeName)
{
return this.type;
}
if (this.provider != null)
{
return this.provider.FindFunctor(name, from);
}
return null;
}
}
}
}

Просмотреть файл

@ -0,0 +1,361 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// Context to provide the following during rule execution:
/// 1. The asserted predicates.
/// 2. Global variables, including some system variables controlling:
/// 2.1: resolve direction
/// 2.2: trace/debug flag
/// 3. A hierarhy of contexts to track parallel execution.
/// </summary>
public class QueryContext : IPropertyContext, IWritablePropertyContext
{
private static long seqGenerator = 0;
private QueryContext parent;
private List<QueryContext> children;
private QueryContext root;
private string orderProperty;
private ResolveOrder order;
private Dictionary<string, object> variables;
private Module asserted;
private bool isLocalStable;
private bool isStable;
private bool isLocalSuspended;
private bool isSuspended;
private bool isCancelled;
private bool enableTrace;
private bool enableDebug;
private List<IWaitingTask> waiting;
private long seq;
public QueryContext()
{
this.seq = Interlocked.Increment(ref seqGenerator);
this.root = this;
this.order = ResolveOrder.None;
this.variables = new Dictionary<string, object>();
this.asserted = new Module("asserted");
this.isStable = this.isLocalStable = false;
this.isSuspended = this.isLocalSuspended = false;
this.isCancelled = false;
this.enableTrace = false;
this.enableDebug = false;
this.waiting = new List<IWaitingTask>();
}
protected QueryContext(QueryContext parent)
{
this.seq = Interlocked.Increment(ref seqGenerator);
this.parent = parent;
this.root = parent.root;
this.orderProperty = parent.orderProperty;
this.asserted = parent.asserted;
this.order = parent.order;
this.variables = parent.variables;
this.isStable = this.isLocalStable = false;
this.isSuspended = this.isLocalSuspended = false;
this.isCancelled = false;
this.enableTrace = parent.enableTrace;
this.enableDebug = parent.enableDebug;
this.waiting = parent.waiting;
lock (this.root)
{
this.SetStable(false);
this.parent.AddChild(this);
}
}
public event EventHandler Suspended;
public event EventHandler Resumed;
public string OrderProperty
{
get
{
return this.orderProperty;
}
}
public ResolveOrder Order
{
get
{
return this.order;
}
}
public bool IsCancelled
{
get
{
return this.isCancelled;
}
set
{
Queue<QueryContext> queue = new Queue<QueryContext>();
queue.Enqueue(this);
while (queue.Count > 0)
{
QueryContext context = queue.Dequeue();
if (!context.IsCancelled)
{
context.isCancelled = true;
context.isSuspended = true;
if (context.children != null)
{
foreach (QueryContext child in context.children)
{
queue.Enqueue(child);
}
}
}
}
}
}
/// <summary>
/// Used to coordinate parallel execution where some tasks should be run
/// together.
/// A context is stable if the context itself and all its children contexts
/// are stable.
/// There is an execution queue at the root level for all tasks that should be
/// executed together and they will only be executed when the root context
/// is stable, which should only be true when no more such tasks will be
/// generated (until some such task is completed and new task is triggered).
/// </summary>
internal bool IsStable
{
get
{
return this.isStable;
}
set
{
lock (this.root)
{
this.SetStable(value);
}
}
}
/// <summary>
/// Whether the context is suspended, which can happen during parallel
/// execution.
/// A context is suspended when either itself or any parent is suspended.
/// </summary>
internal bool IsSuspended
{
get
{
return this.isSuspended;
}
set
{
this.isLocalSuspended = value;
this.UpdateSuspended(this.parent != null ? this.parent.isSuspended : false);
}
}
internal bool EnableTrace
{
get
{
return this.enableTrace;
}
set
{
this.enableTrace = value;
}
}
internal bool EnableDebug
{
get
{
return this.enableDebug || this.enableTrace;
}
set
{
this.enableDebug = value;
}
}
public virtual object this[string name]
{
get
{
if (name == "Order")
{
return this.order;
}
object result;
_ = this.variables.TryGetValue(name, out result);
return result;
}
set
{
if (name == "Order")
{
if (value is ResolveOrder)
{
this.order = (ResolveOrder)value;
}
else
{
this.order = (ResolveOrder)Enum.Parse(typeof(ResolveOrder), (string)value);
}
}
else
{
this.variables[name] = value;
}
}
}
public void SetDirection(string directionProperty, ResolveOrder direction)
{
this.orderProperty = directionProperty;
this.order = direction;
}
public override string ToString()
{
return this.seq.ToString();
}
internal virtual QueryContext CreateChild()
{
return new QueryContext(this);
}
internal void ClearChildren()
{
lock (this.root)
{
if (this.children != null)
{
this.children.Clear();
}
this.isStable = this.isLocalStable;
}
}
internal void Assert(CompoundTerm term, bool append)
{
this.asserted.Add(term, append);
}
internal PredicateType GetAssertedPredicateType(string name)
{
return this.asserted.GetPredicateType(name);
}
internal void AddWaiting(IWaitingTask suspended)
{
this.waiting.Add(suspended);
}
private void AddChild(QueryContext child)
{
if (this.children == null)
{
this.children = new List<QueryContext>();
}
this.children.Add(child);
}
private void SetStable(bool value)
{
this.isLocalStable = value;
QueryContext context = this;
bool updated;
do
{
updated = context.UpdateStable();
context = context.parent;
}
while (context != null && updated);
if (this.root.isStable)
{
this.root.Start();
}
}
private bool UpdateStable()
{
bool result = this.isLocalStable;
for (int i = 0; result && this.children != null && i < this.children.Count; i++)
{
result = this.children[i].isStable;
}
if (result == this.isStable)
{
return false;
}
this.isStable = result;
return true;
}
private void UpdateSuspended(bool value)
{
bool newValue = (value || this.isLocalSuspended);
if (newValue == this.isSuspended)
{
return;
}
this.isSuspended = newValue;
if (this.children != null)
{
foreach (QueryContext child in this.children)
{
child.UpdateSuspended(this.isSuspended);
}
}
if (this.isSuspended)
{
this.Suspended?.Invoke(this, EventArgs.Empty);
}
else
{
this.Resumed?.Invoke(this, EventArgs.Empty);
}
}
private void Start()
{
foreach (IWaitingTask suspended in this.waiting)
{
_ = Task.Run(() => { suspended.Start(); });
}
this.waiting.Clear();
}
}
}

Просмотреть файл

@ -0,0 +1,76 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
internal class QueryToListPredicateType : PredicateType
{
public static readonly QueryToListPredicateType Singleton = new QueryToListPredicateType();
private QueryToListPredicateType()
: base("query_to_list", true, 2, 2)
{
}
public override PredicateResolver CreateResolver(CompoundTerm input, Constraint constraint, QueryContext context)
{
return new Resolver(input, constraint, context);
}
private class Resolver : GroundPredicateResolver
{
public Resolver(CompoundTerm input, Constraint constraint, QueryContext context)
: base(input, constraint, context, 1)
{
Term goal = this.GetInputArgument(0);
ReleaseAssert.IsTrue(goal.IsGround() && goal is CompoundTerm, "query_to_list first argument {0} is not ground", goal);
ReleaseAssert.IsTrue(this.GetInputArgument(1) is Variable, "query_to_list second argument {0} is not variable", this.GetInputArgument(1));
}
protected override async Task<Term> GetNextTermAsync()
{
CompoundTerm goal = (CompoundTerm)this.GetInputArgument(0);
QueryContext context = new QueryContext();
VariableTable table = new VariableTable();
table.GetIndex("_result", true);
VariableBinding binding = new VariableBinding(table, 0, this.Input.Binding.Level + 1);
goal = goal.DuplicateGoal(binding);
goal.AddArgument(binding.GetLocalVariable(0), "this");
List<CompoundTerm> results = new List<CompoundTerm>();
PredicateResolver resolver = goal.PredicateType.CreateResolver(goal, null, context);
UnificationResult result = await resolver.GetNextAsync();
while (this.AddResult(results, result))
{
result = await resolver.GetNextAsync();
}
CompoundTerm compund = new CompoundTerm(this.Input.Functor);
compund.AddArgument(ListTerm.FromEnumerable(results), "1");
return compund;
}
private bool AddResult(List<CompoundTerm> results, UnificationResult result)
{
if (result == null)
{
return false;
}
if (result.Entries.Count > 0)
{
results.Add((CompoundTerm)result.Entries[0].GetEffectiveTerm());
}
return true;
}
}
}
}

Просмотреть файл

@ -0,0 +1,59 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System.Text.RegularExpressions;
using System.Threading.Tasks;
/// <summary>
/// Predicate type for getval.
/// </summary>
internal class RegexPredicateType : PredicateType
{
public static readonly RegexPredicateType Singleton = new RegexPredicateType();
private RegexPredicateType()
: base("regex", true, 2, 2)
{
}
public override PredicateResolver CreateResolver(CompoundTerm input, Constraint constraint, QueryContext context)
{
return new Resolver(input, constraint, context);
}
private class Resolver : GroundPredicateResolver
{
public Resolver(CompoundTerm input, Constraint constraint, QueryContext context)
: base(input, constraint, context, 1)
{
}
protected override Task<Term> GetNextTermAsync()
{
string text = this.GetInputArgumentString(0);
Regex pattern = new Regex(this.GetInputArgumentString(1));
CompoundTerm result;
Match match = pattern.Match(text);
if (match.Success)
{
result = new CompoundTerm(this.Input.Functor, null);
for (int i = 2; i < this.Input.Arguments.Count; i++)
{
string name = this.Input.Arguments[i].Name;
result.AddArgument(new Constant(match.Groups[name].Value), name);
}
}
else
{
result = null;
}
return Task.FromResult<Term>(result);
}
}
}
}

Просмотреть файл

@ -0,0 +1,178 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System;
using System.Globalization;
/// <summary>
/// Assertion class that will dump debug information and terminate the
/// process when assert fails.
/// Should be used to verify invariances.
/// </summary>
public static class ReleaseAssert
{
/// <summary>
/// Terminate the process after logging debug information.
/// </summary>
/// <param name="format">The format string for logging.</param>
/// <param name="args">The arguments for logging. If the argument
/// object supports IDumpable, this interface will be used to dump
/// the object. Otherwise ToString will be used.</param>
public static void Fail(string format, params object[] args)
{
string message;
if ((args != null) && (args.Length > 0))
{
message = string.Format(CultureInfo.InvariantCulture, format, args);
}
else
{
message = format;
}
ConsoleSink.WriteLine(ConsoleColor.Red, "Assert Failed: {0}\nStack:{1}", message, Environment.StackTrace);
throw new SystemException("Assert Failed");
}
/// <summary>
/// Assert that the condition is true. Otherwise terminate the process
/// after logging debug information.
/// </summary>
/// <param name="condition">The condition to assert.</param>
/// <param name="format">The format string for logging.</param>
/// <param name="args">The arguments for logging. If the argument
/// object supports IDumpable, this interface will be used to dump
/// the object. Otherwise ToString will be used.</param>
public static void IsTrue(bool condition, string format, object[] args)
{
if (!condition)
{
Fail(format, args);
}
return;
}
/// <summary>
/// Assert that the condition is true. Otherwise terminate the process
/// after logging debug information.
/// </summary>
/// <param name="condition">The condition to assert.</param>
/// <param name="format">The format string for logging.</param>
public static void IsTrue(bool condition, string format)
{
if (!condition)
{
Fail(format);
}
return;
}
/// <summary>
/// Assert that the condition is true. Otherwise terminate the process
/// after logging debug information.
/// </summary>
/// <param name="condition">The condition to assert.</param>
/// <param name="format">The format string for logging.</param>
/// <param name="t1">The argument for logging.</param>
public static void IsTrue<T1>(bool condition, string format, T1 t1)
{
if (!condition)
{
Fail(format, t1);
}
return;
}
public static void IsTrue<T1, T2>(bool condition, string format, T1 t1, T2 t2)
{
if (!condition)
{
Fail(format, t1, t2);
}
return;
}
public static void IsTrue<T1, T2, T3>(bool condition, string format, T1 t1, T2 t2, T3 t3)
{
if (!condition)
{
Fail(format, t1, t2, t3);
}
return;
}
public static void IsTrue<T1, T2, T3, T4>(bool condition, string format, T1 t1, T2 t2, T3 t3, T4 t4)
{
if (!condition)
{
Fail(format, t1, t2, t3, t4);
}
return;
}
public static void IsTrue<T1, T2, T3, T4, T5>(bool condition, string format, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5)
{
if (!condition)
{
Fail(format, t1, t2, t3, t4, t5);
}
return;
}
public static void IsTrue<T1, T2, T3, T4, T5, T6>(bool condition, string format, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6)
{
if (!condition)
{
Fail(format, t1, t2, t3, t4, t5, t6);
}
return;
}
public static void IsTrue<T1, T2, T3, T4, T5, T6, T7>(bool condition, string format, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7)
{
if (!condition)
{
Fail(format, t1, t2, t3, t4, t5, t6, t7);
}
return;
}
public static void IsTrue<T1, T2, T3, T4, T5, T6, T7, T8>(bool condition, string format, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8)
{
if (!condition)
{
Fail(format, t1, t2, t3, t4, t5, t6, t7, t8);
}
return;
}
/// <summary>
/// Assert that the condition is true. Otherwise terminate the process
/// after logging debug information.
/// </summary>
/// <param name="condition">The condition to assert.</param>
public static void IsTrue(bool condition)
{
if (!condition)
{
Fail(string.Empty, null);
}
return;
}
}
}

Просмотреть файл

@ -0,0 +1,13 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
public enum ResolveOrder
{
Asc,
Dsc,
None,
}
}

273
guan/Guan/Logic/Rule.cs Normal file
Просмотреть файл

@ -0,0 +1,273 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System.Collections.Generic;
using System.Text.RegularExpressions;
/// <summary>
/// Rule which contains a head and an optional body.
/// Parsing of a rule goes through the following steps:
/// 1. Parse the TermExpression into compound term.
/// 2. Break the compound term into head and bodies.
/// 3. For head and each goal, resolve the corresponding predicate type
/// 4. For every compound term, resolve the corresponding functor if there is one
/// 5. Invoke PostProcessing on each head & goal, which includes handling
/// argument name and variables, plus type-specific posting processing.
/// </summary>
public class Rule
{
private static readonly Regex VariablePattern = new Regex(@"^\?[_\w]+$", RegexOptions.Compiled);
private static readonly Regex ArgumentNamePattern = new Regex(@"^[_\.\w]+$", RegexOptions.Compiled);
private string text;
private CompoundTerm head;
private List<CompoundTerm> goals;
private VariableTable variableTable;
internal Rule(string text, CompoundTerm head, List<CompoundTerm> goals, VariableTable variableTable)
{
this.text = text;
this.head = head;
this.goals = goals;
this.variableTable = variableTable;
}
public CompoundTerm Head
{
get
{
return this.head;
}
}
public List<CompoundTerm> Goals
{
get
{
return this.goals;
}
}
internal VariableTable VariableTable
{
get
{
return this.variableTable;
}
}
public static Rule Parse(string text)
{
return Parse(ToGoal(TermExpression.Parse(text)), text);
}
public void AddArgument(CompoundTerm term, string argument, string name)
{
Term arg = TermExpression.Parse(argument);
term.AddArgument(arg, name);
ProcessCompoundTerm(term, this.variableTable, 0);
}
public override string ToString()
{
return this.text;
}
internal static Rule Parse(CompoundTerm rule, string text)
{
CompoundTerm head = null;
List<CompoundTerm> goals = new List<CompoundTerm>();
if (rule != null)
{
if (rule.Functor.Name != ":-")
{
head = rule;
}
else if (rule.Arguments.Count == 2)
{
head = ToGoal(rule.Arguments[0].Value);
if (head == null)
{
throw new GuanException("Invalid head in rule {0}", text);
}
CompoundTerm body = ToGoal(rule.Arguments[1].Value);
if (body == null)
{
throw new GuanException("Invalid body in rule {0}", text);
}
ExpandBody(body, goals);
}
}
if (head == null)
{
throw new GuanException("{0} is not a rule", text);
}
return new Rule(text, head, goals, new VariableTable());
}
internal void PostProcessing()
{
foreach (CompoundTerm goal in this.goals)
{
ProcessCompoundTerm(goal, this.variableTable, 0);
goal.PostProcessing(this);
}
ProcessCompoundTerm(this.head, this.variableTable, 0);
this.head.PostProcessing(this);
}
internal void ProcessMetaDataHead()
{
ProcessCompoundTerm(this.head, null, 0);
}
internal VariableBinding CreateBinding(int level)
{
return new VariableBinding(this.variableTable, this.goals.Count, level);
}
private static CompoundTerm ToGoal(Term term)
{
CompoundTerm result = term as CompoundTerm;
if (result != null)
{
return result;
}
string name = term.GetStringValue();
if (name == null)
{
return null;
}
return new CompoundTerm(Functor.Parse(name));
}
private static void ExpandBody(CompoundTerm body, List<CompoundTerm> goals)
{
while (body.Functor.Name == ",")
{
ReleaseAssert.IsTrue(body.Arguments.Count == 2);
CompoundTerm goal = ToGoal(body.Arguments[0].Value);
CompoundTerm rest = ToGoal(body.Arguments[1].Value);
if (goal == null || rest == null)
{
throw new GuanException("Invalid goal {0}", body);
}
goals.Add(goal);
body = rest;
}
goals.Add(body);
}
private static void ProcessCompoundTerm(CompoundTerm goal, VariableTable variableTable, int level)
{
if (level > 0 || goal.Functor.Name != "not")
{
level++;
}
PredicateType predicateType = goal.Functor as PredicateType;
int minPositional = (predicateType != null ? predicateType.MinPositionalArgument : 0);
int maxPositional = (predicateType != null ? predicateType.MaxPositionalArgument : int.MaxValue);
bool positional = (maxPositional > 0);
if (goal.Arguments.Count < minPositional)
{
throw new GuanException("Predicate {0} must have at least {1} argument(s)", goal, minPositional);
}
for (int i = 0; i < goal.Arguments.Count; i++)
{
bool nameOverride = false;
CompoundTerm compound = goal.Arguments[i].Value as CompoundTerm;
if (level > 0 && compound != null && compound.Functor.Name == "=")
{
ReleaseAssert.IsTrue(compound.Arguments.Count == 2 && i >= minPositional);
string name = compound.Arguments[0].Value.GetStringValue();
if (name == null || !ArgumentNamePattern.IsMatch(name))
{
throw new GuanException("Invalid argument name {0} for {1}", name, compound.Arguments[1].Value);
}
positional = false;
goal.Arguments[i] = new TermArgument(name, compound.Arguments[1].Value, goal.Functor.GetArgumentDescription(name));
compound = goal.Arguments[i].Value as CompoundTerm;
}
else
{
if (i >= maxPositional)
{
positional = false;
}
if (!positional)
{
nameOverride = true;
}
}
if (compound != null)
{
if (nameOverride)
{
goal.Arguments[i] = new TermArgument(compound.Functor.Name, compound, goal.Functor.GetArgumentDescription(compound.Functor.Name));
}
ProcessCompoundTerm(compound, variableTable, level);
if (compound.Functor.Name == "[")
{
goal.Arguments[i].Value = ListTerm.Parse(compound);
}
}
else
{
string constantValue = goal.Arguments[i].Value.GetStringValue();
string variableName;
if (constantValue == "_")
{
variableName = constantValue;
}
else if (constantValue != null && VariablePattern.IsMatch(constantValue))
{
variableName = constantValue.Substring(1);
}
else
{
variableName = null;
}
if (variableName != null)
{
int variableIndex = variableTable.GetIndex(variableName, true);
IndexedVariable variable = new IndexedVariable(variableIndex, variableName);
if (nameOverride)
{
goal.Arguments[i] = new TermArgument(variableName, variable, goal.Functor.GetArgumentDescription(variableName));
}
else
{
goal.Arguments[i].Value = variable;
}
}
else if (nameOverride)
{
goal.Arguments[i] = new TermArgument(constantValue, Constant.True, goal.Functor.GetArgumentDescription(constantValue));
}
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,431 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System.Collections.Generic;
using System.Threading.Tasks;
/// <summary>
/// Resolver using rules. This is typical case except for external predicates.
/// </summary>
internal class RulePredicateResolver : PredicateResolver
{
private Module module;
private List<Rule> rules;
private bool singleActivation;
private Rule currentRule;
private int ruleIndex;
private VariableBinding binding;
private VariableBinding tail;
private PredicateResolver[] resolvers;
private Constraint[] constraints;
private List<Task<UnificationResult>> tasks;
public RulePredicateResolver(Module module, List<Rule> rules, bool singleActivation, CompoundTerm input, Constraint constraint, QueryContext context)
: base(input, constraint, context)
{
this.module = module;
this.rules = rules;
this.singleActivation = singleActivation;
this.ruleIndex = 0;
}
public override async Task<UnificationResult> OnGetNextAsync()
{
if (this.binding != null)
{
// when tail optimization is effective, both binding need to backtrack.
if (this.tail != null)
{
_ = this.tail.MovePrev();
}
this.Backtrack();
}
while (this.ruleIndex < this.rules.Count)
{
if (this.binding == null)
{
if (this.InitializeRule())
{
#pragma warning disable CS8602 // Dereference of a possibly null reference.
this.binding.MoveNext();
#pragma warning restore CS8602 // Dereference of a possibly null reference.
}
else
{
this.ruleIndex++;
}
}
else
{
int currentIndex = this.binding.CurrentIndex;
if (currentIndex == this.currentRule.Goals.Count)
{
UnificationResult result = this.binding.CreateOutput();
// When tail optimization is effective, the output is a two-stage
// process: we use the current binding output to update tail_ and
// then the real output is from _tail.
if (this.tail != null)
{
result.Apply(this.tail);
this.tail.MoveNext();
this.WriteTrace("T-Exit");
result = this.tail.CreateOutput();
}
if (!this.Completed && this.IsCompleted(this.currentRule.Goals.Count - 1))
{
this.Complete();
}
return result;
}
if (this.resolvers[currentIndex] == null)
{
this.resolvers[currentIndex] = this.CreateResolver(currentIndex);
if (this.currentRule.Goals[currentIndex].PredicateType is CutPredicateType)
{
for (int i = currentIndex - 1; i >= 0; i--)
{
this.resolvers[i].Cancel();
}
}
if (currentIndex == this.currentRule.Goals.Count - 1 && !this.Context.EnableDebug && this.OptimizeTail())
{
this.WriteTrace("T-Call");
continue;
}
}
this.resolvers[currentIndex].WriteTrace("Call");
UnificationResult goalResult;
if (this.tasks == null || this.tasks.Count == 0)
{
goalResult = await this.resolvers[currentIndex].GetNextAsync();
}
else
{
// When backtracked to a goal (so that its iteration is not 0),
// if it is a goal immediately before a forward cut, the task for
// this goal is already initiated and in the tasks_ collection.
if ((currentIndex + 1 == this.currentRule.Goals.Count) ||
(this.resolvers[currentIndex].Iteration == 0) ||
!(this.currentRule.Goals[currentIndex + 1].PredicateType is ForwardCutPredicateType))
{
this.tasks.Add(this.resolvers[currentIndex].GetNextAsync());
}
while (true)
{
Task<UnificationResult> completedTask = await Task.WhenAny(this.tasks);
goalResult = await completedTask;
if (completedTask == this.tasks[this.tasks.Count - 1])
{
this.tasks.RemoveAt(this.tasks.Count - 1);
break;
}
else if (goalResult != null)
{
// If an earlier parallel task is completed, cancel all tasks after
// it.
int index = this.tasks.IndexOf(completedTask);
int count = this.tasks.Count - index;
for (; currentIndex > 0 && count > 1; currentIndex--)
{
this.resolvers[currentIndex].Context.IsCancelled = true;
this.resolvers[currentIndex] = null;
if (this.currentRule.Goals[currentIndex].PredicateType is ForwardCutPredicateType)
{
count--;
}
}
ReleaseAssert.IsTrue(count == 1);
this.tasks.RemoveRange(index, this.tasks.Count - index);
this.binding = this.resolvers[currentIndex].Input.Binding;
break;
}
else
{
_ = this.tasks.Remove(completedTask);
}
}
}
if (goalResult != null)
{
goalResult.Apply(this.binding);
bool proceed = this.UpdateConstraints(goalResult);
this.binding.MoveNext();
if (proceed)
{
this.resolvers[currentIndex].WriteTrace("Exit");
}
else
{
this.Backtrack();
}
}
else if (this.currentRule.Goals[currentIndex].PredicateType is ForwardCutPredicateType)
{
this.resolvers[currentIndex] = null;
currentIndex--;
this.binding = this.resolvers[currentIndex].Input.Binding;
this.resolvers[currentIndex].Context.ClearChildren();
}
else
{
this.resolvers[currentIndex].WriteTrace("Fail");
this.Backtrack();
}
}
}
return null;
}
private bool InitializeRule()
{
this.currentRule = this.rules[this.ruleIndex];
this.binding = this.currentRule.CreateBinding(this.Input.Binding.Level + 1);
if (!this.binding.Unify(this.currentRule.Head.DuplicateGoal(this.binding), this.Input))
{
this.binding = null;
return false;
}
this.resolvers = new PredicateResolver[this.currentRule.Goals.Count];
this.constraints = new Constraint[this.currentRule.Goals.Count];
if (this.currentRule.Goals.Count > 0)
{
this.constraints[0] = this.Constraint;
}
return true;
}
private PredicateResolver CreateResolver(int index)
{
CompoundTerm goal = this.currentRule.Goals[index];
QueryContext context = null;
for (int i = index - 1; i >= 0 && context == null; i--)
{
context = this.resolvers[i].Context;
}
if (context == null)
{
context = this.Context;
}
// For forwardcut, the context will be a child to the context for the goal
// immediately before it.
if (this.currentRule.Goals[index].PredicateType is ForwardCutPredicateType && index > 0 && !this.resolvers[index - 1].Completed)
{
if (this.tasks == null)
{
this.tasks = new List<Task<UnificationResult>>();
}
VariableBinding currentBinding = this.binding;
this.binding = new VariableBinding(this.binding);
_ = currentBinding.MovePrev();
context = context.CreateChild();
this.tasks.Add(this.resolvers[index - 1].GetNextAsync());
}
PredicateResolver result = goal.PredicateType.CreateResolver(goal.DuplicateGoal(this.binding), this.constraints[index], context);
if (result == null)
{
throw new GuanException("No resolver defined for {0}", goal.PredicateType);
}
return result;
}
private bool UpdateConstraints(UnificationResult result)
{
int currentIndex = this.binding.CurrentIndex;
Constraint current = this.constraints[currentIndex];
Constraint next;
// The constraints propagated to the next goal comes from two sources:
// One is the constraints from the current goal (the ones remaining after
// applying the output from the current goal, second is the constraints
// added by the current goal.
if (currentIndex == this.currentRule.Goals.Count - 1)
{
next = null;
}
else if (result.Constraints != null)
{
next = new Constraint();
next.Add(result.Constraints);
}
else
{
next = current;
}
// At this point, next is either the same as currennt, or contains nothing
// from current.
if (!result.IsEmpty)
{
for (int i = 0; i < current.Terms.Count; i++)
{
Constant evaluted = current.Terms[i].Evaluate(this.Context) as Constant;
if (evaluted == null)
{
if (next != current && next != null)
{
next.Add(current.Terms[i]);
}
}
else if (!evaluted.IsTrue())
{
return false;
}
else if (next == current)
{
// Add previous constraints that have verified to remain pending,
// which effectlively removes the current constraint
next = new Constraint();
for (int j = 0; j < i; j++)
{
next.Add(current.Terms[j]);
}
}
}
}
else if (next != current && next != null)
{
next.Add(current.Terms);
}
if (next != null)
{
this.constraints[currentIndex + 1] = next;
}
return true;
}
private void Backtrack()
{
int currentIndex = this.binding.CurrentIndex;
if (currentIndex < this.resolvers.Length && this.resolvers[currentIndex] != null)
{
this.resolvers[currentIndex].OnBacktrack();
this.resolvers[currentIndex] = null;
}
if (this.binding.MovePrev())
{
if (this.currentRule.Goals[currentIndex - 1].Functor is CutPredicateType)
{
this.ruleIndex = this.rules.Count;
}
}
else if (this.singleActivation && this.Iteration > 0)
{
this.ruleIndex = this.rules.Count;
}
else
{
this.ruleIndex++;
this.binding = null;
}
}
private bool OptimizeTail()
{
if (this.Input.Option.IsTraceEnabled(null).Item1)
{
return false;
}
int index = this.currentRule.Goals.Count - 1;
RulePredicateResolver ruleResolver = this.resolvers[index] as RulePredicateResolver;
// We can optimize if the goal is defined by rule and it does not require us
// to maintain different Max count.
if (ruleResolver == null || ruleResolver.Max != this.Max)
{
return false;
}
// TODO: allow optimization when there is constraint
if (this.constraints[index].Terms.Count > 0)
{
return false;
}
if (!this.IsCompleted(index - 1))
{
return false;
}
this.UpdateTail(ruleResolver);
this.Load(ruleResolver);
return true;
}
private bool IsCompleted(int index)
{
for (int i = index; i >= 0; i--)
{
if (this.currentRule.Goals[i].Functor == CutPredicateType.Singleton)
{
return true;
}
else if (!this.resolvers[i].Completed)
{
return false;
}
}
return (this.ruleIndex == this.rules.Count - 1);
}
private void UpdateTail(RulePredicateResolver resolver)
{
if (this.tail == null)
{
this.tail = this.binding;
}
else
{
// Use whatever output we have currently to update _tail
UnificationResult result = this.binding.CreateOutput();
result.Apply(this.tail);
// The input to the next goal is for the current bining, we need t
// update so that it uses tail_.
resolver.Input.Migrate(this.tail);
}
this.tail.ResetTail();
this.binding = null;
}
private void Load(RulePredicateResolver resolver)
{
this.Input = resolver.Input;
this.Iteration = 0;
this.module = resolver.module;
this.rules = resolver.rules;
this.ruleIndex = 0;
}
}
}

Просмотреть файл

@ -0,0 +1,72 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System.Threading.Tasks;
/// <summary>
/// Predicate type for setval.
/// </summary>
internal class SetValPredicateType : PredicateType
{
public static readonly SetValPredicateType Backtrack = new SetValPredicateType("b_setval");
public static readonly SetValPredicateType NoBacktrack = new SetValPredicateType("setval");
private SetValPredicateType(string name)
: base(name, true, 2, 2)
{
}
public override PredicateResolver CreateResolver(CompoundTerm input, Constraint constraint, QueryContext context)
{
return new Resolver(this, input, constraint, context);
}
public override void AdjustTerm(CompoundTerm term, Rule rule)
{
string name = term.Arguments[0].Value.GetStringValue();
if (name == null)
{
throw new GuanException("The first argument of getval must be string: {0}", term);
}
}
private class Resolver : BooleanPredicateResolver
{
private SetValPredicateType type;
private string name;
private object oldValue;
public Resolver(SetValPredicateType type, CompoundTerm input, Constraint constraint, QueryContext context)
: base(input, constraint, context)
{
this.type = type;
this.name = this.GetInputArgumentString(0);
}
public override void OnBacktrack()
{
if (this.type == Backtrack)
{
this.Context[this.name] = this.oldValue;
}
}
protected override Task<bool> CheckAsync()
{
if (this.type == Backtrack)
{
this.oldValue = this.Context[this.name];
}
Term term = this.GetInputArgument(1);
ReleaseAssert.IsTrue(term.IsGround());
this.Context[this.name] = term;
return Task.FromResult(true);
}
}
}
}

Просмотреть файл

@ -0,0 +1,56 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System;
using System.Threading.Tasks;
internal class SleepPredicateType : PredicateType
{
public static readonly SleepPredicateType Singleton = new SleepPredicateType();
private SleepPredicateType()
: base("sleep", true, 1)
{
}
public override PredicateResolver CreateResolver(CompoundTerm input, Constraint constraint, QueryContext context)
{
return new Resolver(input, constraint, context);
}
private class Resolver : BooleanPredicateResolver
{
public Resolver(CompoundTerm input, Constraint constraint, QueryContext context)
: base(input, constraint, context)
{
}
protected override async Task<bool> CheckAsync()
{
object value = this.GetInputArgumentObject(0);
TimeSpan interval;
if (value is TimeSpan)
{
interval = (TimeSpan)value;
}
else if (value is long)
{
interval = TimeSpan.FromSeconds((long)value);
}
else
{
interval = TimeSpan.FromSeconds((double)value);
}
Console.WriteLine("Sleeping for {0}", interval);
await Task.Delay(interval);
Console.WriteLine("Sleep completed");
return true;
}
}
}
}

Просмотреть файл

@ -0,0 +1,29 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
internal class SplitFunc : BinaryFunc
{
public static readonly SplitFunc Singleton = new SplitFunc();
private SplitFunc()
: base("Split")
{
}
protected override object InvokeBinary(object arg1, object arg2)
{
string input = (string)arg1;
string delimiter = (string)arg2;
char[] delimiters = new char[delimiter.Length];
for (int i = 0; i < delimiter.Length; i++)
{
delimiters[i] = delimiter[i];
}
return input.Split(delimiters);
}
}
}

Просмотреть файл

@ -0,0 +1,69 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System.Collections.Generic;
/// <summary>
/// Functions that do not depend on the context.
/// </summary>
public abstract class StandaloneFunc : GuanFunc
{
/// <summary>
/// Initializes a new instance of the <see cref="StandaloneFunc"/> class.
/// Constructor.
/// </summary>
/// <param name="name">Name of the function.</param>
protected StandaloneFunc(string name)
: base(name)
{
}
/// <summary>
/// Invoke the function.
/// </summary>
/// <param name="args">The arguments.</param>
/// <returns>Function result.</returns>
public abstract object Invoke(object[] args);
/// <summary>
/// Invoke the function.
/// </summary>
/// <param name="context">The context class.</param>
/// <param name="args">The array of arguments to the function.</param>
/// <returns>The function result.</returns>
public sealed override object Invoke(IPropertyContext context, object[] args)
{
return this.Invoke(args);
}
/// <summary>
/// Replace the function with the only child argument.
/// This is used during Bind to simplify the operation
/// tree if the function result is the same as its only
/// argument.
/// </summary>
/// <param name="args">The arguments.</param>
/// <returns>The optimized function object.</returns>
internal GuanFunc Collapse(IList<GuanExpression> args)
{
if (args.Count == 1)
{
GuanExpression arg = args[0];
// Replace arguments with the ones of the argument.
args.Clear();
foreach (GuanExpression child in arg.Children)
{
args.Add(child);
}
return arg.Func;
}
return this;
}
}
}

Просмотреть файл

@ -0,0 +1,120 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
internal class StringFunc : UnaryFunc
{
public static readonly StringFunc Singleton = new StringFunc();
private StringFunc()
: base("string")
{
}
public override object UnaryInvoke(object arg)
{
return (arg != null ? arg.ToString() : string.Empty);
}
internal class StrFunc : UnaryFunc
{
public static readonly StrFunc Singleton = new StrFunc();
private StrFunc()
: base("str")
{
}
public override object UnaryInvoke(object arg)
{
string result = (arg != null ? arg.ToString() : string.Empty);
return "\"" + result + "\"";
}
}
internal class TrimFunc : UnaryFunc
{
public static readonly TrimFunc Singleton = new TrimFunc();
private TrimFunc()
: base("Trim")
{
}
public override object UnaryInvoke(object arg)
{
string result = (arg != null ? arg.ToString() : string.Empty);
return result?.Trim();
}
}
internal class SubStrFunc : StandaloneFunc
{
public static readonly SubStrFunc Singleton = new SubStrFunc();
private SubStrFunc()
: base("substr")
{
}
public override object Invoke(object[] args)
{
if (args.Length == 0)
{
return string.Empty;
}
string result = args[0] as string;
if (result == null)
{
return string.Empty;
}
int start = 0;
int end = result.Length;
if (args.Length > 1 && args[1] != null)
{
start = Utility.Convert<int>(args[1]);
}
if (args.Length > 2)
{
end = Utility.Convert<int>(args[2]);
}
return result.Substring(start, end - start);
}
}
internal class ContainsFunc : BinaryFunc
{
public static readonly ContainsFunc Singleton = new ContainsFunc();
private ContainsFunc()
: base("Contains")
{
}
protected override object InvokeBinary(object arg1, object arg2)
{
if (arg1 == null || arg2 == null)
{
throw new GuanException("Contains: Arguments cannot be null.");
}
if (arg1.GetType() != typeof(string) || arg2.GetType() != typeof(string))
{
throw new GuanException("Contains: both required parameters must be of type System.String.");
}
string needle = (string)arg1;
string haystack = (string)arg2;
return haystack.Contains(needle);
}
}
}
}

183
guan/Guan/Logic/Term.cs Normal file
Просмотреть файл

@ -0,0 +1,183 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System.Collections.Generic;
/// <summary>
/// Base class for terms, including Constant, Variable and CompoundTerm.
/// </summary>
public abstract class Term
{
protected Term()
{
}
internal virtual VariableBinding Binding
{
get
{
return VariableBinding.Ground;
}
}
public abstract bool IsGround();
public virtual Term GetEffectiveTerm()
{
return this;
}
#pragma warning disable CA1024 // Use properties where appropriate
public object GetObjectValue()
#pragma warning restore CA1024 // Use properties where appropriate
{
Constant constant = this as Constant;
if (constant != null)
{
return constant.Value;
}
ObjectCompundTerm objectCompundTerm = this as ObjectCompundTerm;
if (objectCompundTerm != null)
{
return objectCompundTerm.Value;
}
return null;
}
#pragma warning disable CA1024 // Use properties where appropriate
public string GetStringValue()
#pragma warning restore CA1024 // Use properties where appropriate
{
Constant constant = this as Constant;
if (constant == null)
{
return null;
}
return constant.Value as string;
}
internal static Term FromObject(object value)
{
Term term = value as Term;
if (term != null)
{
return term;
}
return new Constant(value);
}
internal Term ForceEvaluate(QueryContext context)
{
Term result = this.GetEffectiveTerm();
ReleaseAssert.IsTrue(!(result is Variable), "{0} is not gounded", this);
CompoundTerm compound = result as CompoundTerm;
if (compound != null && compound.Functor is EvaluatedFunctor)
{
foreach (TermArgument arg in compound.Arguments)
{
arg.Value = arg.Value.ForceEvaluate(context);
}
return compound.Evaluate(context);
}
return result;
}
/// <summary>
/// Get a copy that gets rid of variables so that the value is
/// not affected by backtracking,
/// </summary>
/// <returns>Grounded copy of the term</returns>
internal Term GetGroundedCopy()
{
Term term = this.GetEffectiveTerm();
Constant constant = term as Constant;
if (constant != null)
{
return constant;
}
ReleaseAssert.IsTrue(!(term is Variable), "{0} is not gounded", this);
CompoundTerm compound = (CompoundTerm)term;
CompoundTerm result = new CompoundTerm(compound.Functor, null);
foreach (TermArgument arg in compound.Arguments)
{
result.AddArgument(arg.Value.GetGroundedCopy(), arg.Name);
}
return result;
}
internal Term UpdateBinding(Dictionary<Variable, Variable> mapping, VariableBinding binding)
{
Term term = this.GetEffectiveTerm();
Constant constant = term as Constant;
if (constant != null)
{
return constant;
}
Variable variable = term as Variable;
if (variable != null)
{
Variable mappedVariable;
if (mapping.TryGetValue(variable, out mappedVariable))
{
return mappedVariable;
}
return variable;
}
CompoundTerm compound = (CompoundTerm)term;
CompoundTerm result = null;
for (int i = 0; i < compound.Arguments.Count; i++)
{
Term newArg = compound.Arguments[i].Value.UpdateBinding(mapping, binding);
if (result == null && newArg != compound.Arguments[i].Value)
{
result = new CompoundTerm(compound.Functor, this.Binding);
for (int j = 0; j < i; j++)
{
result.AddArgument(compound.Arguments[j].Value, compound.Arguments[j].Name);
}
}
if (result != null)
{
result.AddArgument(newArg, compound.Arguments[i].Name);
}
}
if (result != null)
{
return result;
}
return this;
}
internal bool Unify(Term other)
{
VariableBinding binding = this.Binding;
if (binding == VariableBinding.Ground)
{
binding = other.Binding;
}
return binding.Unify(this, other);
}
}
}

Просмотреть файл

@ -0,0 +1,57 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
/// <summary>
/// Argument of a compound term.
/// </summary>
public class TermArgument
{
private string name;
private ArgumentDescription desc;
private Term value;
public TermArgument(string name, Term value, ArgumentDescription desc = null)
{
this.name = name;
this.value = value;
this.desc = desc;
}
public string Name
{
get
{
return this.name;
}
}
public ArgumentDescription Description
{
get
{
return this.desc;
}
}
public Term Value
{
get
{
return this.value;
}
set
{
this.value = value;
}
}
public override string ToString()
{
return this.Name + ":" + this.value.ToString();
}
}
}

Просмотреть файл

@ -0,0 +1,420 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System.Collections.Generic;
/// <summary>
/// Parser for term expressions.s
/// </summary>
internal static class TermExpression
{
private static readonly OperatorTri Operators = CreateOperators();
private static readonly Functor IdentityFunctor = new Functor("()");
private static readonly Operator CommaOperator = Operators.Get(",");
private static readonly Operator CloseParentheses = Operators.Get(")");
private enum Associativity
{
Left,
Right,
None,
Open,
Close,
}
public static Term Parse(string text)
{
// Conceptually, last is the top of the stack. It is separated from
// the stack for convenient access since we often need to examine
// the top item.
Stack<CompoundTerm> pending = new Stack<CompoundTerm>();
CompoundTerm last = new CompoundTerm(IdentityFunctor);
Term term = null;
int i = 0;
while (i <= text.Length)
{
CompoundTerm current = null;
string token;
Operator op = Read(text, ref i, out token);
if (op != null)
{
if (op.Name == "(")
{
if (token != null)
{
current = new CompoundTerm(new Functor(token));
token = null;
}
else
{
current = new CompoundTerm(IdentityFunctor);
}
op = null;
}
else if (op.Associativity != Associativity.Close)
{
current = new CompoundTerm(op.Func);
if (op.Associativity == Associativity.Open)
{
op = null;
}
}
}
if (token != null)
{
if (term != null)
{
throw new GuanException("Not well formed at {0} {1}", term, token);
}
term = Constant.Parse(token);
}
while (op != null && CanPop(last.Functor, ref op))
{
if (term != null)
{
AddArgument(last, term);
}
// Normalize "(term)" to "term".
if (op == null && last.Functor == IdentityFunctor && last.Arguments.Count == 1)
{
CompoundTerm temp = last.Arguments[0].Value as CompoundTerm;
if (temp != null)
{
last = temp;
}
}
if (current != null && current.Functor.Name == "," && op == null)
{
current = null;
term = null;
}
else
{
term = last;
if (pending.Count > 0)
{
last = pending.Pop();
}
else
{
ReleaseAssert.IsTrue(i >= text.Length, "Expression {0} not well formed", text);
}
}
}
if (current != null)
{
if (term != null)
{
AddArgument(current, term);
term = null;
}
pending.Push(last);
last = current;
}
}
ReleaseAssert.IsTrue(pending.Count == 0);
return ConvertIdentity(term);
}
private static OperatorTri CreateOperators()
{
OperatorTri result = new OperatorTri();
result.Add("(", 99, Associativity.Open, "()");
result.Add(")", 99, Associativity.Close);
result.Add("[", 99, Associativity.Open);
result.Add("]", 99, Associativity.Close);
result.Add(":-", 89, Associativity.None);
result.Add(";", 79, Associativity.Right);
result.Add("|", 79, Associativity.Right);
result.Add(",", 78, Associativity.Right, "','");
result.Add("=", 69, Associativity.None);
result.Add("is", 69, Associativity.None);
result.Add("||", 49);
result.Add("&&", 48);
result.Add("==", 39, Associativity.None);
result.Add("!=", 39, Associativity.None);
result.Add(">", 38, Associativity.None);
result.Add(">=", 38, Associativity.None);
result.Add("<", 38, Associativity.None);
result.Add("<=", 38, Associativity.None);
result.Add("+", 19);
result.Add("-", 19);
result.Add("*", 18);
result.Add("/", 18);
result.Add("%", 18);
return result;
}
private static void AddArgument(CompoundTerm term, Term arg)
{
term.AddArgument(ConvertIdentity(arg), term.Arguments.Count.ToString());
}
private static Term ConvertIdentity(Term arg)
{
CompoundTerm compoundArg = arg as CompoundTerm;
while (compoundArg != null && compoundArg.Functor == IdentityFunctor)
{
ReleaseAssert.IsTrue(compoundArg.Arguments.Count == 1);
arg = compoundArg.Arguments[0].Value;
compoundArg = arg as CompoundTerm;
}
return arg;
}
/// <summary>
/// Whether the stack should be popped.
/// </summary>
/// <param name="last">Functor of the top item on the (conceptual) stack.</param>
/// <param name="current">The current operator, will be set to null if it
/// is consumed by the last term.</param>
/// <returns>True if the stack should pop.</returns>
private static bool CanPop(Functor last, ref Operator current)
{
OperatorFunctor lastOp = last as OperatorFunctor;
if (current.Name == ")")
{
if (lastOp == null)
{
current = null;
}
return true;
}
if (current.Name == "]")
{
if (lastOp != null && lastOp.Name == "[")
{
current = null;
}
return true;
}
if (lastOp == null)
{
if (current.Name != "," || last == IdentityFunctor)
{
return false;
}
current = null;
return true;
}
if (lastOp.Operator.Priority < current.Priority)
{
return true;
}
if (lastOp.Operator.Priority > current.Priority)
{
return false;
}
if (current.Associativity == Associativity.Left && lastOp.Operator.Associativity != Associativity.Right)
{
return true;
}
if (current.Associativity == Associativity.Left || lastOp.Operator.Associativity != Associativity.Right)
{
throw new GuanException("Invalid combination of operators {0} and {1}", lastOp.Operator, current);
}
return false;
}
/// <summary>
/// Read the next operator.
/// </summary>
/// <param name="text">The entire expression.</param>
/// <param name="offset">Start index in the expression for reading.</param>
/// <param name="token">The token before the operator, if any.</param>
/// <returns>The next operator, null if a token followed by white space is read.</returns>
private static Operator Read(string text, ref int offset, out string token)
{
int start = offset;
while (start < text.Length && char.IsWhiteSpace(text[start]))
{
start++;
}
Operator op = null;
token = null;
offset = start;
while (offset < text.Length && !char.IsWhiteSpace(text[offset]) && op == null)
{
char c = text[offset];
if (c == '"' || c == '\'')
{
if (offset != start)
{
throw new GuanException("Invalid quote in term expression: {0}", text);
}
int end = SkipQuote(text, c, offset);
token = UnescapeQuote(text.Substring(start, end - start + 1), c);
offset = end + 1;
}
else
{
if (c == '_' || char.IsLetterOrDigit(c))
{
// check for operators like "is"
if (offset == start)
{
op = Operators.Get(text, ref offset);
// If followed by a non-symbol character, consider it as word prefix instead of operator
if (op != null && offset < text.Length && (text[offset] == '_' || char.IsLetterOrDigit(text[offset])))
{
op = null;
offset = start;
}
}
}
else
{
op = Operators.Get(text, ref offset);
}
if (op == null)
{
if (token != null)
{
throw new GuanException("Invalid quote in term expression: {0}", text);
}
offset++;
}
}
}
if (token == null)
{
int tokenEnd = (op != null ? offset - op.Name.Length : offset);
if (tokenEnd > start)
{
token = text.Substring(start, tokenEnd - start);
}
}
// When the end of expression is reached, add a ')' to balance to the initial
// '(' introduced when parsing is started.
if (op == null && offset == text.Length)
{
op = CloseParentheses;
offset++;
}
return op;
}
private static int SkipQuote(string text, char quote, int index)
{
int i = index;
while (++i < text.Length)
{
if (text[i] == quote)
{
return i;
}
else if (text[i] == '\\')
{
i++;
}
}
return index;
}
private static string UnescapeQuote(string text, char quote)
{
string to = quote.ToString();
string from = "\\" + to;
return text.Replace(from, to);
}
private class Operator
{
private string display;
public Operator(string name, int priority, Associativity associativity, string display)
{
this.Name = name;
this.Priority = priority;
this.Associativity = associativity;
this.Func = new OperatorFunctor(this);
this.display = display;
}
public string Name { get; set; }
public int Priority { get; set; }
public Associativity Associativity { get; set; }
public OperatorFunctor Func { get; set; }
public CompoundTerm CreateTerm()
{
return new CompoundTerm(this.Func);
}
public override string ToString()
{
return this.display;
}
}
private class OperatorFunctor : Functor
{
public OperatorFunctor(Operator op)
: base(op.Name)
{
this.Operator = op;
}
public Operator Operator { get; set; }
public override string ToString()
{
return this.Operator.ToString();
}
}
private class OperatorTri : Tri<Operator>
{
public void Add(string name, int priority, Associativity associativity = Associativity.Left, string display = null)
{
this.Add(name, new Operator(name, priority, associativity, display ?? name));
}
}
}
}

Просмотреть файл

@ -0,0 +1,112 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System;
using System.Collections.Generic;
/// <summary>
/// Execution option for goal term.
/// </summary>
public class TermOption
{
public static readonly string MaxIteration = "max";
public static readonly string Trace = "trace";
public static readonly TermOption Default = new TermOption();
private static readonly Functor OptionFunctor = new Functor("_option");
private int max;
private bool catchException;
private List<string> traceTypes;
private CompoundTerm term;
public TermOption()
{
this.term = new CompoundTerm(OptionFunctor);
this.catchException = false;
}
public TermOption(CompoundTerm term)
: this()
{
if (!term.IsGround())
{
throw new ArgumentException("Option is not ground: " + term.ToString());
}
foreach (TermArgument arg in term.Arguments)
{
this[arg.Name] = term[arg.Name];
}
}
public int Max
{
get
{
return this.max;
}
}
public bool CatchException
{
get
{
return this.catchException;
}
}
public object this[string name]
{
get
{
if (name == MaxIteration)
{
return this.max;
}
return this.term[name];
}
internal set
{
if (name == MaxIteration)
{
this.max = (int)(long)value;
}
else if (name == Trace)
{
string traceConfig = (string)value;
this.traceTypes = new List<string>(traceConfig.Split(',', System.StringSplitOptions.RemoveEmptyEntries));
}
else if (name == "CatchException")
{
this.catchException = Utility.Convert<bool>(value);
}
else
{
this.term[name] = value;
}
}
}
internal (bool, bool) IsTraceEnabled(string type)
{
if (this.traceTypes != null)
{
foreach (string traceType in this.traceTypes)
{
if (type == null || traceType.Contains(type))
{
return (true, traceType.StartsWith("!"));
}
}
}
return (false, false);
}
}
}

Просмотреть файл

@ -0,0 +1,69 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System.Threading.Tasks;
/// <summary>
/// Predicate types for term inspection.
/// </summary>
internal class TermPropertyPredicateType : PredicateType
{
public static readonly TermPropertyPredicateType Var = new TermPropertyPredicateType("var");
public static readonly TermPropertyPredicateType NonVar = new TermPropertyPredicateType("nonvar");
public static readonly TermPropertyPredicateType Atom = new TermPropertyPredicateType("atom");
public static readonly TermPropertyPredicateType Compound = new TermPropertyPredicateType("compound");
public static readonly TermPropertyPredicateType Ground = new TermPropertyPredicateType("ground");
private TermPropertyPredicateType(string name)
: base(name, true, 1, 1)
{
}
public override PredicateResolver CreateResolver(CompoundTerm input, Constraint constraint, QueryContext context)
{
return new Resolver(this, input, constraint, context);
}
private class Resolver : BooleanPredicateResolver
{
private TermPropertyPredicateType type;
public Resolver(TermPropertyPredicateType type, CompoundTerm input, Constraint constraint, QueryContext context)
: base(input, constraint, context)
{
this.type = type;
}
protected override Task<bool> CheckAsync()
{
bool result;
Term term = this.GetInputArgument(0);
if (this.type == Var)
{
result = term is Variable;
}
else if (this.type == NonVar)
{
result = !(term is Variable);
}
else if (this.type == Atom)
{
result = term is Constant;
}
else if (this.type == Compound)
{
result = term is CompoundTerm;
}
else
{
result = term.IsGround();
}
return Task.FromResult(result);
}
}
}
}

Просмотреть файл

@ -0,0 +1,51 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System.Threading.Tasks;
/// <summary>
/// Predicate type for trace.
/// </summary>
internal class TracePredicateType : PredicateType
{
public static readonly TracePredicateType Enable = new TracePredicateType("trace");
public static readonly TracePredicateType Disable = new TracePredicateType("notrace");
private TracePredicateType(string name)
: base(name)
{
}
public override PredicateResolver CreateResolver(CompoundTerm input, Constraint constraint, QueryContext context)
{
return new Resolver(this == Enable, context);
}
private class Resolver : BooleanPredicateResolver
{
private bool enable;
private bool oldValue;
public Resolver(bool enable, QueryContext context)
: base(null, null, context)
{
this.enable = enable;
}
public override void OnBacktrack()
{
this.Context.EnableTrace = this.oldValue;
}
protected override Task<bool> CheckAsync()
{
this.oldValue = this.Context.EnableTrace;
this.Context.EnableTrace = this.enable;
return Task.FromResult(true);
}
}
}
}

126
guan/Guan/Logic/Tri.cs Normal file
Просмотреть файл

@ -0,0 +1,126 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System;
public class Tri<T>
where T : class
{
private TriNode root;
public Tri()
{
this.root = new TriNode(0);
}
public void Add(string key, T value)
{
this.root.Add(key, value);
}
public T Get(string key, ref int start)
{
return this.root.Get(key, ref start);
}
public T Get(string key)
{
int start = 0;
T result = this.Get(key, ref start);
if (start != key.Length)
{
return null;
}
return result;
}
private class TriNode
{
private int level;
private T value;
private char begin;
private TriNode[] children;
public TriNode(int level)
{
this.value = null;
this.level = level;
}
public void Add(string key, T value)
{
if (this.level == key.Length)
{
if (this.value != null)
{
throw new ArgumentException("Duplicate value for: " + key);
}
this.value = value;
}
else
{
int i;
if (this.children == null)
{
this.children = new TriNode[1];
this.begin = key[this.level];
i = 0;
}
else
{
i = key[this.level] - this.begin;
if (i < 0)
{
TriNode[] children = new TriNode[this.children.Length - i];
Array.Copy(this.children, 0, children, -i, this.children.Length);
this.children = children;
this.begin = key[this.level];
i = 0;
}
else if (i >= this.children.Length)
{
TriNode[] children = new TriNode[i + 1];
Array.Copy(this.children, 0, children, 0, this.children.Length);
this.children = children;
}
}
if (this.children[i] == null)
{
this.children[i] = new TriNode(this.level + 1);
}
this.children[i].Add(key, value);
}
}
public T Get(string key, ref int start)
{
if (start + this.level < key.Length)
{
int i = key[start + this.level] - this.begin;
if (this.children != null && i >= 0 && i < this.children.Length && this.children[i] != null)
{
T result = this.children[i].Get(key, ref start);
if (result != null)
{
return result;
}
}
}
if (this.value != null)
{
start += this.level;
}
return this.value;
}
}
}
}

Просмотреть файл

@ -0,0 +1,51 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System;
/// <summary>
/// Functions that take only one argument.
/// </summary>
public abstract class UnaryFunc : StandaloneFunc
{
/// <summary>
/// Initializes a new instance of the <see cref="UnaryFunc"/> class.
/// Constructor.
/// </summary>
/// <param name="name">Name of the function.</param>
protected UnaryFunc(string name)
: base(name)
{
}
/// <summary>
/// Invoke the function with one argument.
/// </summary>
/// <param name="arg">The argument.</param>
/// <returns>Function result.</returns>
public abstract object UnaryInvoke(object arg);
/// <summary>
/// Invoke the function.
/// </summary>
/// <param name="args">The arguments.</param>
/// <returns>Function result.</returns>
public sealed override object Invoke(object[] args)
{
if (args == null)
{
throw new ArgumentNullException("args");
}
if (args.Length != 1)
{
throw new ArgumentException("Invalid argument for UnaryFunc: " + this);
}
return this.UnaryInvoke(args[0]);
}
}
}

Просмотреть файл

@ -0,0 +1,130 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System.Collections.Generic;
using System.Text;
/// <summary>
/// The output from resolving a goal, including the instantiation of
/// variables and the constraints to be propagated to the remaining
/// goals.
/// </summary>
public class UnificationResult
{
internal static readonly UnificationResult Empty = new UnificationResult(0);
private List<OutputVariable> entries;
private List<CompoundTerm> constraints;
public UnificationResult(int capacity)
{
this.entries = new List<OutputVariable>(capacity);
}
public List<CompoundTerm> Constraints
{
get
{
return this.constraints;
}
}
internal bool IsEmpty
{
get
{
return (this.entries.Count == 0);
}
}
internal List<OutputVariable> Entries
{
get
{
return this.entries;
}
}
public void AddConstraint(CompoundTerm constraint)
{
if (this.constraints == null)
{
this.constraints = new List<CompoundTerm>();
}
this.constraints.Add(constraint);
}
public override string ToString()
{
StringBuilder result = new StringBuilder();
foreach (OutputVariable entry in this.entries)
{
if (!entry.Original.Name.StartsWith('_'))
{
Term value = entry.GetBoundTerm();
LinkedVariable linkedVariable = value as LinkedVariable;
if (linkedVariable != null)
{
_ = result.AppendFormat("?{0}=?{1},", entry.Original.Name, linkedVariable.Original.Name);
}
else
{
Constant constant = value as Constant;
if (constant != null)
{
value = ObjectCompundTerm.Create(constant.Value);
if (value == null)
{
value = constant;
}
}
_ = result.AppendFormat("?{0}={1},", entry.Original.Name, value);
}
}
}
if (result.Length > 1)
{
result.Length--;
}
return result.ToString();
}
internal void Add(OutputVariable entry)
{
this.entries.Add(entry);
}
internal void Apply(VariableBinding binding)
{
ReleaseAssert.IsTrue(binding.CurrentIndex >= 0);
foreach (OutputVariable entry in this.entries)
{
Term value = entry.GetEffectiveTerm();
if (!value.IsGround())
{
CompoundTerm compound = value as CompoundTerm;
if (compound != null)
{
value = compound.DuplicateOutput(binding);
}
else
{
OutputVariable outputVariable = (OutputVariable)value;
value = outputVariable.Original;
}
}
ReleaseAssert.IsTrue(entry.Original.Binding == binding);
entry.Original.SetValue(value);
}
}
}
}

Просмотреть файл

@ -0,0 +1,42 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System.Threading.Tasks;
/// <summary>
/// Predicate type for explicit unification.
/// </summary>
internal class UnifyPredicateType : PredicateType
{
public static readonly UnifyPredicateType Regular = new UnifyPredicateType("=");
private UnifyPredicateType(string name)
: base(name, true, 2, 2)
{
}
public override PredicateResolver CreateResolver(CompoundTerm input, Constraint constraint, QueryContext context)
{
return new Resolver(input, constraint, context);
}
private class Resolver : BooleanPredicateResolver
{
public Resolver(CompoundTerm input, Constraint constraint, QueryContext context)
: base(input, constraint, context)
{
}
protected override Task<bool> CheckAsync()
{
Term term1 = this.GetInputArgument(0);
Term term2 = this.GetInputArgument(1);
return Task.FromResult(term1.Unify(term2));
}
}
}
}

Просмотреть файл

@ -0,0 +1,49 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System.Threading.Tasks;
/// <summary>
/// Predicate type for updateobj.
/// </summary>
internal class UpdateObjectPredicateType : PredicateType
{
public static readonly UpdateObjectPredicateType Singleton = new UpdateObjectPredicateType();
private UpdateObjectPredicateType()
: base("updateobj", true, 3, 3)
{
}
public override PredicateResolver CreateResolver(CompoundTerm input, Constraint constraint, QueryContext context)
{
return new Resolver(input, constraint, context);
}
private class Resolver : BooleanPredicateResolver
{
public Resolver(CompoundTerm input, Constraint constraint, QueryContext context)
: base(input, constraint, context)
{
}
protected override Task<bool> CheckAsync()
{
Term term1 = this.GetInputArgument(0);
IWritablePropertyContext context = term1.GetObjectValue() as IWritablePropertyContext;
if (context == null)
{
throw new GuanException("1st argument of updateobj {0} is not writable context", term1);
}
string name = this.GetInputArgumentString(1);
context[name] = this.GetInputArgumentObject(2);
return Task.FromResult(true);
}
}
}
}

360
guan/Guan/Logic/Utility.cs Normal file
Просмотреть файл

@ -0,0 +1,360 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Guan.Logic
{
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Globalization;
using System.IO;
using System.Text;
public static class Utility
{
/// <summary>
/// Retrieve a string configuration entry from application
/// configuration file.
/// </summary>
/// <param name="name">Name of the entry.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns>The value of the entry, defaultValue if the
/// entry is not found.</returns>
public static string GetConfig(string name, string defaultValue = null)
{
string value = Environment.GetEnvironmentVariable(name);
if (value == null)
{
return defaultValue;
}
return value;
}
/// <summary>
/// Format the time into a string.
/// </summary>
/// <param name="time">The time stamp.</param>
/// <returns>The string representation of the time stamp.</returns>
public static string FormatTime(DateTime time)
{
if (time == DateTime.MinValue)
{
return "MIN";
}
if (time == DateTime.MaxValue)
{
return "MAX";
}
return time.ToString("yyyy-M-d HH:mm:ss.fff", CultureInfo.InvariantCulture);
}
public static string FormatTimeWithTicks(DateTime time)
{
long ticks = time.Ticks % TimeSpan.TicksPerMillisecond;
string result = FormatTime(time);
if (ticks > 0 && time != DateTime.MaxValue && time != DateTime.MinValue)
{
result = result + "+" + ticks.ToString();
}
return result;
}
/// <summary>
/// Round down a time stamp to milli-second.
/// </summary>
/// <param name="time">The original time stamp.</param>
/// <returns>The time stamp after round down.</returns>
public static DateTime TimeRounddown(DateTime time)
{
long ticks = time.Ticks;
return new DateTime(ticks - (ticks % TimeSpan.TicksPerMillisecond));
}
/// <summary>
/// Round up a time stamp to milli-second.
/// </summary>
/// <param name="time">The original time stamp.</param>
/// <returns>The time stamp after round up.</returns>
public static DateTime TimeRoundup(DateTime time)
{
long ticks = time.Ticks - 1;
return new DateTime(ticks - (ticks % TimeSpan.TicksPerMillisecond) + TimeSpan.TicksPerMillisecond);
}
public static bool TryParse(string value, out bool result)
{
result = (!string.IsNullOrEmpty(value) && value != "false");
return true;
}
public static bool TryParse(string value, out DateTime result)
{
if (string.Compare(value, "min", true, CultureInfo.InstalledUICulture) == 0)
{
result = DateTime.MinValue;
return true;
}
if (string.Compare(value, "max", true, CultureInfo.InstalledUICulture) == 0)
{
result = DateTime.MaxValue;
return true;
}
return DateTime.TryParse(value, out result);
}
public static bool TryParse(string value, Type type, out object result)
{
if (type == typeof(bool))
{
result = (!string.IsNullOrEmpty(value) && value != "false");
return true;
}
System.ComponentModel.TypeConverter converter = System.ComponentModel.TypeDescriptor.GetConverter(type);
if (converter == null)
{
result = null;
return false;
}
result = converter.ConvertFromString(value);
return true;
}
public static bool TryParse<T>(string value, out T result)
{
object obj;
bool success = TryParse(value, typeof(T), out obj);
result = (success ? (T)obj : default(T));
return success;
}
public static bool TryParse<T>(string value, T defaultValue, out T result)
{
if (string.IsNullOrEmpty(value))
{
result = defaultValue;
return true;
}
return Utility.TryParse<T>(value, out result);
}
public static bool TryConvert<T>(object value, out T result)
{
if (value == null)
{
result = default(T);
return true;
}
if (typeof(T).IsAssignableFrom(value.GetType()))
{
result = (T)value;
return true;
}
return Utility.TryParse(value.ToString(), out result);
}
public static T Convert<T>(object value)
{
T result;
if (!TryConvert(value, out result))
{
ReleaseAssert.Fail("Can't convert {0} to {1}", value, typeof(T));
}
return result;
}
public static T MaxValue<T>()
{
if (typeof(T) == typeof(long))
{
return (T)(object)long.MaxValue;
}
else if (typeof(T) == typeof(ulong))
{
return (T)(object)ulong.MaxValue;
}
else if (typeof(T) == typeof(double))
{
return (T)(object)double.MaxValue;
}
throw new NotSupportedException(typeof(T).ToString());
}
public static T MinValue<T>()
{
if (typeof(T) == typeof(long))
{
return (T)(object)long.MinValue;
}
else if (typeof(T) == typeof(ulong))
{
return (T)(object)ulong.MinValue;
}
else if (typeof(T) == typeof(double))
{
return (T)(object)double.MinValue;
}
throw new NotSupportedException(typeof(T).ToString());
}
public static object CreateInstanceByReflection(string classSpec)
{
Type type = Type.GetType(classSpec);
if (type == null)
{
throw new ArgumentException("Can't find type: " + classSpec);
}
return Activator.CreateInstance(type);
}
public static string CollectionToString<T>(List<T> list)
{
StringBuilder result = new StringBuilder();
foreach (T item in list)
{
_ = result.AppendFormat("{0},", item);
}
if (result.Length > 0)
{
result.Length--;
}
return result.ToString();
}
public static string FormatString(string format, params object[] args)
{
return string.Format(CultureInfo.InvariantCulture, format, args);
}
public static TimeSpan SafeSubtract(DateTime t1, DateTime t2)
{
if (t1 == DateTime.MaxValue)
{
ReleaseAssert.IsTrue(t2 != DateTime.MaxValue);
return TimeSpan.MaxValue;
}
if (t2 == DateTime.MinValue)
{
ReleaseAssert.IsTrue(t1 != DateTime.MinValue);
return TimeSpan.MaxValue;
}
return t1 - t2;
}
public static DateTime SafeSubtract(DateTime time, TimeSpan duration)
{
if (duration == TimeSpan.MaxValue)
{
return DateTime.MinValue;
}
if (time == DateTime.MaxValue || time == DateTime.MinValue)
{
return time;
}
return time - duration;
}
public static TimeSpan SafeSubtract(TimeSpan t1, TimeSpan t2)
{
if (t1 == TimeSpan.MaxValue || t2 == TimeSpan.MinValue)
{
return TimeSpan.MaxValue;
}
return t1 - t2;
}
public static DateTime SafeAdd(DateTime time, TimeSpan duration)
{
if (duration == TimeSpan.MaxValue)
{
return DateTime.MaxValue;
}
if (time == DateTime.MaxValue || time == DateTime.MinValue)
{
return time;
}
return time + duration;
}
public static DateTime Max(DateTime t1, DateTime t2)
{
return (t1 > t2 ? t1 : t2);
}
public static DateTime Min(DateTime t1, DateTime t2)
{
return (t1 < t2 ? t1 : t2);
}
public static TimeSpan Max(TimeSpan t1, TimeSpan t2)
{
return (t1 > t2 ? t1 : t2);
}
public static TimeSpan Min(TimeSpan t1, TimeSpan t2)
{
return (t1 < t2 ? t1 : t2);
}
public static TimeSpan AbsoluteValue(TimeSpan interval)
{
return (interval < TimeSpan.Zero ? -interval : interval);
}
public static bool TryCompare(object arg1, object arg2, out int result)
{
if (arg1 == null)
{
result = (arg2 == null ? 0 : -1);
return true;
}
else if (arg2 == null)
{
result = 1;
return true;
}
if (object.Equals(arg1, arg2))
{
result = 0;
return true;
}
IComparable comparable = arg1 as IComparable;
if (comparable != null)
{
//TODO: consider supporting args with different types (e.g. int vs. long)
result = comparable.CompareTo(arg2);
return true;
}
result = 0;
return false;
}
}
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше