code_builder icon indicating copy to clipboard operation
code_builder copied to clipboard

Add ability to generate if-else blocks

Open filiph opened this issue 6 years ago • 4 comments

A previous version had .asIf() for building statements like

if (something) {
  // ...
}

As far as I know, the only way to do these with current API is to write this manually via Code(...).

filiph avatar Aug 10 '18 04:08 filiph

The problem with builders for statements is there are so many, and it is hard to cover everything.

Happy to accept PRs though for statements you feel add value!

matanlurey avatar Aug 10 '18 05:08 matanlurey

Hmm, good point.

Maybe there a way to just let folks built their own statements (like the one above) in a safer way than right now?

Right now, I do something like this:

final conditionString = condition.accept(_dartEmitter).toString();
final returnStatement = literal(returnValue).returned.statement;
final returnString = returnStatement.accept(_dartEmitter).toString();
return Code('if ($conditionString) { $returnString }');

(_dartEmitter is a private member of a class that does not end up building the final code, so it seems a bit out of place here.)

Would it be better to have something like:

final buf = CodeBuffer()
  ..addString('if (')
  ..addExpression(condition)
  ..addString(') {')
  ..addStatement(literal(returnValue).returned)
  ..addString('}');
return buf.build();

Eh. Not sure if this is any better. What I was driving at is a way not to use DartEmitter until the last step. To keep the expressions and statements in their semantic form (as opposed to a string) as long as possible, and yet have the flexibility to just write stuff directly.

filiph avatar Aug 10 '18 06:08 filiph

Just came across this issue and I was thinking about something like this:

CompountStatementBuilder()
  ..addStatement(refer('myBool').eq(literal('1')).asIf())
  ..addBlock(BlockBuilder()
    ..addStatement(literal(returnValue).returned.statement)
  )
  ..followedBy(CompountStatementBuilder()
    ..addStatement(literalElse)
    ..addBlock(/* .. */));

CompountStatementBuilder()
  ..addStatement(refer('myArray').asFor(refer('item')))
  ..addBlock(/* .. */);

CompountStatementBuilder()
  ..addStatement(literalTry)
  ..addBlock(/* .. */)
  // Maybe use builder callback directly for ease of use?
  ..followedBy((b) => b
    ..addStatement(literalCatch)
    ..addBlock(/* .. */));

Happy to setup a PR.

eredo avatar Sep 20 '18 07:09 eredo

I created a helper method like so:

Block ifStatement(
  Expression conditional,
  Code body, [
  bool singleLine = false,
]) =>
    Block.of([
      Code('if ('),
      conditional.code,
      Code(') {'),
      body,
      Code('}'),
    ]);

I even have one for try catch thats a bit more complicated

Block tryStatement({
  required Code try$,
  List<CatchClause> clauses = const [],
  Catch? catch$,
  Code? finally$,
}) {
  if (clauses.isEmpty && catch$ == null && finally$ == null) {
    throw ArgumentError(
        r'Must provide at least one clause such as in `clauses`, `catch$`, or `finally$`');
  }
  return Block((b) {
    b.statements.add(Code('try {'));
    b.statements.add(try$);
    b.statements.add(Code('}'));

    // on X {}

    // on X catch (e) {}
    // on X catch (e, s) {}

    // catch (e) {}
    // catch (e, s) {}
    for (final clause in clauses) {
      if (clause.on case final on) {
        b.statements.add(Code('on '));
        b.statements.add(on.code);
      }
      if (clause.catch$) {
        b.statements.add(Code('catch (e, s)'));
      }
      b.statements.add(Code('{'));
      b.statements.add(clause.body(refer('e'), refer('s')));
      b.statements.add(Code('}'));
    }

    if (catch$ != null) {
      b.statements.add(Code('catch (e, s) {'));
      b.statements.add(catch$.body(refer('e'), refer('s')));
      b.statements.add(Code('}'));
    }

    if (finally$ != null) {
      b.statements.add(Code('finally {'));
      b.statements.add(finally$);
      b.statements.add(Code('}'));
    }
  });
}

class CatchClause {
  final CatchBodyBuilder body;

  final Reference on;
  final bool catch$;

  CatchClause(
    Code body, {
    required this.on,
  })  : catch$ = false,
        body = ((_, __) => body);

  const CatchClause.catch$(
    this.body, {
    required this.on,
  }) : catch$ = true;
}

class Catch {
  final CatchBodyBuilder body;
  const Catch(this.body);

  static const rethrow$ = Catch(_rethrowBody);
  static Code _rethrowBody(_, __) => refer('rethrow').statement;
}

typedef CatchBodyBuilder = Code Function(Reference error, Reference stackTrace);

(note, i did not implement an option that excludes the stacktrace option) I believe that both statements need to exist in code_builder in some form, not necessarily like this.

TekExplorer avatar Dec 15 '23 05:12 TekExplorer