Skip to content

Instantly share code, notes, and snippets.

@xoposhiy
Last active August 29, 2015 14:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save xoposhiy/bc2a22984817113e0f8d to your computer and use it in GitHub Desktop.
Save xoposhiy/bc2a22984817113e0f8d to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace solution
{
public class Program
{
private static void Main(string[] args)
{
/*
Решением тестового задания считается исходный код консольного приложения на C#,
принимающее в качестве единственного аргумента командной строки путь до директории
с исходными кодами на языке C#,
и выводящее в файл long.txt список самых длинных функций,
а в файл nesting.txt — список функций с самым большим уровнем вложенности операторов.
*/
var functions =
Directory.EnumerateFiles(args[0], "*.cs", SearchOption.AllDirectories)
.SelectMany(GetFunctionInfos)
.ToList();
OutputMetricRatings(functions, "long.txt", m => m.Length);
OutputMetricRatings(functions, "nesting.txt", m => m.MaxNestingLevel);
}
private static IEnumerable<FunctionInfo> GetFunctionInfos(string sourceFile)
{
string source = File.ReadAllText(sourceFile);
var syntaxTree = CSharpSyntaxTree.ParseText(source);
return syntaxTree.GetRoot().DescendantNodes()
.Where(IsFunction)
.Select(node =>
new FunctionInfo
{
Filename = sourceFile,
StartLineNumber = GetLineNumber(syntaxTree, node),
Length = GetLength(node),
MaxNestingLevel = GetNesting(node)
});
}
private static bool IsFunction(SyntaxNode member)
{
/*
Функцией в рамках этой задачи мы называем любой член класса или структуры,
который содержит внутри себя операторы (statements),
за исключением вложенных типов и полей.
*/
return member is MemberDeclarationSyntax
&& !(member is FieldDeclarationSyntax)
&& !(member is NamespaceDeclarationSyntax)
&& !(member is StructDeclarationSyntax)
&& !(member is ClassDeclarationSyntax)
&& !(member is EnumDeclarationSyntax)
&& member.DescendantNodes().Any(c => c is StatementSyntax);
}
private static int GetLineNumber(SyntaxTree syntaxTree, SyntaxNode n)
{
return syntaxTree.GetLineSpan(n.Span).StartLinePosition.Line + 1;
}
private static int GetLength(SyntaxNode member)
{
/*
Длина функции — это количество операторов (statements), которые содержит метод,
не считая блочных операторов (block statement)
*/
return
member.DescendantNodes()
.Count(n => n is StatementSyntax && !(n is BlockSyntax));
}
private static int GetNesting(SyntaxNode member)
{
/*
Уровень вложенности функции — максимальный уровень вложенности среди всех операторов,
содержащихся внутри этой функции.
*/
return member.DescendantNodes().Where(n => n is StatementSyntax)
.Max(s => GetParentsAndSelf(s).Count(IsNestingNode));
}
private static bool IsNestingNode(SyntaxNode node)
{
/*
Если рассмотреть в синтаксическом дереве функции путь от отдельного оператора до корня дерева,
то каждая из следующих синтаксических конструкций добавляет единицу
к уровню вложенности этого оператора:
if, switch, for, foreach, do, while, checked, unchecked, fixed, using, lock, unsafe,
try, catch, finally, определение анонимного метода, определение лямбда выражения.
*/
return node is IfStatementSyntax
|| node is SwitchStatementSyntax
|| node is ForStatementSyntax
|| node is ForEachStatementSyntax
|| node is DoStatementSyntax
|| node is WhileStatementSyntax
|| node is CheckedStatementSyntax //unchecked — это тоже CheckedStatementSyntax
|| node is FixedStatementSyntax
|| node is UsingStatementSyntax
|| node is LockStatementSyntax
|| node is UnsafeStatementSyntax
|| node is TryStatementSyntax // catch и finally — это составные части Try
|| node is AnonymousMethodExpressionSyntax
|| node is SimpleLambdaExpressionSyntax
|| node is ParenthesizedLambdaExpressionSyntax;
}
private static IEnumerable<SyntaxNode> GetParentsAndSelf(SyntaxNode statement)
{
while (statement != null)
{
yield return statement;
statement = statement.Parent;
}
}
private static void OutputMetricRatings(
IEnumerable<FunctionInfo> functions, string filename, Func<FunctionInfo, int> getMetric)
{
/*
Top-100 нужно брать у списка, отсортированного сначала
по измеряемому параметру (длине или уровню вложенности) по убыванию,
при равных значениях — по имени файла по возрастанию,
при прочих равных — по номеру строки по возрастанию.
*/
File.WriteAllLines(
filename,
functions
.OrderByDescending(getMetric)
.ThenBy(function => function.Filename)
.ThenBy(function => function.StartLineNumber)
.Select(function => FormatMetricValue(function, getMetric(function)))
.Take(100));
}
private static string FormatMetricValue(FunctionInfo function, int metricValue)
{
// [value][TAB][filename]:[lineNumber]
return string.Format("{0}\t{1}:{2}",
metricValue,
Path.GetFileName(function.Filename),
function.StartLineNumber);
}
}
public class FunctionInfo
{
public string Filename { get; set; }
public int StartLineNumber { get; set; }
public int Length { get; set; }
public int MaxNestingLevel { get; set; }
}
}
@wizardIlmir
Copy link

Фаридонов Ильмир:

  1. ИЗ ТЗ: Если рассмотреть в синтаксическом дереве функции путь от отдельного оператора до корня дерева, то каждая из следующих синтаксических конструкций добавляет единицу к уровню вложенности этого оператора: if, switch, for, foreach, do, while, checked, unchecked, fixed, using, lock, unsafe, try, catch, finally, определение анонимного метода, определение лямбда выражения

Если рассматривать синтаксическое дерево получившее в результате команды CSharpSyntaxTree.ParseText(source);
то для оператора if-elseif(1) будет уровень вложенности 2, так как второй оператор if будет вложен в else, раз так, тогда справедливо это и для try-catch-finaly(2), потому что в синтаксическом дереве сatch и finaly будет вложен try, как второй if в операторе if-elseif
Код(1)
if(...){ // за if +1
}elseif(...){ // еще if +1
} // итого 2
Код(2)
try{ +1
}catch{}+ 1 (В ТЗ есть Catch)
}finally{} (В ТЗ есть finaly, но уровень все равно будет 2) итого 2

Вы же просто учитываете try без catch и finally, и по почему то обрабатываете второй if в конструкции if-elseif

  1. ParenthesizedLambdaExpressionSyntax является expression lambda. А в ТЗ было слово только о lambda expression. Вот здесь можно почитать что это разные вещи http://msdn.microsoft.com/ru-ru/library/bb397687.aspx

3)ИЗ ТЗ: Заметьте, что вложенность функции f2 — единица, а не двойка. Так получается, потому что тело простого лямбда выражения (тело которого не заключено в фигурные скобки) согласно спецификации — это не statement, а expression, а значит, согласно описанным выше правилам, «b =>!b» не влияет на вычисление уровня вложенности.

А соответственно где вы проверяете что тело Лямбда выражения заключено/не заключено в фигурные скобки?

@iDawer
Copy link

iDawer commented Jun 5, 2014

Опередил на счет лямбды, в коде есть несоответствие с условием задачи: SimpleLambdaExpressionSyntax не нужно считать, потому что это простое лямбда выражение, которое в условии сказано не учитывать.
Ильмир, по 1ому:
if(){}else if(){} вложенность 2, потому что кострукция if внутри кострукции if-else,
а блок try{}catch{}finally{} - 1, потому что это единая конструкция.
по 2 и 3ему:
Если тело лямбды простое (т.е. expression ), то это SimpleLambdaExpressionSyntax, а если с фигурной скобкой (т.е. statement) - ParenthesizedLambdaExpressionSyntax

@wizardIlmir
Copy link

Вы не правы проверьте этот код стандартным инструментом Roslyn Syntax Visualizers входящий в состав Roslyn.

Func<bool, bool> not = b => !b;
Func<bool, bool> not1 = b => {return !b; };
в дереве Roslyn Syntax Visualizers являются классами SimpleLambdaExpressionSyntax что со скобками что без.
а вот (int x, string s) => s.Length > x; это является уже ParenthesizedLambdaExpressionSyntax(это выражения лямбда)

так же если посмотреть на синтаксическое дерево которое получается в Roslyn Syntax Visualizers правильней будет учитывать catch и finally. Потому что в этом дереве так же располагается второй if, в структуре if-else-if как и finally-catch в структуре try-finally-catch и не важно что finally и catch не могут существовать без try, ведь в ТЗ написано про catch и finally, и написано мне кажется об этом не просто так, а с каким то смыслом.

@borovskyav
Copy link

А если просто находить всех предков стейтмента, то даже не придется задумываться о таких вещах.

@xoposhiy
Copy link
Author

xoposhiy commented Jun 7, 2014

  1. В условии было написано лямбда выражения. Кажется, должно быть очевидно, что подразумеваются оба вида и такие: a => a+1 и такие (int a) => a+1. Если это казалось не очевидно, была возможность задать вопрос.
  2. А это не нужно проверять фигурные скобки. Мы же считаем максимум вложенности по всем statement-ам. Этого достаточно. Более того, если вы будете проверять наличие скобочек, то может быть ошибка на таком примере: a => (b => { f(); }) тут есть statement-ы, но чуть глубже.

Про первый пункт надо подумать и перепроверить. Действительно похоже на косяк в условии задачи. Правда тесты были устроены так, что сильно это на результаты повлиять не должно было.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment