From ad5cb6597ba31e15403ee6f78f376ec40da165c9 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Wed, 19 Mar 2025 11:48:40 -0400 Subject: [PATCH] progress on parser and lexer revamp --- base/components/interface.hpp | 3 - base/components/interface.parsing.cpp | 185 +++++++++++++++----------- base/components/interface.untyped.cpp | 2 +- base/components/lexer.cpp | 69 +++++----- base/components/parser.cpp | 6 +- base/components/parser_types.hpp | 1 + 6 files changed, 141 insertions(+), 125 deletions(-) diff --git a/base/components/interface.hpp b/base/components/interface.hpp index 1f97487..d3dcd32 100644 --- a/base/components/interface.hpp +++ b/base/components/interface.hpp @@ -78,9 +78,6 @@ struct Context StringTable StrCache; - // TODO(Ed): This needs to be just handled by a parser context - Array(Token) Lexer_Tokens; - // TODO(Ed): Active parse context vs a parse result need to be separated conceptually ParseContext parser; diff --git a/base/components/interface.parsing.cpp b/base/components/interface.parsing.cpp index 66e3fa9..78d22d7 100644 --- a/base/components/interface.parsing.cpp +++ b/base/components/interface.parsing.cpp @@ -22,6 +22,7 @@ ParseInfo wip_parse_str(LexedInfo lexed, ParseOpts* opts) // TODO(Ed): ParseInfo should be set to the parser context. + ctx->parser = struct_zero(); ctx->parser.tokens = lexed.tokens; ParseStackNode scope = NullScope; @@ -40,17 +41,16 @@ CodeClass parse_class( Str def ) check_parse_args( def ); - TokArray toks = lex( def ); - if ( toks.Arr == nullptr ) - return InvalidCode; + ctx->parser = struct_zero(); - ctx->parser.Tokens = toks; + LexedInfo lexed = lex(ctx, def); + ctx->parser.tokens = lexed.tokens; + if ( ctx->parser.tokens.ptr == nullptr ) + return InvalidCode; ParseStackNode scope = NullScope; parser_push(& ctx->parser, & scope); - CodeClass result = (CodeClass) parse_class_struct( ctx, Tok_Decl_Class, parser_not_inplace_def ); - parser_pop(& ctx->parser); return result; } @@ -62,10 +62,16 @@ CodeConstructor parse_constructor(Str def ) check_parse_args( def ); - TokArray toks = lex( def ); - if ( toks.Arr == nullptr ) + ctx->parser = struct_zero(); + + LexedInfo lexed = lex(ctx, def); + ctx->parser.tokens = lexed.tokens; + if ( ctx->parser.tokens.ptr == nullptr ) return InvalidCode; + ParseStackNode scope = NullScope; + parser_push(& ctx->parser, & scope); + // TODO(Ed): Constructors can have prefix attributes CodeSpecifiers specifiers = NullCode; @@ -92,7 +98,7 @@ CodeConstructor parse_constructor(Str def ) break; default : - log_failure( "Invalid specifier %s for variable\n%S", spec_to_str( spec ), parser_to_strbuilder(ctx->parser, ctx->Allocator_Temp) ); + log_failure( "Invalid specifier %s for variable\n%S", spec_to_str( spec ), parser_to_strbuilder(& ctx->parser, ctx->Allocator_Temp) ); parser_pop(& ctx->parser); return InvalidCode; } @@ -106,14 +112,13 @@ CodeConstructor parse_constructor(Str def ) eat( currtok.Type ); } - if ( NumSpecifiers ) - { + if ( NumSpecifiers ) { specifiers = def_specifiers_arr( NumSpecifiers, specs_found ); // ... } - ctx->parser.Tokens = toks; CodeConstructor result = parser_parse_constructor(ctx, specifiers); + parser_pop(& ctx->parser); return result; } @@ -124,17 +129,16 @@ CodeDefine parse_define( Str def ) check_parse_args( def ); - TokArray toks = lex( def ); - if ( toks.Arr == nullptr ) - return InvalidCode; + ctx->parser = struct_zero(); - ctx->parser.Tokens = toks; + LexedInfo lexed = lex(ctx, def); + ctx->parser.tokens = lexed.tokens; + if ( ctx->parser.tokens.ptr == nullptr ) + return InvalidCode; ParseStackNode scope = NullScope; parser_push(& ctx->parser, & scope); - CodeDefine result = parser_parse_define(ctx); - parser_pop(& ctx->parser); return result; } @@ -146,14 +150,16 @@ CodeDestructor parse_destructor( Str def ) check_parse_args( def ); - TokArray toks = lex( def ); - if ( toks.Arr == nullptr ) + ctx->parser = struct_zero(); + + LexedInfo lexed = lex(ctx, def); + ctx->parser.tokens = lexed.tokens; + if ( ctx->parser.tokens.ptr == nullptr ) return InvalidCode; // TODO(Ed): Destructors can have prefix attributes // TODO(Ed): Destructors can have virtual - ctx->parser.Tokens = toks; CodeDestructor result = parser_parse_destructor(ctx, NullCode); return result; } @@ -165,17 +171,14 @@ CodeEnum parse_enum( Str def ) check_parse_args( def ); - ParseStackNode scope = NullScope; - parser_push(& ctx->parser, & scope); + ctx->parser = struct_zero(); - TokArray toks = lex( def ); - if ( toks.Arr == nullptr ) - { - parser_pop(& ctx->parser); + LexedInfo lexed = lex(ctx, def); + ctx->parser.tokens = lexed.tokens; + if ( ctx->parser.tokens.ptr == nullptr ) { return InvalidCode; } - ctx->parser.Tokens = toks; return parser_parse_enum(ctx, parser_not_inplace_def); } @@ -186,11 +189,13 @@ CodeBody parse_export_body( Str def ) check_parse_args( def ); - TokArray toks = lex( def ); - if ( toks.Arr == nullptr ) + ctx->parser = struct_zero(); + + LexedInfo lexed = lex(ctx, def); + ctx->parser.tokens = lexed.tokens; + if ( ctx->parser.tokens.ptr == nullptr ) return InvalidCode; - ctx->parser.Tokens = toks; return parser_parse_export_body(ctx); } @@ -201,11 +206,13 @@ CodeExtern parse_extern_link( Str def ) check_parse_args( def ); - TokArray toks = lex( def ); - if ( toks.Arr == nullptr ) + ctx->parser = struct_zero(); + + LexedInfo lexed = lex(ctx, def); + ctx->parser.tokens = lexed.tokens; + if ( ctx->parser.tokens.ptr == nullptr ) return InvalidCode; - ctx->parser.Tokens = toks; return parser_parse_extern_link(ctx); } @@ -216,11 +223,13 @@ CodeFriend parse_friend( Str def ) check_parse_args( def ); - TokArray toks = lex( def ); - if ( toks.Arr == nullptr ) + ctx->parser = struct_zero(); + + LexedInfo lexed = lex(ctx, def); + ctx->parser.tokens = lexed.tokens; + if ( ctx->parser.tokens.ptr == nullptr ) return InvalidCode; - ctx->parser.Tokens = toks; return parser_parse_friend(ctx); } @@ -231,11 +240,13 @@ CodeFn parse_function( Str def ) check_parse_args( def ); - TokArray toks = lex( def ); - if ( toks.Arr == nullptr ) + ctx->parser = struct_zero(); + + LexedInfo lexed = lex(ctx, def); + ctx->parser.tokens = lexed.tokens; + if ( ctx->parser.tokens.ptr == nullptr ) return InvalidCode; - ctx->parser.Tokens = toks; return (CodeFn) parser_parse_function(ctx); } @@ -246,17 +257,16 @@ CodeBody parse_global_body( Str def ) check_parse_args( def ); - TokArray toks = lex( def ); - if ( toks.Arr == nullptr ) - return InvalidCode; + ctx->parser = struct_zero(); - ctx->parser.Tokens = toks; + LexedInfo lexed = lex(ctx, def); + ctx->parser.tokens = lexed.tokens; + if ( ctx->parser.tokens.ptr == nullptr ) + return InvalidCode; ParseStackNode scope = NullScope; parser_push(& ctx->parser, & scope); - CodeBody result = parse_global_nspace(ctx, CT_Global_Body ); - parser_pop(& ctx->parser); return result; } @@ -268,11 +278,13 @@ CodeNS parse_namespace( Str def ) check_parse_args( def ); - TokArray toks = lex( def ); - if ( toks.Arr == nullptr ) + ctx->parser = struct_zero(); + + LexedInfo lexed = lex(ctx, def); + ctx->parser.tokens = lexed.tokens; + if ( ctx->parser.tokens.ptr == nullptr ) return InvalidCode; - ctx->parser.Tokens = toks; return parser_parse_namespace(ctx); } @@ -283,11 +295,13 @@ CodeOperator parse_operator( Str def ) check_parse_args( def ); - TokArray toks = lex( def ); - if ( toks.Arr == nullptr ) + ctx->parser = struct_zero(); + + LexedInfo lexed = lex(ctx, def); + ctx->parser.tokens = lexed.tokens; + if ( ctx->parser.tokens.ptr == nullptr ) return InvalidCode; - ctx->parser.Tokens = toks; return (CodeOperator) parser_parse_operator(ctx); } @@ -298,11 +312,13 @@ CodeOpCast parse_operator_cast( Str def ) check_parse_args( def ); - TokArray toks = lex( def ); - if ( toks.Arr == nullptr ) + ctx->parser = struct_zero(); + + LexedInfo lexed = lex(ctx, def); + ctx->parser.tokens = lexed.tokens; + if ( ctx->parser.tokens.ptr == nullptr ) return InvalidCode; - ctx->parser.Tokens = toks; return parser_parse_operator_cast(ctx, NullCode); } @@ -313,17 +329,16 @@ CodeStruct parse_struct( Str def ) check_parse_args( def ); - TokArray toks = lex( def ); - if ( toks.Arr == nullptr ) - return InvalidCode; + ctx->parser = struct_zero(); - ctx->parser.Tokens = toks; + LexedInfo lexed = lex(ctx, def); + ctx->parser.tokens = lexed.tokens; + if ( ctx->parser.tokens.ptr == nullptr ) + return InvalidCode; ParseStackNode scope = NullScope; parser_push(& ctx->parser, & scope); - CodeStruct result = (CodeStruct) parse_class_struct( ctx, Tok_Decl_Struct, parser_not_inplace_def ); - parser_pop(& ctx->parser); return result; } @@ -335,11 +350,13 @@ CodeTemplate parse_template( Str def ) check_parse_args( def ); - TokArray toks = lex( def ); - if ( toks.Arr == nullptr ) + ctx->parser = struct_zero(); + + LexedInfo lexed = lex(ctx, def); + ctx->parser.tokens = lexed.tokens; + if ( ctx->parser.tokens.ptr == nullptr ) return InvalidCode; - ctx->parser.Tokens = toks; return parser_parse_template(ctx); } @@ -350,11 +367,13 @@ CodeTypename parse_type( Str def ) check_parse_args( def ); - TokArray toks = lex( def ); - if ( toks.Arr == nullptr ) + ctx->parser = struct_zero(); + + LexedInfo lexed = lex(ctx, def); + ctx->parser.tokens = lexed.tokens; + if ( ctx->parser.tokens.ptr == nullptr ) return InvalidCode; - ctx->parser.Tokens = toks; return parser_parse_type( ctx, parser_not_from_template, nullptr); } @@ -365,11 +384,13 @@ CodeTypedef parse_typedef( Str def ) check_parse_args( def ); - TokArray toks = lex( def ); - if ( toks.Arr == nullptr ) + ctx->parser = struct_zero(); + + LexedInfo lexed = lex(ctx, def); + ctx->parser.tokens = lexed.tokens; + if ( ctx->parser.tokens.ptr == nullptr ) return InvalidCode; - ctx->parser.Tokens = toks; return parser_parse_typedef(ctx); } @@ -380,11 +401,13 @@ CodeUnion parse_union( Str def ) check_parse_args( def ); - TokArray toks = lex( def ); - if ( toks.Arr == nullptr ) + ctx->parser = struct_zero(); + + LexedInfo lexed = lex(ctx, def); + ctx->parser.tokens = lexed.tokens; + if ( ctx->parser.tokens.ptr == nullptr ) return InvalidCode; - ctx->parser.Tokens = toks; return parser_parse_union(ctx, parser_not_inplace_def); } @@ -395,11 +418,13 @@ CodeUsing parse_using( Str def ) check_parse_args( def ); - TokArray toks = lex( def ); - if ( toks.Arr == nullptr ) + ctx->parser = struct_zero(); + + LexedInfo lexed = lex(ctx, def); + ctx->parser.tokens = lexed.tokens; + if ( ctx->parser.tokens.ptr == nullptr ) return InvalidCode; - ctx->parser.Tokens = toks; return parser_parse_using(ctx); } @@ -410,11 +435,13 @@ CodeVar parse_variable( Str def ) check_parse_args( def ); - TokArray toks = lex( def ); - if ( toks.Arr == nullptr ) + ctx->parser = struct_zero(); + + LexedInfo lexed = lex(ctx, def); + ctx->parser.tokens = lexed.tokens; + if ( ctx->parser.tokens.ptr == nullptr ) return InvalidCode; - ctx->parser.Tokens = toks; return parser_parse_variable(ctx); } diff --git a/base/components/interface.untyped.cpp b/base/components/interface.untyped.cpp index 3e05369..14cfc4e 100644 --- a/base/components/interface.untyped.cpp +++ b/base/components/interface.untyped.cpp @@ -179,7 +179,7 @@ Code untyped_token_fmt( s32 num_tokens, char const* fmt, ... ) Code untyped_toks( TokenSlice tokens ) { - if ( tokens.Num == 0 ) { + if ( tokens.num == 0 ) { log_failure( "untyped_toks: empty token slice" ); return InvalidCode; } diff --git a/base/components/lexer.cpp b/base/components/lexer.cpp index 9643361..f6cf4b7 100644 --- a/base/components/lexer.cpp +++ b/base/components/lexer.cpp @@ -137,7 +137,7 @@ s32 lex_preprocessor_define( LexContext* ctx ) ); // GEN_DEBUG_TRAP(); } - array_append( _ctx->Lexer_Tokens, name ); + array_append( ctx->tokens, name ); if ( ctx->left && (* ctx->scanner) == '(' ) { @@ -152,7 +152,7 @@ s32 lex_preprocessor_define( LexContext* ctx ) } Token opening_paren = { { ctx->scanner, 1 }, Tok_Paren_Open, ctx->line, ctx->column, TF_Preprocess }; - array_append( _ctx->Lexer_Tokens, opening_paren ); + array_append( ctx->tokens, opening_paren ); move_forward(); Token last_parameter = {}; @@ -168,7 +168,7 @@ s32 lex_preprocessor_define( LexContext* ctx ) move_forward(); move_forward(); - array_append(_ctx->Lexer_Tokens, parameter); + array_append(ctx->tokens, parameter); skip_whitespace(); last_parameter = parameter; @@ -202,7 +202,7 @@ s32 lex_preprocessor_define( LexContext* ctx ) move_forward(); parameter.Text.Len++; } - array_append(_ctx->Lexer_Tokens, parameter); + array_append(ctx->tokens, parameter); skip_whitespace(); last_parameter = parameter; } @@ -229,7 +229,7 @@ s32 lex_preprocessor_define( LexContext* ctx ) return Lex_ReturnNull; } Token comma = { { ctx->scanner, 1 }, Tok_Comma, ctx->line, ctx->column, TF_Preprocess }; - array_append(_ctx->Lexer_Tokens, comma); + array_append(ctx->tokens, comma); move_forward(); } @@ -243,7 +243,7 @@ s32 lex_preprocessor_define( LexContext* ctx ) return Lex_ReturnNull; } Token closing_paren = { { ctx->scanner, 1 }, Tok_Paren_Close, ctx->line, ctx->column, TF_Preprocess }; - array_append(_ctx->Lexer_Tokens, closing_paren); + array_append(ctx->tokens, closing_paren); move_forward(); } else if ( registered_macro && macro_is_functional( * registered_macro) ) { @@ -268,7 +268,7 @@ s32 lex_preprocessor_directive( LexContext* ctx ) { char const* hash = ctx->scanner; Token hash_tok = { { hash, 1 }, Tok_Preprocess_Hash, ctx->line, ctx->column, TF_Preprocess }; - array_append( _ctx->Lexer_Tokens, hash_tok ); + array_append(ctx->tokens, hash_tok); move_forward(); skip_whitespace(); @@ -344,14 +344,14 @@ s32 lex_preprocessor_directive( LexContext* ctx ) ctx->token.Text.Len = ctx->token.Text.Len + ctx->token.Text.Ptr - hash; ctx->token.Text.Ptr = hash; - array_append( _ctx->Lexer_Tokens, ctx->token ); + array_append(ctx->tokens, ctx->token); return Lex_Continue; // Skip found token, its all handled here. } if ( ctx->token.Type == Tok_Preprocess_Else || ctx->token.Type == Tok_Preprocess_EndIf ) { ctx->token.Flags |= TF_Preprocess_Cond; - array_append( _ctx->Lexer_Tokens, ctx->token ); + array_append(ctx->tokens, ctx->token); end_line(); return Lex_Continue; } @@ -360,7 +360,7 @@ s32 lex_preprocessor_directive( LexContext* ctx ) ctx->token.Flags |= TF_Preprocess_Cond; } - array_append( _ctx->Lexer_Tokens, ctx->token ); + array_append(ctx->tokens, ctx->token); skip_whitespace(); @@ -411,7 +411,7 @@ s32 lex_preprocessor_directive( LexContext* ctx ) move_forward(); } - array_append( _ctx->Lexer_Tokens, preprocess_content ); + array_append(ctx->tokens, preprocess_content); return Lex_Continue; // Skip found token, its all handled here. } @@ -475,14 +475,14 @@ s32 lex_preprocessor_directive( LexContext* ctx ) preprocess_content.Text.Len++; } - array_append( _ctx->Lexer_Tokens, preprocess_content ); + array_append(ctx->tokens, preprocess_content); return Lex_Continue; // Skip found token, its all handled here. } void lex_found_token( LexContext* ctx ) { if ( ctx->token.Type != Tok_Invalid ) { - array_append( _ctx->Lexer_Tokens, ctx->token ); + array_append(ctx->tokens, ctx->token); return; } @@ -508,7 +508,7 @@ void lex_found_token( LexContext* ctx ) } ctx->token.Type = type; - array_append( _ctx->Lexer_Tokens, ctx->token ); + array_append(ctx->tokens, ctx->token); return; } if ( ( type <= Tok_Star && type >= Tok_Spec_Alignas) @@ -517,13 +517,13 @@ void lex_found_token( LexContext* ctx ) { ctx->token.Type = type; ctx->token.Flags |= TF_Specifier; - array_append( _ctx->Lexer_Tokens, ctx->token ); + array_append(ctx->tokens, ctx->token); return; } if ( type != Tok_Invalid ) { ctx->token.Type = type; - array_append( _ctx->Lexer_Tokens, ctx->token ); + array_append(ctx->tokens, ctx->token); return; } @@ -561,7 +561,7 @@ void lex_found_token( LexContext* ctx ) ctx->token.Type = Tok_Identifier; } - array_append( _ctx->Lexer_Tokens, ctx->token ); + array_append(ctx->tokens, ctx->token); } // TODO(Ed): We should dynamically allocate the lexer's array in Allocator_DyanmicContainers. @@ -579,8 +579,7 @@ LexedInfo lex(Context* lib_ctx, Str content) c.scanner = content.Ptr; c.line = 1; c.column = 1; - - Array(Token) tokens = array_init_reserve(Token, lib_ctx->Allocator_DyanmicContainers, lib_ctx->InitSize_LexerTokens ); + c.tokens = array_init_reserve(Token, lib_ctx->Allocator_DyanmicContainers, lib_ctx->InitSize_LexerTokens ); // TODO(Ed): Re-implement to new constraints: // 1. Ability to continue on error @@ -592,18 +591,10 @@ LexedInfo lex(Context* lib_ctx, Str content) return info; } - array_clear(_ctx->Lexer_Tokens); - b32 preprocess_args = true; while (c.left ) { - #if 0 - if (Tokens.num()) { - log_fmt("\nLastTok: %SB", Tokens.back().to_strbuilder()); - } - #endif - c.token = struct_init(Token) { { c.scanner, 0 }, Tok_Invalid, c.line, c.column, TF_Null }; bool is_define = false; @@ -623,7 +614,7 @@ LexedInfo lex(Context* lib_ctx, Str content) c.token.Type = Tok_NewLine; c.token.Text.Len++; - array_append( _ctx->Lexer_Tokens, c.token ); + array_append(c.tokens, c.token); continue; } } @@ -662,7 +653,7 @@ LexedInfo lex(Context* lib_ctx, Str content) c.token.Text.Len++; move_forward(); - array_append( _ctx->Lexer_Tokens, c.token ); + array_append(c.tokens, c.token); } } continue; @@ -1118,7 +1109,7 @@ LexedInfo lex(Context* lib_ctx, Str content) move_forward(); c.token.Text.Len++; } - array_append( _ctx->Lexer_Tokens, c.token ); + array_append(c.tokens, c.token); continue; } else if ( (* ctx->scanner) == '*' ) @@ -1154,7 +1145,7 @@ LexedInfo lex(Context* lib_ctx, Str content) move_forward(); c.token.Text.Len++; } - array_append( _ctx->Lexer_Tokens, c.token ); + array_append(c.tokens, c.token); // end_line(); continue; } @@ -1242,14 +1233,14 @@ LexedInfo lex(Context* lib_ctx, Str content) } else { - s32 start = max( 0, array_num(_ctx->Lexer_Tokens) - 100 ); + s32 start = max( 0, array_num(c.tokens) - 100 ); log_fmt("\n%d\n", start); - for ( s32 idx = start; idx < array_num(_ctx->Lexer_Tokens); idx++ ) + for ( s32 idx = start; idx < array_num(c.tokens); idx++ ) { log_fmt( "Token %d Type: %s : %.*s\n" , idx - , toktype_to_str( _ctx->Lexer_Tokens[ idx ].Type ).Ptr - , _ctx->Lexer_Tokens[ idx ].Text.Len, _ctx->Lexer_Tokens[ idx ].Text.Ptr + , toktype_to_str( c.tokens[ idx ].Type ).Ptr + , c.tokens[ idx ].Text.Len, c.tokens[ idx ].Text.Ptr ); } @@ -1265,7 +1256,7 @@ LexedInfo lex(Context* lib_ctx, Str content) FoundToken: { lex_found_token( ctx ); - TokType last_type = array_back(_ctx->Lexer_Tokens)->Type; + TokType last_type = array_back(c.tokens)->Type; if ( last_type == Tok_Preprocess_Macro_Stmt || last_type == Tok_Preprocess_Macro_Expr ) { Token thanks_c = { { c.scanner, 0 }, Tok_Invalid, c.line, c.column, TF_Null }; @@ -1280,21 +1271,21 @@ LexedInfo lex(Context* lib_ctx, Str content) c.token.Text.Len++; move_forward(); - array_append( _ctx->Lexer_Tokens, c.token ); + array_append(c.tokens, c.token); continue; } } } } - if ( array_num(_ctx->Lexer_Tokens) == 0 ) { + if ( array_num(c.tokens) == 0 ) { log_failure( "Failed to lex any tokens" ); return info; } info.messages = c.messages; info.text = content; - info.tokens = struct_init(TokenSlice) { tokens, scast(s32, array_num(tokens)) }; + info.tokens = struct_init(TokenSlice) { pcast(Token*, c.tokens), scast(s32, array_num(c.tokens)) }; return info; } diff --git a/base/components/parser.cpp b/base/components/parser.cpp index c850e26..b2eb17c 100644 --- a/base/components/parser.cpp +++ b/base/components/parser.cpp @@ -126,14 +126,14 @@ bool lex__eat(Context* ctx, ParseContext* parser, TokType type) internal void parser_init(Context* ctx) { - ctx->Lexer_Tokens = array_init_reserve(Token, ctx->Allocator_DyanmicContainers, ctx->InitSize_LexerTokens ); + // ctx->Lexer_Tokens = array_init_reserve(Token, ctx->Allocator_DyanmicContainers, ctx->InitSize_LexerTokens ); } internal void parser_deinit(Context* ctx) { - Array(Token) null_array = { nullptr }; - ctx->Lexer_Tokens = null_array; + // Array(Token) null_array = { nullptr }; + // ctx->Lexer_Tokens = null_array; } #pragma region Helper Macros diff --git a/base/components/parser_types.hpp b/base/components/parser_types.hpp index 6e12c9f..bf61b11 100644 --- a/base/components/parser_types.hpp +++ b/base/components/parser_types.hpp @@ -135,6 +135,7 @@ struct LexContext s32 line; s32 column; Token token; + Array(Token) tokens; }; struct LexedInfo