Skip to content

Commit b4fe90d

Browse files
committed
new Console implementation, hacky support for macro context
1 parent 496295e commit b4fe90d

2 files changed

Lines changed: 322 additions & 34 deletions

File tree

src/om/Console.hx

Lines changed: 248 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,275 @@
11
package om;
22

3-
// TODO ....
3+
import om.Time;
4+
#if macro
5+
import haxe.macro.Context;
6+
import haxe.macro.Expr;
7+
import haxe.macro.PositionTools;
8+
#end
9+
10+
using om.FloatTools;
11+
12+
enum abstract Level(Int) to Int {
13+
/** Highest severity level, for critical errors. */
14+
var error;
415

5-
#if (js&&!nodejs)
6-
typedef Console = js.html.ConsoleInstance;
16+
/** For warnings that indicate potential problems. */
17+
var warn;
718

8-
#elseif (sys||nodejs)
19+
/** For general informational messages. */
20+
var info;
21+
22+
/** Lowest severity level, for detailed debugging information. */
23+
var debug;
24+
25+
// var trace;
26+
@:to public function toString():String
27+
return switch this {
28+
case Level.error: "error";
29+
case Level.warn: "warn";
30+
case Level.info: "info";
31+
case Level.debug: "debug";
32+
case _: "unknown";
33+
}
34+
}
935

10-
/// node is missing some features (clear,debug,..) there using this
36+
/**
37+
A cross-platform console logging utility.
1138
12-
//@:noDoc
13-
@defines('no_console','Remove calls to om.Console')
14-
#if no_console @exclude #end
39+
Provides leveled logging (`error`, `warn`, `info`, `debug`), grouping, and timing functions.
40+
The output is color-coded and includes timestamps and location info on sys targets.
41+
42+
All logging calls are macros, which means they are completely removed from the code
43+
when compiling with the `-D om_noconsole` flag, resulting in zero performance overhead.
44+
45+
The verbosity can be controlled at runtime by setting the `Console.level` static variable.
46+
**/
47+
@defines('om_noconsole', 'Remove all calls to om.Console')
1548
class Console {
49+
/**
50+
The current logging level.
51+
Only messages with a level less than or equal to this value will be displayed.
52+
For example, if `level` is `Level.warn`, only `error` and `warn` messages will be shown.
53+
Defaults to `Level.debug`.
54+
**/
55+
public static var level:Level = Level.debug;
1656

17-
static var instance : om.Console;
57+
#if sys
58+
/**
59+
ANSI color codes for different log levels.
60+
**/
61+
public static var theme = [[41], [45], [44], [30, 100]];
1862

19-
public static function getInstance() : om.Console {
20-
return (instance == null) ? instance = new om.Console() : instance;
21-
}
63+
/**
64+
Toggles ANSI color output. Defaults to `true`.
65+
**/
66+
public static var colorMode = true;
2267

23-
public static inline function print( s : String ) {
24-
Sys.print( s );
25-
}
68+
/**
69+
The number of spaces to use for each indentation level in groups.
70+
Defaults to `2`.
71+
**/
72+
public static var groupIndentation = 2;
2673

27-
public static inline function println( s : String ) {
28-
Sys.println( s );
29-
}
74+
/**
75+
The string to use for one level of indentation in groups.
76+
Defaults to `" "`.
77+
**/
78+
public static var groupIndentationString = " ";
3079

31-
function new() {}
80+
static var groupDepth = 0;
81+
static var timers:Map<String, Float> = new Map<String, Float>();
3282

33-
public inline function debug( data : String ) {
34-
//TODO
83+
/**
84+
Wraps a string with ANSI escape codes for coloring.
85+
@param str The string to colorize.
86+
@param codes An array of ANSI codes.
87+
**/
88+
public static function ansify(str:String, codes:Array<Int>):String
89+
return !colorMode || codes == null || codes.length == 0 ? str : '\x1B[${codes.join(";")}m$str\x1B[0m';
90+
91+
/**
92+
Returns the indentation string for the current group depth.
93+
**/
94+
public static inline function groupIndent():String
95+
return om.StringTools.repeat(groupIndentationString, groupIndentation * groupDepth);
96+
#end
97+
98+
macro public static function error(args:Array<Expr>)
99+
return _log(Level.error, args);
100+
101+
macro public static function warn(args:Array<Expr>)
102+
return _log(Level.warn, args);
103+
104+
macro public static function info(args:Array<Expr>)
105+
return _log(Level.info, args);
106+
107+
macro public static function debug(args:Array<Expr>)
108+
return _log(Level.debug, args);
109+
110+
/**
111+
Creates a new inline group in the console log, indenting all following output.
112+
Call `groupEnd()` to exit the group.
113+
@param label The label for the group.
114+
**/
115+
public static inline function group(label:String) {
116+
#if nodejs
117+
js.Node.console.group(label);
118+
#elseif js
119+
js.Browser.console.group(label);
120+
#elseif sys
121+
Sys.println(groupIndent() + label);
122+
groupDepth++;
123+
#end
124+
}
125+
126+
/**
127+
Exits the current inline group and decreases the indentation level.
128+
**/
129+
public static inline function groupEnd() {
130+
#if nodejs
131+
js.Node.console.groupEnd();
132+
#elseif js
133+
js.Browser.console.groupEnd();
134+
#elseif sys
135+
if (groupDepth > 0)
136+
groupDepth--;
137+
#end
35138
}
36139

37-
public inline function log( data : String ) {
38-
println( data );
140+
/**
141+
Starts a timer with the specified label.
142+
Call `timeEnd()` with the same label to stop the timer and log the elapsed time.
143+
@param label The label for the timer.
144+
**/
145+
public static inline function time(label:String) {
146+
#if nodejs
147+
js.Node.console.time(label);
148+
#elseif js
149+
js.Browser.console.time(label);
150+
#elseif sys
151+
timers.set(label, Time.now());
152+
#end
39153
}
40154

41-
public inline function info( data : String ) {
42-
println( data );
155+
/**
156+
Stops the timer with the specified label and logs the elapsed time in milliseconds.
157+
@param label The label of the timer to stop.
158+
**/
159+
public static inline function timeEnd(label:String) {
160+
#if nodejs
161+
js.Node.console.timeEnd(label);
162+
#elseif js
163+
js.Browser.console.timeEnd(label);
164+
#elseif sys
165+
if (timers.exists(label)) {
166+
final start = timers.get(label);
167+
timers.remove(label);
168+
final duration = (Time.now() - start) * 1000;
169+
Sys.println(label + ': ${duration.precisionString(2)}ms');
170+
} else {
171+
Sys.println('Timer \'${label}\' does not exist');
172+
}
173+
#end
43174
}
44175

45-
public inline function warn( data : String ) {
46-
println( data );
176+
/**
177+
Clears the console.
178+
**/
179+
public static inline function clear() {
180+
#if nodejs
181+
js.Node.console.clear();
182+
#elseif js
183+
js.Browser.console.clear();
184+
#elseif sys
185+
Sys.print('\033c');
186+
#end
47187
}
48188

49-
public inline function error( data : String ) {
50-
println( data );
189+
#if macro
190+
static function _log(level:Level, args:Array<Expr>):Expr {
191+
if (Context.defined('om_noconsole')) {
192+
return macro null;
193+
}
194+
var logExpr:Expr;
195+
if (Compiler.isSysTarget()) {
196+
var metaAnsi = [90, 40];
197+
var timeExpr:Expr = macro om.Console.ansify(om.FloatTools.precisionString(om.Time.now(), 3) + " ", $v{metaAnsi});
198+
var loc = PositionTools.toLocation(Context.currentPos());
199+
var inf = loc.file + ":" + loc.range.start.line;
200+
if (loc.range.start.line != loc.range.end.line)
201+
inf += '-${loc.range.end.line}';
202+
inf += ':${loc.range.start.character}-${loc.range.end.character}';
203+
var infAnsi = ansify(" " + inf + " ", metaAnsi);
204+
var levelStr = ansify(" " + getLevelInfoString(level) + " ", theme[level]);
205+
var msg = _buildMessageExpr(args);
206+
logExpr = macro Sys.println(Console.groupIndent() + $timeExpr + $v{levelStr + infAnsi} + " " + $msg);
207+
} else {
208+
final target = om.Compiler.target();
209+
logExpr = switch target {
210+
case js:
211+
var consoleExpr = Context.defined("nodejs") ? macro js.Node.console : macro js.Browser.console;
212+
switch level {
213+
case Level.error: macro $consoleExpr.error($a{args});
214+
case Level.warn: macro $consoleExpr.warn($a{args});
215+
case Level.info: macro $consoleExpr.info($a{args});
216+
case Level.debug: macro $consoleExpr.debug($a{args});
217+
case _: macro $consoleExpr.log($a{args});
218+
}
219+
case _:
220+
macro null;
221+
};
222+
}
223+
if (logExpr == null || logExpr.expr == EConst(CIdent("null"))) {
224+
return macro null;
225+
}
226+
final messageLevel:Int = level;
227+
return macro if ($v{messageLevel} <= untyped om.Console.level) {
228+
${logExpr}
229+
};
51230
}
52231

53-
public inline function clear() {
54-
print( '\033c' );
232+
static function _buildMessageExpr(args:Array<Expr>):ExprOf<String> {
233+
if (args.length == 0)
234+
return macro "";
235+
var parts:Array<Expr> = [];
236+
var currentString = new StringBuf();
237+
function flush() {
238+
if (currentString.length > 0) {
239+
parts.push(macro $v{currentString.toString()});
240+
currentString = new StringBuf();
241+
}
242+
}
243+
for (i in 0...args.length) {
244+
if (i > 0)
245+
currentString.add(', ');
246+
switch args[i].expr {
247+
case EConst(CString(s)):
248+
currentString.add(s);
249+
case EConst(CInt(i)):
250+
currentString.add(Std.string(i));
251+
case EConst(CFloat(f)):
252+
currentString.add(Std.string(f));
253+
default:
254+
flush();
255+
parts.push(macro Std.string(${args[i]}));
256+
}
257+
}
258+
flush();
259+
if (parts.length == 0)
260+
return macro "";
261+
if (parts.length == 1)
262+
return parts[0];
263+
var result = parts[0];
264+
for (i in 1...parts.length) {
265+
result = macro $result + ${parts[i]};
266+
}
267+
return result;
55268
}
56269

57-
// TODO ...
58-
270+
public static inline function getLevelInfoString(level:Level):String {
271+
return level.toString().toUpperCase();
272+
}
273+
#end
59274
}
60275

61-
#end

src/om/ConsoleMacro.hx

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package om;
2+
3+
#if macro
4+
import haxe.macro.Context;
5+
import om.Console;
6+
7+
using om.FloatTools;
8+
9+
/**
10+
Macro-only `Console`.
11+
**/
12+
@defines('om_noconsole_macro', 'Remove all calls to om.Console in macro context')
13+
class ConsoleMacro {
14+
public static var level:Level = Level.debug;
15+
16+
public static inline function error(a:Dynamic, ...data:Dynamic)
17+
println(Level.error, a, data);
18+
19+
public static inline function warn(a:Dynamic, ...data:Dynamic)
20+
println(Level.warn, a, data);
21+
22+
public static inline function info(a:Dynamic, ...data:Dynamic)
23+
println(Level.info, a, data);
24+
25+
public static inline function debug(a:Dynamic, ...data:Dynamic)
26+
println(Level.debug, a, data);
27+
28+
public static inline function log(level:Level, a:Dynamic, ...data:Dynamic)
29+
println(level, a, data);
30+
31+
public static inline function group(label:String)
32+
om.Console.group(label);
33+
34+
public static inline function groupEnd()
35+
om.Console.groupEnd();
36+
37+
public static inline function clear()
38+
om.Console.clear();
39+
40+
public static inline function time(label:String)
41+
om.Console.time(label);
42+
43+
public static inline function timeEnd(label:String)
44+
om.Console.timeEnd(label);
45+
46+
static function println(level:Level, a:Dynamic, data:Array<Dynamic>) {
47+
if (Context.defined("om_noconsole_macro"))
48+
return;
49+
final out = format(level, [a].concat(data)) + "\n";
50+
switch level {
51+
case Level.error:
52+
Sys.stderr().writeString(out);
53+
Sys.stderr().flush();
54+
case _:
55+
Sys.stdout().writeString(out);
56+
Sys.stdout().flush();
57+
}
58+
}
59+
60+
static function format(level:om.Console.Level, data:Array<Dynamic>):String {
61+
var metaAnsi = [90, 40];
62+
final out = new StringBuf();
63+
// out.add(Console2.ansify(" MACRO ", [47]));
64+
out.add(Console.groupIndent());
65+
out.add(Console.ansify(Time.now().precisionString(3) + ' ', metaAnsi));
66+
out.add(Console.ansify(" " + Console.getLevelInfoString(level) + " ", Console.theme[level]));
67+
out.add(" ");
68+
var pos = Context.getPosInfos(Context.currentPos());
69+
out.add(Console.ansify(pos.file + ":" + pos.min + "-" + pos.max + ": ", metaAnsi));
70+
out.add(data.join(", "));
71+
return out.toString();
72+
}
73+
}
74+
#end

0 commit comments

Comments
 (0)