107 Commits
Author SHA1 Message Date
samsonium 0fc6406e97 fix: missing freeing and type (in CIF prepare) 2026-11-08 18:46:21 +03:00
samsonium d09b4f2761 wip: working on externs loading
- Feat: finish FFI CIF prepare
2026-11-08 18:40:44 +03:00
samsonium 2488e9fb07 fix: add loaded symbols count increment 2026-11-08 17:26:46 +03:00
samsonium 899c22eece ref: move extern fns prepare into main 2026-11-08 17:26:32 +03:00
samsonium 1c57525604 wip: working on externs loading
- WIP: add extern objects initializing/freeing functions
- Feat: add extern objects loading in VM initializer
2026-11-08 17:12:31 +03:00
samsonium 5de995a3c2 feat: add debug print for CALL_EXTERN opcode 2026-11-06 14:06:18 +03:00
samsonium 7759136aa8 feat: add linking tests 2026-11-06 14:06:07 +03:00
samsonium d38365f10c feat: add extern functions call support 2026-11-06 14:06:01 +03:00
samsonium 317b846108 feat+fix: add extern fns reading; fix VM reading issues 2026-11-06 13:32:41 +03:00
samsonium 376e7bd1b4 fix: add strings terminators 2026-11-06 12:02:37 +03:00
samsonium d95ff88b66 wip: working on extern in VM
- Feat: add usage of vm reader instead of old way in vm.c
- Ref: update debug opcodes printing
2026-11-05 23:23:38 +03:00
samsonium 1db42c1e8e wip: working on extern in VM
- Feat: add writer externs writing fns
- Feat: add VM reader functions
2026-11-05 21:48:40 +03:00
samsonium 84572edb0d feat: implement extern libs data writing 2026-11-05 18:40:02 +03:00
samsonium d91ba3d63a fix: add missing RM_ENVTABLE opcode 2026-11-05 18:39:48 +03:00
samsonium de0e88f2e9 wip: begin writer impl for externs 2026-11-05 04:01:23 +03:00
samsonium 66f31ab865 fix: fix parser/generator issues 2026-11-05 03:46:16 +03:00
samsonium bd8272abc7 feat: finish generator 2026-11-05 00:04:12 +03:00
samsonium 181458ecda feat: add prepared error/null ok returns from gen_* functions 2026-11-04 22:52:40 +03:00
samsonium aae183c0c0 ref: add type/classname result for each gen_* function 2026-11-04 22:25:25 +03:00
samsonium b278e8bb74 wip: working on generator
- Feat: add break handler
- Feat: add class handler
- Feat: add format string handler
- Feat: add loop handler
2026-11-04 20:04:33 +03:00
samsonium 7fe3fe3ff3 wip: working on generator
- Feat: add typedefs' branches
2026-11-04 19:33:17 +03:00
samsonium 731b662cfc wip: working on generator
- Feat: add extern type resolving to type slot
- Feat: add extern block handling
- Feat: add extern function handling
2026-11-04 18:30:02 +03:00
samsonium f2b88e448e feat: add .clangd 2026-11-04 17:16:52 +03:00
samsonium c5c522cde8 feat: add generators' main entry 2026-11-04 17:10:21 +03:00
samsonium c0bd0d95b2 fix: add classname value for structs and handles 2026-11-04 17:04:09 +03:00
samsonium 26decf2626 wip: working on extern
- Feat: add new generator scope to replace walker
- Feat: add generators' context functions
- Feat: add generators' ops array functions
- Feat: add generators' utility functions
2026-11-04 17:03:48 +03:00
samsonium 358ede4c8c wip: working on extern
- Feat: stabilize extern header
- Feat: add externs to walker
- Feat: add extern node walking logic
2026-11-02 20:31:43 +03:00
samsonium 6a035a71eb wip: working on extern
- Ref: move extern header into common scope
- Feat: add extern symbols descriptors
- Feat: add `is_out` flag in declaration node
2026-11-02 19:24:09 +03:00
samsonium 8975d2fece feat: add out type arg info storing in declaration node 2026-11-02 18:55:40 +03:00
samsonium 3dd70ac293 wip: working on extern
Add TODOs for new nodes
2026-11-01 21:24:23 +03:00
samsonium 4d9c4ddd21 wip: working on walker (externs)
Begin working on extern walking (libs metadata collet + opcode gen)
2026-10-31 18:45:33 +03:00
samsonium a4aa220cfa feat: implement extern block analyzer 2026-10-31 01:58:10 +03:00
samsonium 5c96f96220 wip: working on dynamic linking
- Feat: add analyzers for:
  - external functions
  - reference
  - structures
  - type definitions
- Feat: add new nodes to debug print (ast)
2026-10-30 20:32:14 +03:00
samsonium f769313169 feat: implement extern block reading and parsing 2026-10-29 22:13:28 +03:00
samsonium 0448be0d38 ref: update linking example 2026-10-29 20:24:06 +03:00
samsonium 01dabd2bdd ref: remove build-release dependency from install make target 2026-10-29 20:23:54 +03:00
samsonium 6f54286724 doc: update project building steps 2026-10-29 18:18:50 +03:00
samsonium fe5fcd7b96 feat: add compiler installation step
- Ref: add `-debug` suffix to debug targets
- Ref: remove `-release` suffix from release targets
- Feat: add `make install` command
- Feat: add cmake variables:
  - `FZL_TESTS` for enable testing
  - `FZL_SKIP_C` to not build the compiler
  - `FZL_SKIP_VM` to not build the vm
2026-10-29 18:18:35 +03:00
samsonium a885746fdd feat: add object escaping on assing to variable in outer env 2026-10-29 17:46:56 +03:00
samsonium 2cf96d06c6 fix: fmtstring printing 2026-10-29 17:46:20 +03:00
samsonium bbdf91ee02 fix: cutted space in string literal begin 2026-10-29 17:46:08 +03:00
samsonium 27374a96eb fix: AST levels print 2026-10-29 17:45:39 +03:00
samsonium dcab2a8875 feat: add runtime speed comparison between fz, c and cpython 2026-10-29 05:21:31 +03:00
samsonium 7b804b8809 feat: optimize runtime (2x+ faster) 2026-10-29 05:21:00 +03:00
samsonium eeca5e653d feat: add benchmar Make target 2026-10-29 03:25:48 +03:00
samsonium f4c97795fa feat: optimize VM using dispatch table 2026-10-29 02:59:38 +03:00
samsonium e2de23ed57 fix: suppress libffi cmake warnings 2026-10-29 00:26:33 +03:00
samsonium 8c90d54782 ref: split parser into separate files 2026-10-29 00:08:44 +03:00
samsonium 62b0030e4a ref: move analyzer fn defs into compiler/iternals 2026-10-28 23:24:17 +03:00
samsonium bbdaa4a9b9 ref: split reader into separate files 2026-10-28 23:12:46 +03:00
samsonium 7c04d8b7ec ref: remove unused loop checker 2026-10-28 23:12:06 +03:00
samsonium 567bf49d27 wip: working on dynamic linking
- Feat: add example for wrapping dynamically linked library
- Feat: add new tokens (import, extern, type, handle, struct, ref, out)
2026-10-28 21:43:56 +03:00
samsonium d93c315359 fix: clear first AST node if got error inside parse_program 2026-10-28 19:37:17 +03:00
samsonium e11089c90f feat: implement format strings 2026-10-28 17:57:18 +03:00
samsonium ea28c470d2 wip: begin dynamic library loading dev 2026-10-28 15:20:58 +03:00
samsonium 70bf63e2ea feat: add libffi cmake dependency 2026-10-28 00:31:09 +03:00
samsonium b24cefee96 Merge pull request 'Impl analyzer' (#5) from impl-analyzer into v1.1
Reviewed-on: #5
2026-10-27 12:43:08 +00:00
samsonium 6498c73960 ref: move linked list example into tests folder 2026-10-27 15:42:02 +03:00
samsonium dc80de66e4 fix: review problems 2026-10-27 15:40:25 +03:00
samsonium c61322718d fix: some analysis stage problems 2026-10-27 14:12:09 +03:00
samsonium 411d291a2c feat: add VM protection for classes w/o fields 2026-10-27 12:55:46 +03:00
samsonium aa7a06bd22 feat: finish implementing analyzer 2026-10-27 12:50:58 +03:00
samsonium a263b6206f wip: working on analyzer
- Feat: add assigns analysis
- Fix: change node check condition in binary math analysis
- Feat: add unary and binary conditions analysis
- Fix: add type passtrough in declaration analysis
- Feat: add unary math analysis
2026-10-27 12:21:49 +03:00
samsonium 25991c6f15 wip: working on analyzer
- Feat: add binary math analysis
- Fix: check for function context in block analysis
- Feat: add 'break' analysis
- Fix: few fixes in call analysis
- Feat: add class analysis
- Feat: add construct analysis
- Feat: add declaration analysis
- Feat: add field analysis
- Feat: add function analysis
- Feat: add loop analysis
- Ref: updated utilities and context functions
2026-10-27 05:18:36 +03:00
samsonium d7630dbb6a wip: working on analyzer
- Feat: implement return analyzer
- Fix: add block return check if inside function
2026-10-26 22:20:09 +03:00
samsonium a70ad011f3 wip: working on analyzer
- Feat: add `nest_ctx` and `fin_ctx` fns for working with context and
environment
- Feat: add block analyzer
- Feat: add conditional statement analyzer
2026-10-26 21:34:54 +03:00
samsonium 62ae05b4cd wip: working on analyzer
- Feat: now symbols table store node on PUT
- Feat: call analyzer with callee resolving and arguments validate
- Feat: identifier analyzer
- Feat: analyzer utils (types compat, tok-to-type, type-to-string)
2026-10-26 20:47:24 +03:00
samsonium 4904f40037 wip: working on analyzer 2026-10-25 19:24:02 +03:00
samsonium 562b1b2e0a Merge pull request 'Implement loops' (#4) from feat-loops into v1.1
- Fix: memory leaks in parser
- Feat: `for` and `while` loops
- Feat: `break` for exit from loops
- Doc: new examples in `exl` directory
- Feat: optimized tests calling CMakeLists
2026-10-25 02:01:47 +00:00
samsonium 6d1f1ce66e fix: memory leak in postfix parsing function 2026-10-25 04:57:01 +03:00
samsonium c03fddf47f fix: fixes in compiler 2026-10-25 04:52:18 +03:00
samsonium 3bb60071bb feat: add infinite loop control args 2026-10-25 04:52:07 +03:00
samsonium 04fdd58e4f fix: fix typo in examples 2026-10-25 04:51:37 +03:00
samsonium e00e609589 ref: rename 'hard-1' test to 'linked-list' 2026-10-25 01:56:20 +03:00
samsonium a37b8cfe6e ref: remove test examples 2026-10-25 01:56:07 +03:00
samsonium c4eb148d17 ref: temporary comment out infinite loop check 2026-10-25 01:54:49 +03:00
samsonium dbca3129ab feat: add "while-not-null" test 2026-10-25 01:54:07 +03:00
samsonium e3878047d7 upd: rename neg1 tests (for negative in runtime) to negr 2026-10-25 01:53:46 +03:00
samsonium 7cfb8efef4 feat: implement break 2026-10-22 21:30:32 +03:00
samsonium b5ae959d14 feat: add test name format for runtime-only error 2026-10-22 21:30:17 +03:00
samsonium 0e319308a7 wip: working on infinite loop handling in vm 2026-10-22 20:25:48 +03:00
samsonium a20743a835 feat: implement simple infinite loop checker 2026-10-22 20:24:48 +03:00
samsonium 5dde75523d wip: working on loop infinity checker 2026-10-22 18:25:07 +03:00
samsonium cc5bbd61f2 feat: implement loops 2026-10-22 18:24:28 +03:00
samsonium 0188991f4d feat: add new tests for loops 2026-10-22 03:39:04 +03:00
samsonium 73a2a50a0b feat: add error message for variable redefenition attempt 2026-10-21 04:42:58 +03:00
samsonium 4a366cb0ac fix: missing ';' in variables example 2026-10-21 04:42:41 +03:00
samsonium c7cce94119 fix: add AST freeing on errors; add checks for parser, walker and writer are not NULL 2026-10-21 04:31:57 +03:00
samsonium 673a28fbc7 fix: check for EOF on block parsing (closing brace) 2026-10-21 04:31:02 +03:00
samsonium 160c6c6488 fix: incorrect test file directory prefix 2026-10-21 04:30:16 +03:00
samsonium c8f3cd61f4 ref: add build-release make target for building c/vm without sanitizer libs 2026-10-21 04:01:23 +03:00
samsonium 138d503bce feat: add examples with descriptions 2026-10-21 04:00:48 +03:00
samsonium e49ebba346 feat: add new tests 2026-10-21 04:00:03 +03:00
samsonium 80f07dc7c6 doc: fix typo in philosophy doc name 2026-10-21 02:29:27 +03:00
samsonium dbe387f916 Merge pull request 'Docs' (#3) from docs into master
Reviewed-on: #3
2026-10-19 18:57:11 +00:00
samsonium c43b5dfef0 feat: translate doc to en, add index files 2026-10-19 21:34:12 +03:00
samsonium 195b82408b doc: add 'basics-and-types' doc 2026-10-19 21:23:44 +03:00
samsonium 5e58ea1796 Merge branch 'master' into docs 2026-10-19 20:11:53 +03:00
samsonium d22b4009b0 doc: add stacks doc 2026-10-19 17:24:40 +03:00
samsonium a8d42f86c4 doc: add operators and types_system docs 2026-10-19 16:49:07 +03:00
samsonium a3319cd3be doc: add builtins and errors_print documents 2026-10-19 14:23:45 +03:00
samsonium a6d21a65cb doc: add full EBDF grammar 2026-10-19 12:02:10 +03:00
samsonium 2d006d5e2b ref: remove old docs variant 2026-10-19 12:01:59 +03:00
samsonium 81d07aa6e5 doc: add book,internal,reference,builtins sections 2026-10-19 11:11:43 +03:00
samsonium 306be6b82d feat: add first two book topics 2026-10-19 04:05:00 +03:00
samsonium c2532ae11f upd: update usage hint in vm and compiler 2026-10-19 04:04:32 +03:00
samsonium 345d711570 feat: add ru docs 2026-10-19 03:15:35 +03:00
246 changed files with 13534 additions and 3949 deletions
+4
View File
@@ -0,0 +1,4 @@
Style:
AngledHeaders: [".*"]
QuotedHeaders: []
+1
View File
@@ -5,6 +5,7 @@ skip is
skip -rfunction fzl_mk* skip -rfunction fzl_mk*
skip -rfunction fzl_free* skip -rfunction fzl_free*
skip -rfunction fzl_symt* skip -rfunction fzl_symt*
skip -rfunction fzl_linked*
skip add_const skip add_const
skip push_op skip push_op
skip push_word skip push_word
+4 -1
View File
@@ -3,15 +3,18 @@
# Dirs # Dirs
!/docs !/docs
!/exl !/exl
!/benchmarks
!/include !/include
!/scripts
!/src !/src
!/tests
# Files # Files
!/.clang-format !/.clang-format
!/.clangd
!/.gdbinit !/.gdbinit
!/.gitignore !/.gitignore
!/CMakeLists.txt !/CMakeLists.txt
!/Makefile !/Makefile
!/README.md !/README.md
!/README.ru.md !/README.ru.md
+201 -115
View File
@@ -8,135 +8,221 @@ add_compile_options(-Wall -Wextra -Werror=implicit-function-declaration)
if (CMAKE_BUILD_TYPE STREQUAL "Debug") if (CMAKE_BUILD_TYPE STREQUAL "Debug")
add_compile_options(-fsanitize=address,undefined -g) add_compile_options(-fsanitize=address,undefined -g)
add_link_options(-fsanitize=address,undefined) add_link_options(-fsanitize=address,undefined)
set(FZL_TESTS TRUE)
endif() endif()
include_directories(include) include_directories(include)
include(FetchContent)
# --- libffi ---
set(PREV_SUPPRESS_DEV ${CMAKE_SUPPRESS_DEVELOPER_WARNINGS})
set(CMAKE_SUPPRESS_DEVELOPER_WARNINGS TRUE CACHE INTERNAL "")
FetchContent_Declare(ffi
GIT_REPOSITORY "https://git.samsonium.dev/samsonium/libffi.git"
GIT_TAG "main"
)
FetchContent_MakeAvailable(ffi)
set(CMAKE_SUPPRESS_DEVELOPER_WARNINGS ${PREV_SUPPRESS_DEV} CACHE INTERNAL "")
# --------------
# Compiler # Compiler
add_executable(fzc if (NOT FZL_SKIP_C)
src/compiler/fzl_token.c add_executable(fzc
src/compiler/fzl_reader.c src/compiler/utils/fzl_const_pool.c
src/compiler/fzl_parser.c src/compiler/utils/fzl_symtable.c
src/compiler/fzl_debug.c src/compiler/utils/fzl_linked.c
src/compiler/fzl_walker.c src/compiler/utils/fzl_options.c
src/compiler/fzl_writer.c src/compiler/utils/fzl_ctable.c
src/compiler/utils/fzl_const_pool.c src/compiler/analyzer/utility.c
src/compiler/utils/fzl_symtable.c src/compiler/analyzer/context.c
src/compiler/utils/fzl_linked.c src/compiler/analyzer/an_assign.c
src/compiler/utils/fzl_options.c src/compiler/analyzer/an_binmath.c
src/compiler/utils/fzl_ctable.c src/compiler/analyzer/an_block.c
src/compiler/main.c) src/compiler/analyzer/an_break.c
src/compiler/analyzer/an_call.c
src/compiler/analyzer/an_class.c
src/compiler/analyzer/an_common.c
src/compiler/analyzer/an_condition.c
src/compiler/analyzer/an_conditional.c
src/compiler/analyzer/an_construct.c
src/compiler/analyzer/an_declaration.c
src/compiler/analyzer/an_extern.c
src/compiler/analyzer/an_externfn.c
src/compiler/analyzer/an_field.c
src/compiler/analyzer/an_function.c
src/compiler/analyzer/an_identifier.c
src/compiler/analyzer/an_loop.c
src/compiler/analyzer/an_ref.c
src/compiler/analyzer/an_return.c
src/compiler/analyzer/an_strfmt.c
src/compiler/analyzer/an_struct.c
src/compiler/analyzer/an_typedef.c
src/compiler/analyzer/an_unarycond.c
src/compiler/analyzer/an_unarymath.c
src/compiler/generator/context.c
src/compiler/generator/op.c
src/compiler/generator/utility.c
src/compiler/generator/gen_alias.c
src/compiler/generator/gen_assign.c
src/compiler/generator/gen_block.c
src/compiler/generator/gen_break.c
src/compiler/generator/gen_call.c
src/compiler/generator/gen_class.c
src/compiler/generator/gen_common.c
src/compiler/generator/gen_compare.c
src/compiler/generator/gen_condition.c
src/compiler/generator/gen_construct.c
src/compiler/generator/gen_declaration.c
src/compiler/generator/gen_extern.c
src/compiler/generator/gen_externfn.c
src/compiler/generator/gen_field.c
src/compiler/generator/gen_fmtstr.c
src/compiler/generator/gen_fn.c
src/compiler/generator/gen_handle.c
src/compiler/generator/gen_identifier.c
src/compiler/generator/gen_literal.c
src/compiler/generator/gen_logical.c
src/compiler/generator/gen_loop.c
src/compiler/generator/gen_math.c
src/compiler/generator/gen_reference.c
src/compiler/generator/gen_return.c
src/compiler/generator/gen_struct.c
src/compiler/generator/gen_typedef.c
src/compiler/generator/gen_unary_logical.c
src/compiler/generator/gen_unary_math.c
src/compiler/parser/utility.c
src/compiler/parser/pa_and.c
src/compiler/parser/pa_assignment.c
src/compiler/parser/pa_block.c
src/compiler/parser/pa_break.c
src/compiler/parser/pa_class.c
src/compiler/parser/pa_cond.c
src/compiler/parser/pa_expression.c
src/compiler/parser/pa_extern.c
src/compiler/parser/pa_externfn.c
src/compiler/parser/pa_factor.c
src/compiler/parser/pa_for.c
src/compiler/parser/pa_function.c
src/compiler/parser/pa_if.c
src/compiler/parser/pa_loop.c
src/compiler/parser/pa_new.c
src/compiler/parser/pa_or.c
src/compiler/parser/pa_postfix.c
src/compiler/parser/pa_power.c
src/compiler/parser/pa_primary.c
src/compiler/parser/pa_program.c
src/compiler/parser/pa_ref.c
src/compiler/parser/pa_return.c
src/compiler/parser/pa_statement.c
src/compiler/parser/pa_struct.c
src/compiler/parser/pa_term.c
src/compiler/parser/pa_typedef.c
src/compiler/parser/pa_unary.c
src/compiler/parser/pa_while.c
src/compiler/reader/rd_fmtstr.c
src/compiler/reader/rd_name.c
src/compiler/reader/rd_number.c
src/compiler/reader/rd_string_literal.c
src/compiler/reader/utility.c
src/compiler/writer/wr_consts.c
src/compiler/writer/wr_extern_fns.c
src/compiler/writer/wr_extern_libs.c
src/compiler/writer/wr_extern_types.c
src/compiler/writer/wr_ops.c
src/compiler/fzl_analyzer.c
src/compiler/fzl_debug.c
src/compiler/fzl_gen.c
src/compiler/fzl_parser.c
src/compiler/fzl_reader.c
src/compiler/fzl_token.c
src/compiler/fzl_writer.c
src/compiler/main.c)
endif ()
# Virtual machine # Virtual machine
add_executable(fz if (NOT FZL_SKIP_VM)
src/vm/fzl_vm.c add_executable(fz
src/vm/fzl_vm_env.c src/vm/handlers/group_and_or.c
src/vm/fzl_vm_debug.c src/vm/handlers/group_cmp.c
src/vm/main.c) src/vm/handlers/group_ctx.c
src/vm/handlers/group_flow.c
src/vm/handlers/group_fn.c
src/vm/handlers/group_math.c
src/vm/handlers/group_not.c
src/vm/handlers/group_obj.c
src/vm/handlers/group_objcmp.c
src/vm/handlers/group_stack.c
src/vm/handlers/group_str.c
src/vm/handlers/group_strcmp.c
src/vm/handlers/master.c
src/vm/reader/utility.c
src/vm/reader/vm_rd_consts.c
src/vm/reader/vm_rd_extern_fns.c
src/vm/reader/vm_rd_extern_libs.c
src/vm/reader/vm_rd_extern_types.c
src/vm/reader/vm_rd_header.c
src/vm/reader/vm_rd_ops.c
src/vm/reader/vm_rd_sections_sz.c
src/vm/fzl_vm.c
src/vm/fzl_vm_env.c
src/vm/fzl_vm_extern.c
src/vm/fzl_vm_reader.c
src/vm/fzl_vm_debug.c
src/vm/main.c)
target_link_libraries(fz PRIVATE ffi_static dl)
endif ()
# #
# ========== Compilation tests ========== # ========== Installtion ===========
# #
enable_testing() if (NOT FZL_SKIP_C)
install (TARGETS fzc RUNTIME DESTINATION bin)
endif ()
add_test( if (NOT FZL_SKIP_VM)
NAME compile_hw install (TARGETS fz RUNTIME DESTINATION bin)
COMMAND fzc -o "${CMAKE_CURRENT_BINARY_DIR}/hw.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/exl/hw.fzs" endif ()
)
add_test(
NAME compile_print_empty
COMMAND fzc -o "${CMAKE_CURRENT_BINARY_DIR}/print_empty.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/exl/print_empty.fzs"
)
add_test(
NAME compile_problematic_hw
COMMAND fzc -o "${CMAKE_CURRENT_BINARY_DIR}/problematic_hw.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/exl/problematic_hw.fzs"
)
add_test(
NAME compile_base_math
COMMAND fzc -o "${CMAKE_CURRENT_BINARY_DIR}/base_math.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/exl/base_math.fzs"
)
add_test(
NAME compile_variables
COMMAND fzc -o "${CMAKE_CURRENT_BINARY_DIR}/variables.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/exl/variables.fzs"
)
add_test(
NAME compile_conditions
COMMAND fzc -o "${CMAKE_CURRENT_BINARY_DIR}/conditions.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/exl/conditions.fzs"
)
add_test(
NAME compile_fn
COMMAND fzc -o "${CMAKE_CURRENT_BINARY_DIR}/fn.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/exl/fn.fzs"
)
add_test(
NAME compile_comment
COMMAND fzc -o "${CMAKE_CURRENT_BINARY_DIR}/comment.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/exl/comment.fzs"
)
add_test(
NAME compile_class
COMMAND fzc -o "${CMAKE_CURRENT_BINARY_DIR}/class.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/exl/class.fzs"
)
add_test(
NAME compile_raii
COMMAND fzc -o "${CMAKE_CURRENT_BINARY_DIR}/raii.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/exl/raii.fzs"
)
add_test(
NAME compile_borrow
COMMAND fzc -o "${CMAKE_CURRENT_BINARY_DIR}/borrow.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/exl/borrow.fzs"
)
add_test(
NAME compile_hard_1
COMMAND fzc -o "${CMAKE_CURRENT_BINARY_DIR}/hard-1.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/exl/hard-1.fzs"
)
set_tests_properties(compile_problematic_hw PROPERTIES WILL_FAIL TRUE)
# #
# ========== Runtime tests ========== # ========== Tests ==========
# #
add_test( if (FZL_TESTS)
NAME runtime_hw enable_testing()
COMMAND fz "${CMAKE_CURRENT_BINARY_DIR}/hw.fzb"
) file(GLOB TEST_LIBS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "tests/libs/*.c")
add_test( foreach (TEST_LIB IN LISTS TEST_LIBS)
NAME runtime_print_empty get_filename_component(LIB_NAME "${TEST_LIB}" NAME_WE)
COMMAND fz "${CMAKE_CURRENT_BINARY_DIR}/print_empty.fzb" add_library("${LIB_NAME}" ${TEST_LIB})
) endforeach ()
add_test(
NAME runtime_base_math file(GLOB TEST_PROGS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "tests/programs/*.fzs")
COMMAND fz "${CMAKE_CURRENT_BINARY_DIR}/base_math.fzb" foreach (TEST_FILE IN LISTS TEST_PROGS)
) get_filename_component(TEST_NAME "${TEST_FILE}" NAME_WE)
add_test(
NAME runtime_variables add_test(
COMMAND fz "${CMAKE_CURRENT_BINARY_DIR}/variables.fzb" NAME "progs_compile_${TEST_NAME}"
) COMMAND fzc -o "${CMAKE_CURRENT_BINARY_DIR}/p_${TEST_NAME}.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/${TEST_FILE}"
add_test( )
NAME runtime_conditions add_test(
COMMAND fz "${CMAKE_CURRENT_BINARY_DIR}/conditions.fzb" NAME "progs_runtime_${TEST_NAME}"
) COMMAND fz "${CMAKE_CURRENT_BINARY_DIR}/p_${TEST_NAME}.fzb"
add_test( )
NAME runtime_fn
COMMAND fz "${CMAKE_CURRENT_BINARY_DIR}/fn.fzb" if (TEST_NAME MATCHES "neg_")
) set_tests_properties("progs_compile_${TEST_NAME}" PROPERTIES WILL_FAIL TRUE)
add_test( set_tests_properties("progs_runtime_${TEST_NAME}" PROPERTIES WILL_FAIL TRUE)
NAME runtime_comment endif()
COMMAND fz "${CMAKE_CURRENT_BINARY_DIR}/comment.fzb"
) if (TEST_NAME MATCHES "negr_")
add_test( set_tests_properties("progs_runtime_${TEST_NAME}" PROPERTIES WILL_FAIL TRUE)
NAME runtime_class endif ()
COMMAND fz "${CMAKE_CURRENT_BINARY_DIR}/class.fzb" endforeach ()
) endif ()
add_test(
NAME runtime_raii
COMMAND fz "${CMAKE_CURRENT_BINARY_DIR}/raii.fzb"
)
add_test(
NAME runtime_borrow
COMMAND fz "${CMAKE_CURRENT_BINARY_DIR}/borrow.fzb"
)
add_test(
NAME runtime_hard_1
COMMAND fz "${CMAKE_CURRENT_BINARY_DIR}/hard-1.fzb"
)
+48 -7
View File
@@ -1,17 +1,58 @@
.PHONY: configure build debug-c debug-vm test .PHONY: build debug-c debug-vm test bench
configure: BENCH_FILE ?= benchmarks/loop.fzs
RUNS ?= 7
WARMUP ?= 2
MAX_ITER ?= 2000000
current_time = python3 -c "import time; print(time.time())"
build-debug:
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
build: configure
cmake --build build cmake --build build
debug-c: build build:
gdb --args ./build/fzc ${FILE} cmake -S . -B build -DCMAKE_BUILD_TYPE=Release $(COMPARING)
cmake --build build $(COMPARING)
debug-vm: build install:
cmake --install build
debug-c: build-debug
gdb --args ./build/fzc -v ${FILE}
debug-vm: build-debug
gdb --args ./build/fz ${FILE} gdb --args ./build/fz ${FILE}
test-debug: build-debug
ctest --output-on-failure --test-dir build
test: build test: build
ctest --output-on-failure --test-dir build ctest --output-on-failure --test-dir build
bench: COMPARING=&>/dev/null
bench: build
@./build/fzc -o build/benchmark.fzb $(BENCH_FILE)
@python3 scripts/benchmark.py --runs $(RUNS) --warmup $(WARMUP) \
--max-iter $(MAX_ITER) --vm ./build/fz --program build/benchmark.fzb
compare: COMPARING=&>/dev/null
compare: build
@echo "=== Million sums benchmark ==="
@./build/fzc -o build/bench.fzb $(BENCH_FILE) 1>/dev/null 2>/dev/null
@ST=$$($(current_time)); \
./build/fz --allow-infinite build/bench.fzb 1>/dev/null 2>/dev/null; \
ET=$$($(current_time)); \
python3 -c "delta = ($$ET - $$ST) * 1000; print(f'FZLang : {delta:.5f} ms')"
@ST=$$($(current_time)); \
./benchmarks/loop-c 1>/dev/null 2>/dev/null; \
ET=$$($(current_time)); \
python3 -c "delta = ($$ET - $$ST) * 1000; print(f'C : {delta:.5f} ms')"
@ST=$$($(current_time)); \
python3 ./benchmarks/loop.py 1>/dev/null 2>/dev/null; \
ET=$$($(current_time)); \
python3 -c "delta = ($$ET - $$ST) * 1000; print(f'CPython: {delta:.5f} ms')"
+2 -10
View File
@@ -1,6 +1,6 @@
# FromZero / Language # FromZero / Language
[README in Russian](./README.ru.md) [README in Russian](./README.ru.md) · [Documentation](./docs/en/index.md)
**FromZero/Language** is a package consisting of a JIT compiler and a virtual machine for the FZL language. **FromZero/Language** is a package consisting of a JIT compiler and a virtual machine for the FZL language.
@@ -24,18 +24,10 @@ At the moment, the language has the following capabilities:
Working examples of code can be found in `exl/` directory. Working examples of code can be found in `exl/` directory.
## File extensions
Standard file extensions:
- `.fzs` - source code
- `.fzb` - compiled binary file
## Building ## Building
1. Clone the repository: `git clone https://git.samsonium.dev/samsonium/fz-lang.git` 1. Clone the repository: `git clone https://git.samsonium.dev/samsonium/fz-lang.git`
2. Build: `make build` 2. Build: `make build-release`
After this, a "build" directory will appear in the project root. It will contain two executable files: After this, a "build" directory will appear in the project root. It will contain two executable files:
+19 -14
View File
@@ -1,6 +1,6 @@
# FromZero / Language # FromZero / Language
[README in English](./README.md) [README in English](./README.md) · [Документация](./docs/ru/index.md)
**FromZero/Language** - пакет из JIT-компилятора и виртуальной машины для языка FZL. **FromZero/Language** - пакет из JIT-компилятора и виртуальной машины для языка FZL.
@@ -24,23 +24,11 @@
Примеры кода лежат в директории `exl/`. Примеры кода лежат в директории `exl/`.
## Расширения файлов
Стандартными для языка являются два расширения файлов:
- `.fzs` - исходный код
- `.fzb` - скомпилированный бинарный файл
## Инструкции по сборке ## Инструкции по сборке
1. Склонировать проект: `git clone https://git.samsonium.dev/samsonium/fz-lang.git` 1. Склонировать проект: `git clone https://git.samsonium.dev/samsonium/fz-lang.git`
2. Собрать проект: `make build` 2. Собрать проект: `make build`
3. Установить проект: `sudo make install`
После этого в корне появится директория **"build"**. В ней будут лежать два скомпилированных бинарных файла:
- `fzc` - компилятор
- `fz` - виртуальная машина
Оба бинарных файла предоставляют справку по использованию при запуске с флагом `--help` (или `-h`). Оба бинарных файла предоставляют справку по использованию при запуске с флагом `--help` (или `-h`).
@@ -59,3 +47,20 @@ $ make debug-c FILE=exl/hw.fzs
Для проверки новых фич, нужно использовать make-таргет `test`, а тесты писать в `CMakeLists.txt`. Для проверки новых фич, нужно использовать make-таргет `test`, а тесты писать в `CMakeLists.txt`.
### Бенчмаркинг
`make bench` собирает Release-версию, компилирует `benchmarks/loop.fzs` один раз и
измеряет время запуска программы в виртуальной машине. По умолчанию выполняются
2 прогрева и 7 замеров; выводятся медиана, среднее, минимум и максимум. Компиляция
исходника в измерения не входит.
Параметры можно переопределить:
```shell
make bench RUNS=15 WARMUP=3
make bench BENCH_FILE=path/to/program.fzs MAX_ITER=2000000
```
Измеряется полное время запуска процесса `fz` (включая загрузку программы), а не
только цикл интерпретации. Для сопоставимых результатов запускайте бенчмарк на
одной машине и не меняйте нагрузку между прогонами.
BIN
View File
Binary file not shown.
+9
View File
@@ -0,0 +1,9 @@
// Integer loop workload for measuring VM execution without I/O.
uint i = 0;
uint sum = 0;
for (i = 0; i < 1000000; i = i + 1)
sum = sum + i;
printline(sum);
+4
View File
@@ -0,0 +1,4 @@
s = 0
for i in range(1_000_000):
s += i
print(s)
+34
View File
@@ -0,0 +1,34 @@
# Introduction
Welcome to this short book on the basics of FZLang, and thank you for giving this programming language a try.
First, let's talk about what FZLang is and how this book is organized.
## What is FZLang?
FZLang is a programming language with its own toolchain, which includes a compiler and a virtual machine. The compiler checks and compiles source code into a portable file, and the virtual machine runs programs written in FZLang.
This project originally began as a way to learn about building compilers. I (Samsonium) wanted to explore a few questions:
- Why is there a meaningful difference between a compiler and an interpreter?
- How does a program called a compiler understand what I want it to do?
- How does execution of a compiled program work? And how does the process of translating from human-readable code to machine instructions happen?
I've been programming for more than ten years, but had never taken the time to find out!
So this project became a place to experiment and learn more about the foundations of the tools programmers use every day. Along the way, the language began to take shape and develop a philosophy of its own.
Today, I would describe that philosophy as: “Be as convenient as a tool, while always trying to remain predictable.” I imagine every language community wants something similar for its language, but this principle has helped me make choices at important points in the project's development.
### How FZLang handles memory
The question first came up when I introduced stacks to the virtual machine: “A garbage collector, or something else?” Learning how garbage collection works would certainly have been interesting. However, having chosen Java as my first language, I was familiar with its “weight” and runtime behavior. Of course, its garbage collector deserves some of the credit for that.
So instead of a GC, I decided to explore RAII. This led to a simple model for how objects move through a program: when an object is passed as an argument, it is “borrowed” into a nested environment; when returned, it is moved into the parent environment. This approach avoids a GC and, most importantly, gives objects a deterministic lifetime—and therefore more predictable memory use, as far as possible.
In short, FZLang uses scope-based RAII.
---
Next: [Getting started](./01_getting-started.md)
+44
View File
@@ -0,0 +1,44 @@
# Getting started
To get started with FZLang, you need two executable files on your computer:
- `fzc` — the compiler
- `fz` — the virtual machine
To compile a program, run `fzc` and pass it the paths to your source files. If compilation succeeds, run the compiled program with `fz`, passing the path to the compiled file. If you do not specify an output path for the compiler, it saves the compiled program as `a.fzb`.
## Compiler options
To see the available compiler options, run `fzc --help` (or `fzc -h`). The help currently looks like this:
```
Usage: fzc [flags] <sources...>
Available flags:
-h --help Print help
-v --verbose Display compilation info (tokens, AST, consts table, etc)
-V --version Print compiler version
-o --out Set compiled file name
```
The `-h` (`--help`) flag prints the same help text. The `-v` / `--verbose` flags print detailed compilation information, including:
- The tokens that were read;
- The syntax tree that was built;
- The values stored in the constants table;
- The opcodes written to the program.
The `-V` / `--version` flags print the compiler version. The `-o` / `--out` flags let you choose the output file path.
## Virtual machine options
The virtual machine has two options: `--help` (`-h`) and `-d`.
- `--help` (or `-h`) prints the help text;
- `-d <path to file>` prints the opcodes with their indices. Use this flag to see how the VM will read your program.
---
Next: [Basics and types](./02_basics-and-types.md)
Previous: [Introduction](./00_introduction.md)
+161
View File
@@ -0,0 +1,161 @@
# Basics and types
In our first program, we asked FZLang to print some text. That's a good start: a program isn't mysterious; it's a set of instructions that the computer carries out one after another. Now let's see how those instructions work, how to store data, and how to ask a program to make a decision.
Don't worry if some words—such as “type” or “expression”—sound unfamiliar for now. We'll unpack them with small examples. Try changing the values as you read: seeing what changes is one of the best ways to understand what a program does.
## A first program: instructions and comments
The built-in `printline` function writes the value you pass to it to standard output:
```fz
printline("Hello, FZLang!");
```
Here we call `printline` and pass it text inside parentheses. The quotation marks show where the text begins and ends. The semicolon marks the end of the instruction. Try replacing the greeting with your own—the program will print the text you gave it.
Code doesn't have to contain only instructions. You can add notes for yourself and other readers, called comments. A single-line comment starts with `//`; the program ignores everything from those characters to the end of the line:
```fz
// First, calculate a result, then print it.
printline(2 + 2);
```
A comment doesn't affect how the program runs. It helps explain why the code is there, especially when you come back to it later.
## Variables: giving values names
Imagine that a program needs to use the same value more than once. You could write it out every time, but it's more convenient to save it under a name. That's what variables are: named places to keep values.
To declare a variable, write its type, its name, and, if you like, an initial value:
```fz
int count = 10;
str greeting = "Hello there!";
bool ready = true;
```
These lines create three variables: `count` stores a number, `greeting` stores text, and `ready` stores a yes-or-no value. A type helps the language understand what kind of value we're working with. For example, numbers can be added, while text is useful for messages.
You can also declare a variable first and assign a value to it later:
```fz
u64 total;
total = 15;
printline(total);
```
Once a variable has been declared, you can use its name to read its value or assign it a new one. For example, `count = count + 1;` means: take the current value of `count`, add one, and store the result back in `count`. If it was 10, it will be 11 afterward.
### Numbers: integers and their sizes
FZLang has signed and unsigned integer types. Signed numbers can be negative; unsigned types are for non-negative values. For example, a temperature below zero fits a signed type, while a visitor count can use an unsigned type:
```fz
int temperature = -5;
uint visitors = 42;
```
For more precise choices, there are fixed-width types: `i8`, `i16`, `i32`, `i64` and their unsigned counterparts `u8`, `u16`, `u32`, `u64`. The number after `i` or `u` tells you the type's width in bits. `int` and `uint` are the size of a machine word.
```fz
i16 offset = -120;
u8 level = 7;
```
For now, it's enough to remember that `int` works well for ordinary integers. You can choose an exact width when it matters. Numeric types are compatible with one another under the language's rules, but you still state a variable's type when declaring it.
### Boolean values and text
The `bool` type has just two values: `true` and `false`. It's useful for storing the answer to a question—for example, whether a door is open or a program is ready to continue.
```fz
bool is_open = true;
printline(is_open);
```
Text uses the `str` type, short for “string”:
```fz
str language = "FZLang";
printline(language);
```
The type reference also lists `char`, but it isn't currently used by the language. We'll leave classes and object values for later sections; for now, numbers, text, and boolean values are plenty to get comfortable with.
## Expressions: asking the program to calculate
An expression is a piece of code that can be evaluated to produce a value. The simple expression `2 + 2` is one example. Expressions can be stored in variables or passed to functions:
```fz
int result = 2 + 2 * 2;
printline(result);
```
The program evaluates the expression, stores the result in `result`, and then prints it. Just as in ordinary arithmetic, multiplication happens before addition, so the result is 6. To change the order, use parentheses: `(2 + 2) * 2`.
Numbers support addition `+`, subtraction `-`, multiplication `*`, division `/`, remainder `%`, and exponentiation `^`. A unary minus lets you write a negative number, such as `-5`.
Comparisons let us ask questions about values. `==` and `!=` check whether values are equal or unequal; `<`, `<=`, `>` and `>=` compare numbers. A comparison produces a boolean value, either `true` or `false`:
```fz
bool enough = result >= 4 && result != 0;
printline(enough);
```
Here `enough` is true only if `result` is at least 4 and is not zero. The `&&` operator means “and,” `||` means “or,” and `!` flips a boolean value. The [Operators](../reference/operators.md) reference lists all operations and their limitations. Note that comparing objects with `==` or `!=` checks whether the variables refer to the same object, not whether the objects contain the same values.
## Blocks and conditions: choosing what happens next
Sometimes a program needs to do different things in different situations. The `if` condition lets it choose a path. It checks a boolean expression and runs the matching branch:
```fz
bool is_ready = true;
if (is_ready)
{
printline("We can continue");
}
else
{
printline("Let's wait");
}
```
If `is_ready` is `true`, the program prints the first message; otherwise, it prints the second. You can leave out `else` when there's nothing to do in the other case. If there are more than two possibilities, add another condition with `else if`.
Curly braces `{}` group instructions into a block. You've already seen one in the condition example; functions use blocks too. Blocks make it clear which instructions belong together. An `if` condition must have type `bool`, such as a comparison result or a boolean variable.
## Functions: putting useful actions together
When you want to use the same sequence of actions in different places, you can put it in a function. A function receives values through parameters, runs instructions, and can return a result.
```fz
fn sum(int left, int right) int
{
return left + right;
}
int result = sum(2, 3);
printline(result);
```
A declaration starts with `fn`, followed by the function name, its parameters, and its result type. Here, `sum` takes two `int` values, adds them, and returns an `int`. The `return` keyword sends the result back to the code that called the function. That's why `sum(2, 3)` can be used as an expression—for example, to initialize a variable.
## What to remember
You've now met some of the basic building blocks of a program: instructions, comments, variables, types, expressions, conditions, and functions. There's no need to memorize every operator right away; you can return to the reference as you need it.
- A variable has a name and a type, and stores a value.
- For your first programs, you'll use numbers (`int`, `uint`, and fixed-width types), boolean values (`bool`), and strings (`str`).
- Expressions calculate new values, and comparisons help a program make decisions.
- `if` conditions and `{}` blocks control which instructions the program runs.
- Functions group actions and can return a result.
- Instructions usually end with a semicolon, and `printline` helps you see what your program did.
---
Next: [Type system](../reference/types_system.md)
Previous: [Getting started](./01_getting-started.md)
+8
View File
@@ -0,0 +1,8 @@
# The FZLang book
A gentle introduction to FZLang, from getting set up to writing programs with variables, types, and functions.
- [Introduction](./00_introduction.md)
- [Getting started](./01_getting-started.md)
- [Basics and types](./02_basics-and-types.md)
+12
View File
@@ -0,0 +1,12 @@
# FZLang documentation
Choose a section to explore the FZLang documentation.
- [The book](./book/index.md) — a guided introduction to the language.
- [Reference](./reference/index.md) — types, operators, grammar, and runtime diagnostics.
- [Internals](./internals/index.md) — virtual machine implementation details.
- [Standard library](./stdlib/index.md) — built-in functions.
- [Language philosophy](./philosopy.md)
[Документация на русском языке](../ru/index.md)
+8
View File
@@ -0,0 +1,8 @@
# Internals
Implementation details of the FZLang virtual machine and compiled programs.
- [Object layout](./object_layout.md)
- [Opcodes](./opcodes.md)
- [Virtual machine stacks](./stacks.md)
+4
View File
@@ -0,0 +1,4 @@
# Object layout
This page is a placeholder for documentation of the in-memory layout of FZLang objects.
+118
View File
@@ -0,0 +1,118 @@
# Opcodes
FZLang programs run on the virtual machine included in the FZLang toolchain, which is built alongside the compiler. The FZC compiler can emit the following opcodes, which the VM understands and executes.
## Loading data and working with the stack
Codes: `0xF00001`..`0xF00004`
| Number | Name | Purpose | Argument |
| ------ | ---- | ------- | -------- |
| 0xF00001 | FZL_OPCODE_LOADCONST | Push constant `N` onto the stack | `N` — constant index |
| 0xF00002 | FZL_OPCODE_CALL_BUILTIN | Call built-in function `N` | `N` — function index |
| 0xF00003 | FZL_OPCODE_PUSHNUM | Push number `N` onto the stack | `N` — value |
| 0xF00004 | FZL_OPCODE_PUSHBOOL | Push boolean `B` onto the stack | `B` — value |
## Context and environment
Codes: `0xF10001`..`0xF10005`
| Number | Name | Purpose |
| ------ | ---- | ------- |
| 0xF10001 | FZL_OPCODE_MK_ENVTABLE | Create a new environment |
| 0xF10002 | FZL_OPCODE_RM_ENVTABLE | Destroy an environment |
| 0xF10003 | FZL_OPCODE_STORE | Create a name in the environment from the value at the top of the stack |
| 0xF10004 | FZL_OPCODE_LOOKUP_ENV | Take a name from the stack, look it up in the environment, and push its value |
| 0xF10005 | FZL_OPCODE_WRITE | Take a name and value from the stack and write the value to the environment |
## Control flow
Codes: `0xF20001`..`0xF20002`
| Number | Name | Purpose | Argument |
| ------ | ---- | ------- | -------- |
| 0xF20001 | FZL_OPCODE_JMP | Jump to address `IP` | `IP` — target address |
| 0xF20002 | FZL_OPCODE_JZ | Jump to address `IP` if the value at the top of the stack is zero | `IP` — target address |
## Function calls
Codes: `0xF30001`..`0xF30002`
| Number | Name | Purpose |
| ------ | ---- | ------- |
| 0xF30001 | FZL_OPCODE_CALL | Push the current address onto the call stack and jump to the start of a function |
| 0xF30002 | FZL_OPCODE_RET | Pop the last address from the call stack and jump to it |
## Objects
Codes: `0xFA0001`..`0xFA0005`
| Number | Name | Purpose | Argument |
| ------ | ---- | ------- | -------- |
| 0xFA0001 | FZL_OPCODE_ALLOC_OBJECT | Allocate an object for `N` values and push it onto the stack | `N` — number of values |
| 0xFA0002 | FZL_OPCODE_GET_FIELD | Pop an object reference from the stack and get field `N` | `N` — field index |
| 0xFA0003 | FZL_OPCODE_SET_FIELD | Pop an object reference and a value, then write the value to field `N` | `N` — field index |
| 0xFA0004 | FZL_OPCODE_ESCAPE_OBJECT | Move an object (including its children) to the parent environment | none |
## Arithmetic operations
Codes: `0xA00001`..`0xA00007`
| Number | Name | Purpose |
| ------ | ---- | ------- |
| 0xA00001 | FZL_OPCODE_ADD | Pop two numbers, add them, and push the result |
| 0xA00002 | FZL_OPCODE_SUB | Pop two numbers, subtract the first from the second, and push the result |
| 0xA00003 | FZL_OPCODE_MUL | Pop two numbers, multiply them, and push the result |
| 0xA00004 | FZL_OPCODE_DIV | Pop two numbers, divide the first by the second, and push the result |
| 0xA00005 | FZL_OPCODE_POW | Pop two numbers, raise the second to the power of the first, and push the result |
| 0xA00006 | FZL_OPCODE_MOD | Pop two numbers and push the remainder of the second divided by the first |
| 0xA00007 | FZL_OPCODE_NEG | Pop a number, negate it, and push it back onto the stack |
## Numeric comparisons
Codes: `0xB00001`..`0xB00006`
| Number | Name | Purpose |
| ------ | ---- | ------- |
| 0xB00001 | FZL_OPCODE_CMP_EQ | Pop two numbers, compare them for equality, and push the result |
| 0xB00002 | FZL_OPCODE_CMP_NEQ | Pop two numbers, compare them for inequality, and push the result |
| 0xB00003 | FZL_OPCODE_CMP_LT | Pop two numbers, check whether the second is less than the first, and push the result |
| 0xB00004 | FZL_OPCODE_CMP_LTEQ | Pop two numbers, check whether the second is less than or equal to the first, and push the result |
| 0xB00005 | FZL_OPCODE_CMP_GT | Pop two numbers, check whether the second is greater than the first, and push the result |
| 0xB00006 | FZL_OPCODE_CMP_GTEQ | Pop two numbers, check whether the second is greater than or equal to the first, and push the result |
## String comparisons
Codes: `0xB10001`..`0xB10002`
| Number | Name | Purpose |
| ------ | ---- | ------- |
| 0xB10001 | FZL_OPCODE_CMP_EQ_STR | Pop two strings, compare them for equality, and push the result |
| 0xB10002 | FZL_OPCODE_CMP_NEQ_STR | Pop two strings, compare them for inequality, and push the result |
## Object comparisons
Codes: `0xB20001`..`0xB20002`
| Number | Name | Purpose |
| ------ | ---- | ------- |
| 0xB20001 | FZL_OPCODE_CMP_EQ_OBJ | Pop two objects, compare them for equality, and push the result |
| 0xB20002 | FZL_OPCODE_CMP_NEQ_OBJ | Pop two objects, compare them for inequality, and push the result |
## Binary logical operations
Codes: `0xC00001`..`0xC00002`
| Number | Name | Purpose |
| ------ | ---- | ------- |
| 0xC00001 | FZL_OPCODE_AND | Pop two values and push their logical AND |
| 0xC00002 | FZL_OPCODE_OR | Pop two values and push their logical OR |
## Unary logical operations
Code: `0xD00001`
| Number | Name | Purpose |
| ------ | ---- | ------- |
| 0xD00001 | FZL_OPCODE_NOT | Pop a value and push its inverted boolean result |
+92
View File
@@ -0,0 +1,92 @@
# Virtual machine stacks
While running, the FZLang virtual machine relies on three stacks: the data stack, the environment stack, and the call stack. Each stack is initialized as an array of 256 values and then zeroed. All three share the same basic property: the most recently added value is at the top and is the first one removed, decrementing the stack-top pointer.
## Data stack
The data stack is an array of 256 `fzl_value_t` elements. The `fzl_value_t` structure is a container for values used while a program runs.
```c
typedef struct
{
fzl_type_t type;
union
{
void *obj;
struct
{
const char *value;
size_t length;
} s;
char c;
bool b;
int8_t i8;
uint8_t u8;
int16_t i16;
uint16_t u16;
int32_t i32;
uint32_t u32;
int64_t i64;
uint64_t u64;
intptr_t i;
uintptr_t u;
} as;
} fzl_value_t;
```
The VM uses this stack to store and retrieve intermediate values while the program runs. The stack should be empty when execution finishes.
## Environment stack
The environment stack tracks the lexical environment in which evaluation and instructions are currently taking place. It is an array of 256 `fzl_vm_env_t` elements.
```c
typedef struct
{
size_t size;
size_t remainder;
fzl_vm_env_entry_t *table;
fzl_vm_object_t **owned_objects;
size_t owned_objects_remainder;
size_t owned_objects_count;
} fzl_vm_env_t;
```
Each stack element stores a table of local names (`fzl_vm_env_entry_t`) and the objects owned by the current environment. A local name is a container that wraps an `fzl_value_t` and gives it a name:
```c
typedef struct
{
char *name;
fzl_value_t value;
} fzl_vm_env_entry_t;
```
An object is an array of its fields. The number of fields is set when the object is allocated by the `FZL_OPCODE_ALLOC_OBJECT N` opcode (see the [opcode reference](./opcodes.md) for details).
```c
typedef struct
{
fzl_value_t *values;
size_t values_count;
} fzl_vm_object_t;
```
An environment is created or removed by the `FZL_OPCODE_MK_ENVTABLE` and `FZL_OPCODE_RM_ENVTABLE` opcodes, respectively. If the compiler sees a value of type `FZL_TYPE_OBJECT` being returned from a function, it inserts `FZL_OPCODE_ESCAPE_OBJECT` before `FZL_OPCODE_RET` and `FZL_OPCODE_RM_ENVTABLE`. This moves ownership of the object to the parent environment.
## Call stack
The call stack stores instruction addresses when entering a function through the `FZL_OPCODE_CALL` opcode. `FZL_OPCODE_RET` pops an address from the call stack and jumps to it.
The stack itself is an array of 256 `size_t` elements.
+24
View File
@@ -0,0 +1,24 @@
# Language philosophy
FZLang has two guiding goals: “be convenient as a tool, while always trying to remain predictable.”
Here are some of the ways the language tries to follow that philosophy.
## Memory and resources
In environments with a garbage collector (GC), programmers don't know exactly when the collector will run. An object may remain in memory for seconds after a method has finished. And when cleanup does happen, the runtime may pause briefly—something that can matter in audio, games, networking, or embedded systems.
FZLang uses scope-based RAII, a model in which memory use is deterministic. Every object belongs to a scope, and when that scope is destroyed, the object is destroyed with it. This makes response times more consistent and predictable.
Scope-based RAII also helps manage resources such as sockets and files. Resource cleanup follows the same clear rule: when a scope ends, everything attached to it is cleaned up.
Finally, scope-based RAII avoids the runtime overhead of a full garbage collector. A GC needs mechanisms such as memory maps, background threads, and read and write barriers. FZLang's virtual machine is small, and memory is released as instructions are processed.
## Syntax
FZLang deliberately keeps some parts of its syntax explicit. For example, methods of a class instance must use `this`, and declarations require types. These are strict parts of the language.
On the other hand, a function declaration can omit a return type when it doesn't return a value; writing `void` every time would be unnecessary.
In this way, FZLang aims to balance syntax that keeps behavior predictable with simplifications that are genuinely useful.
+112
View File
@@ -0,0 +1,112 @@
# Error output
When a runtime error occurs, the VM prints its state at the time it stopped. The output includes:
1. The instruction pointer;
2. The data stack;
3. The environment stack;
4. The call stack.
## Instruction pointer (IP)
The instruction pointer is useful for debugging alongside the program's opcode listing. Run the VM with the `--debug` (`-d`) flag and the path to a compiled program to print its opcodes without running it.
The output looks roughly like this:
```
0000: MK_ENVTABLE
0001: JMP IP: 53
0003: MK_ENVTABLE
0004: LOADCONST #0 ('a')
0006: STORE
0007: LOADCONST #0 ('a')
0009: WRITE
0010: MK_ENVTABLE
0011: LOADCONST #0 ('a')
0013: LOOKUP_ENV
0014: GET_FIELD #0
...
```
The instruction index helps you find the instruction where the exception occurred. See the [opcode reference](../internals/opcodes.md) for the complete list and descriptions.
## Data stack
The data stack is printed as a list of values in the form `[T] V`, where `T` is the type number and `V` is the value. Strings and objects are prefixed with `str: V` and `ref: V`, respectively.
Example:
```
> Data stack:
> [2] str: 'this'
```
Values are printed from top to bottom, so the most recent values appear first.
## Environment stack
The environment stack is printed as two lists: objects owned by the environment and names local to it.
Example environment:
```
> Environments:
> [1] 0x55a9d8196a20:
> Owned objects (0/2):
> Local entries (3/4):
> [14] b = 4
> [1] a = ref 0x55a9d81969e0
> [0] this = 0
> [0] 0x55a9d8196930:
> Owned objects (1/2):
> ref: 0x55a9d81969e0 (entries: 1)
> Local entries (1/2):
> [1] sum = ref 0x55a9d81969e0
```
`> [N] A` identifies the environment table itself. `N` is the table's nesting level (larger values mean a deeper environment), and `A` is its memory address.
The first list is `Owned objects`; it shows the number of elements and the allocated array size as `(<count>/<capacity>)`. Objects are listed as `ref: A (entries: N)`, where `A` is the object's memory address and `N` is the number of fields it contains.
The second list is `Local entries`, with the number of entries and allocated capacity. Each entry has the form `[T] N = V`, where `T` is the type number, `N` is the entry name, and `V` is its value.
## Call stack
The call stack contains return addresses. An address is pushed when the `CALL` opcode runs and popped by `RET`.
```
> Call stack:
> [0] 53
```
Each entry has the form `[L] IP`, where `L` is the nesting level (larger means deeper) and `IP` is the instruction index to jump to after returning.
## Complete output
A complete error report looks like this:
```
===== ERROR =====
> Caused on IP: 21
> Data stack:
> [2] str: 'this'
> Environments:
> [1] 0x55a9d8196a20:
> Owned objects (0/2):
> Local entries (3/4):
> [14] b = 4
> [1] a = ref 0x55a9d81969e0
> [0] this = 0
> [0] 0x55a9d8196930:
> Owned objects (1/2):
> ref: 0x55a9d81969e0 (entries: 1)
> Local entries (1/2):
> [1] sum = ref 0x55a9d81969e0
> Call stack:
> [0] 53
Error: runtime error - expected 2 values in data stack
```
+61
View File
@@ -0,0 +1,61 @@
/* FZL (FromZero Language) syntax */
/* Version 1 */
/* Authors: */
/* - Samsonium (https://git.samsonium.dev/Samsonium) */
<program> ::= <_> <statement>+
<statement> ::= <class_stmt>
| <fn_stmt>
| <if_stmt>
| <block_stmt>
| <ret_stmt>
| <expr_stmt>
<_> ::= " "*
<ws> ::= " "+
<expr_stmt> ::= <expr> <_> ";" <_>
<block_stmt> ::= "{" <_> <statement>* "}" <_>
<ret_stmt> ::= "return" (<ws> <expr>)? <_> ";" <_>
<class_stmt> ::= "class" <ws> <name> <_> "{" <_> (<declaration> | <fn_stmt>)* "}" <_>
<fn_stmt> ::= "fn" <ws> <name> <_> "(" <_> (<param> (<_> "," <_> <param>)*)? ")" <_> <block_stmt>
<param> ::= <type> <ws> <name>
<if_stmt> ::= "if" <_> "(" <_> <expr> <_> ")" <_> <statement> (<_> "else" <ws> <statement>)?
/* Assign (lowest expression priority, right-associative) */
<expr> ::= <assign_expr>
<assign_expr> ::= <new_expr> (<_> "=" <_> <assign_expr>)?
<new_expr> ::= ("new" <ws> <name>) | <or_expr>
<or_expr> ::= <and_expr> (<_> "||" <_> <and_expr>)*
<and_expr> ::= <cond_expr> (<_> "&&" <_> <cond_expr>)*
<cond_expr> ::= <term_expr> (<_> <cond_op> <_> <term_expr>)*
<cond_op> ::= "==" | "!=" | "<=" | ">=" | "<" | ">"
<term_expr> ::= <factor_expr> (<_> ("+" | "-") <_> <factor_expr>)*
<factor_expr> ::= <power_expr> (<_> ("*" | "/" | "%") <_> <power_expr>)*
<power_expr> ::= <unary_expr> (<_> "^" <_> <power_expr>)?
<unary_expr> ::= (("!" | "-" | "+") <_>)* <postfix_expr>
/* Postfix: chain of calls and dots */
<postfix_expr> ::= <primary_expr> (<call_suffix> | <member_suffix>)*
<call_suffix> ::= <_> "(" <_> (<expr> (<_> "," <_> <expr>)*)? <_> ")"
<member_suffix> ::= <_> "." <_> <name>
<primary_expr> ::= <literal>
| <declaration>
| "(" <_> <expr> <_> ")"
| <name>
<declaration> ::= <type> <ws> <name>
<type> ::= "int" | "uint" | "bool" | "char" | "str"
| "i8" | "i16" | "i32" | "i64"
| "u8" | "u16" | "u32" | "u64"
<literal> ::= <number> | <string_literal> | "true" | "false" | "null"
<name> ::= ([A-z] | "_") | ([A-z] | [0-9] | "_")*
<string_literal> ::= ('"' [^"]* '"') | ("'" [^']* "'")
+4
View File
@@ -0,0 +1,4 @@
# Grammar (EBNF)
The formal grammar is maintained in the [grammar.ebnf](./grammar.ebnf) file.
+9
View File
@@ -0,0 +1,9 @@
# Reference
Language and runtime reference material.
- [Operators](./operators.md)
- [Type system](./types_system.md)
- [Grammar (EBNF)](./grammar_ebnf.md)
- [Error output](./errors_print.md)
+37
View File
@@ -0,0 +1,37 @@
# Operators
FZLang supports arithmetic, comparison, logical, and unary operations. During execution, a binary operation takes two values from the stack and puts its result back. A unary operation takes one value from the stack and puts its result back.
## Arithmetic operations
| Operation | Supported types | Description |
| --------- | --------------- | ----------- |
| `N + N` | *all numeric types* | Add two operands |
| `N - N` | *all numeric types* | Subtract the right operand from the left |
| `N * N` | *all numeric types* | Multiply two operands |
| `N / N` | *all numeric types* | Divide the left operand by the right |
| `N % N` | *all numeric types* | Remainder of the left operand divided by the right |
| `N ^ N` | *all numeric types* | Raise the left operand to the power of the right |
| `-N` | *all numeric types* | Negate the operand |
## Comparison operations
| Operation | Supported types | Description |
| --------- | --------------- | ----------- |
| `N == N` | *all numeric types*, booleans, objects, strings | Check whether the operands are equal |
| `N != N` | *all numeric types*, booleans, objects, strings | Check whether the operands are unequal |
| `N > N` | *all numeric types* | Check whether the left operand is greater than the right |
| `N >= N` | *all numeric types* | Check whether the left operand is greater than or equal to the right |
| `N < N` | *all numeric types* | Check whether the left operand is less than the right |
| `N <= N` | *all numeric types* | Check whether the left operand is less than or equal to the right |
| `!N` | booleans | Invert the operand |
> **Important:** Comparing objects with `==` or `!=` compares their addresses, not their values.
## Logical operations
| Operation | Supported types | Description |
| --------- | --------------- | ----------- |
| `N && N` | booleans | Logical **AND** of two operands |
| `N || N` | booleans | Logical **OR** of two operands |
+50
View File
@@ -0,0 +1,50 @@
# Type system
FZLang's type system has two foundations: primitives and objects.
Primitive types include strings, numeric types, booleans, and the internal object type. Objects currently include class instances and the `null` literal.
## Type table
| Internal name | Syntax | Description |
| ------------- | ------ | ----------- |
| FZL_TYPE_NULL | *none* | *currently unused* |
| FZL_TYPE_OBJECT | *none* | Class instance or the `null` literal |
| FZL_TYPE_STRING | `str` | String |
| FZL_TYPE_CHAR | `char` | *currently unused* |
| FZL_TYPE_BOOL | `bool` | Boolean |
| FZL_TYPE_INT8 | `i8` | 8-bit signed integer |
| FZL_TYPE_UINT8 | `u8` | 8-bit unsigned integer |
| FZL_TYPE_INT16 | `i16` | 16-bit signed integer |
| FZL_TYPE_UINT16 | `u16` | 16-bit unsigned integer |
| FZL_TYPE_INT32 | `i32` | 32-bit signed integer |
| FZL_TYPE_UINT32 | `u32` | 32-bit unsigned integer |
| FZL_TYPE_INT64 | `i64` | 64-bit signed integer |
| FZL_TYPE_UINT64 | `u64` | 64-bit unsigned integer |
| FZL_TYPE_INT | `int` | Signed integer the size of a machine word |
| FZL_TYPE_UINT | `uint` | Unsigned integer the size of a machine word |
## Compatibility
Type compatibility is checked using these rules:
1. If both types are objects (`FZL_TYPE_OBJECT`):
1. If either type has the class name `<nullptr>`, the check **passes**.
2. If both types have the same class name, the check **passes**.
3. Otherwise, the check **fails**.
2. If the types are equal, the check **passes**.
3. If both types are numeric (`u8`..`uint`, `i8`..`int`), the check **passes**.
4. If none of the rules above apply, the check **fails**.
**Passes** means the types are compatible; **fails** means they are not.
## Runtime behavior
Primitive values (that is, values other than `FZL_TYPE_OBJECT`) are used directly. Operations on them use the value stored in the stack slot.
Object values are stored on the stack as **references to objects**. Valid operations on them include:
- `FZL_OPCODE_ESCAPE_OBJECT` (move an object to the parent environment)
- `FZL_OPCODE_GET_FIELD N` (read field `N` from an object)
- `FZL_OPCODE_SET_FIELD N` (write a value to field `N` of an object)
+20
View File
@@ -0,0 +1,20 @@
# Built-in functions
FZLang provides built-in functions that are available in every program.
## Exit: `exit`
```
fn exit(uint code)
```
Takes an unsigned integer argument and exits the program with the specified code.
## Print to stdout: `printline`
```
fn printline(value)
```
Accepts any value and writes it to standard output. Primitive values are printed directly according to their types. Objects are printed as `obj<...>`, where `...` is a list of their field values. Each field is printed with its type and value in the format `T<V>`, where `T` is the type name and `V` is the current value.
+6
View File
@@ -0,0 +1,6 @@
# Standard library
Documentation for functions available to FZLang programs without additional imports.
- [Built-in functions](./builtins.md)
-6
View File
@@ -1,6 +0,0 @@
program := statement*
statement := expression
expression := call | string_literal
call := name "(" argument-list? ")"
argument-list := string_literal ("," string_literal)*
+43
View File
@@ -0,0 +1,43 @@
# Вступление
Добро пожаловать в небольшую книжку по основам языка FZLang и спасибо, что решились попробовать этот язык программирования.
Для начала, стоит рассказать, что это за язык и как будет идти обучение в этой "книжке".
## Что такое FZLang
FZLang - это язык программирования с собственным тулчейном, включающим в себя компилятор и виртуальную машину.
Компилятор позволяет проверить и скомпилировать исходный код программы, чтобы сохранить в переносимый файл, а виртуальная машина - выполнить программу, написанную на FZLang.
Изначально, этот проект задумывался как самоучитель по компиляторостроению.
Мне (Samsonium) было интересно разобрать:
- Почему между компилятором и интерпретатором есть значимая разница?
- Как программа, под названием "компилятор" понимает, что я хочу сделать?
- Как устроено исполнение скомпилированной программы? Да и как, вообще, происходит процесс "перевода" с человеческого на машинный?
Я занимаюсь программированием уже более 10 лет, но за всё время так и не удосужился об этом разузнать!
Поэтому, этот проект был рождён как поле для экспериментов, разрабатывая который я стану лучше понимать устройство того, на чём строится весь мир программистов.
Однако, по ходу разработки у языка стала появлятся своя форма, а также философия.
Сейчас, я бы описал философию языка так: "быть удобным, как инструмент, но всегда стараться оставаться предсказуемым".
Полагаю, любое комьюнити желает своему языку того же, но мне это помогло сделать определённый выбор в ключевых точках развития.
### Как в FZLang устроена работа с памятью
Изначально вопрос появился после внедрения стеков виртуальной машины, и звучал он так: "сборщик мусора или что-то другое?".
Изучить работу сборщика было, конечно, интересно.
Однако мне, человеку, выбравшему Java как первый язык, была знакома его "тяжесть" и "скорость" работы.
Конечно же, отдельное "спасибо" за это можно сказать его сборщику мусора.
Поэтому, вместо GC я решил посмотреть в сторону RAII и, в итоге, получил хорошую формулу гуляния объекта в программе:
если объект подан как аргумент, то происходит "заимствование" его во вложенное окружение, и если объект возвращается, то происходит перемещение объекта в родительское окружение.
Такой подход позволяет не делать GC, а ещё, что самое важное, получить детерминированное время жизни объекта, а значит, детерминированную память (настолько, насколько это возможно).
Короче говоря, FZLang обзавёлся scope-based RAII.
---
Далее: [Начало работы](./01_getting-started.md)
+46
View File
@@ -0,0 +1,46 @@
# Начало работы
Для того, чтобы начать работу с FZLang, достаточно установить себе на компьютер два исполяемых файла:
- `fzc` - компилятор
- `fz` - виртуальная машина
Для того, чтобы скомпилировать программу, достаточно выполнить программу `fzc`, указав в качестве аргументов пути к файлам с исходным кодом.
Если программа успешно скомпилировалась, то её можно запустить через программу `fz`, также указав путь скомпилированному файлу.
Без указания компилятору пути к выходному файлу, скомпилированная программа будет сохранена как `a.fzb`.
## Настройки компилятора
Чтобы посмотреть о доступных настройках компилятора, выполните `fzc --help` (или `fzc -h`).
На данный момент, справка выглядит так:
```
Usage: fzc [flags] <sources...>
Available flags:
-h --help Print help
-v --verbose Display compilation info (tokens, AST, consts table, etc)
-V --version Print compiler version
-o --out Set compiled file name
```
Флаг `-h` (`--help`) просто выводит ту же справку. Флаги `-v` / `--verbose` делает подробный вывод компиляции:
- Какие лексемы были прочитаны;
- Какое синтаксическое дерево было собрано;
- Какие значения были записаны в таблицу констант;
- Какие опкоды были записаны в программу.
Флаги `-V` / `--version` выводят версию компилятора. Флаги `-o`, `--out` позволяют задать путь выходного файла.
## Настройки виртуальной машины
У виртуальной машины есть всего два флага: `--help` (`-h`) и `-d`.
- `--help` (или `-h`) выводит справку;
- `-d <путь к файлу>` выводит список опкодов с нумерацией. С помощью этого флага вы можете посмотреть, как вашу программу будет читать ВМ.
---
Далее: [Основы и типы](./02_basics-and-types.md)
Назад: [Вступление](./00_introduction.md)
+199
View File
@@ -0,0 +1,199 @@
# Основы и типы
В первой программе мы уже попросили FZLang вывести текст на экран.
Это хороший старт: программа — не что-то загадочное, а набор инструкций, которые компьютер выполняет одну за другой.
Теперь разберёмся, как эти инструкции устроены, как сохранить данные и как попросить программу принять решение.
Не переживайте, если некоторые слова — например, «тип» или «выражение» — пока звучат непривычно.
Мы будем разбирать их на небольших примерах.
Попробуйте не только читать код, но и менять значения: так гораздо легче увидеть, что именно делает программа.
## Первая программа: инструкция и комментарий
Встроенная функция `printline` выводит переданное ей значение в стандартный вывод:
```fz
printline("Привет, FZLang!");
```
Здесь мы вызываем функцию `printline` и передаём ей текст в круглых скобках.
Кавычки показывают, где текст начинается и заканчивается.
Точка с запятой сообщает, что инструкция завершена.
Попробуйте заменить приветствие на своё — программа выведет именно тот текст, который вы ей передали.
Код не обязан состоять только из команд.
В него можно добавлять пояснения для себя и других читателей — комментарии.
Однострочный комментарий начинается с `//`; всё, что стоит после этих символов до конца строки, программа пропускает:
```fz
// Сначала посчитаем, потом выведем результат.
printline(2 + 2);
```
Комментарий не влияет на выполнение программы. Он помогает объяснить, зачем написан код, особенно когда вы вернётесь к нему позже.
## Переменные: давайте дадим значениям имена
Представьте, что программе нужно несколько раз использовать одно и то же значение.
Можно каждый раз писать его заново, но удобнее сохранить его под именем.
Для этого существуют переменные — именованные места для значений.
При объявлении переменной указывают её тип, имя и, если нужно, начальное значение:
```fz
int count = 10;
str greeting = "Здравствуйте!";
bool ready = true;
```
В этих строках мы создали три переменные: `count` хранит число, `greeting` — текст, а `ready` — ответ «да» или «нет».
Тип помогает языку понять, с каким значением мы работаем. Например, число можно складывать, а текст удобно выводить как сообщение.
Переменную можно сначала объявить, а значение присвоить позже:
```fz
u64 total;
total = 15;
printline(total);
```
Когда переменная уже объявлена, её имя можно использовать, чтобы прочитать значение или присвоить новое.
Например, `count = count + 1;` означает: взять текущее значение `count`, прибавить единицу и сохранить результат обратно.
Если до этого `count` было равно 10, после инструкции оно станет равно 11.
### Числа: целые и разных размеров
Для целых чисел в FZLang есть знаковые и беззнаковые типы.
Знаковые числа могут быть отрицательными, а беззнаковые предназначены для неотрицательных значений.
Например, температуру ниже нуля удобно представить знаковым типом, а количество посетителей — беззнаковым:
```fz
int temperature = -5;
uint visitors = 42;
```
Для более точного выбора есть типы фиксированной ширины: `i8`, `i16`, `i32`, `i64` и их беззнаковые варианты `u8`, `u16`, `u32`, `u64`.
Число после `i` или `u` указывает ширину типа в битах. Типы `int` и `uint` имеют размер машинного слова.
```fz
i16 offset = -120;
u8 level = 7;
```
На первых шагах достаточно запомнить, что `int` подходит для обычных целых чисел, а точный тип можно выбрать, когда это действительно важно.
Числовые типы совместимы между собой по правилам языка, но при объявлении переменной тип всё равно указывается явно.
### Логические значения и текст
У типа `bool` всего два значения: `true` (истина) и `false` (ложь).
Это удобно, когда нужно хранить ответ на вопрос: например, открыта ли дверь или готова ли программа продолжать работу.
```fz
bool is_open = true;
printline(is_open);
```
Для текста используется тип `str` (строка):
```fz
str language = "FZLang";
printline(language);
```
В справочнике типов также упоминается `char`, но в текущей версии языка он не используется.
Классы и объектные значения мы оставим для следующих разделов — пока достаточно освоиться с числами, текстом и логическими значениями.
## Выражения: попросим программу посчитать
Выражение — это часть кода, которую можно вычислить и получить значение.
Самый простой пример — `2 + 2`. Выражения можно сохранять в переменные или передавать функциям:
```fz
int result = 2 + 2 * 2;
printline(result);
```
Здесь программа сначала вычисляет выражение, сохраняет результат в `result`, а затем выводит его.
Как и в обычной математике, умножение выполняется раньше сложения, поэтому результат будет 6.
Если хотите изменить порядок вычислений, используйте скобки: ` (2 + 2) * 2`.
Для чисел доступны сложение `+`, вычитание `-`, умножение `*`, деление `/`, остаток от деления `%` и возведение в степень `^`.
Унарный минус позволяет записывать отрицательные числа, например `-5`.
Сравнения помогают задавать вопросы о значениях. Операторы `==` и `!=` проверяют равенство и неравенство, а `<`, `<=`, `>` и `>=` сравнивают числа.
Результат сравнения — логическое значение `true` или `false`:
```fz
bool enough = result >= 4 && result != 0;
printline(enough);
```
В примере переменная `enough` станет истинной, только если `result` не меньше 4 и одновременно не равен нулю.
Оператор `&&` означает «и», `||` — «или», а `!` меняет логическое значение на противоположное.
Полный перечень операций и их ограничений приведён в справочнике [«Операторы»](../reference/operators.md).
Учтите, что при сравнении объектов `==` и `!=` проверяют, ссылаются ли переменные на один и тот же объект, а не совпадает ли содержимое объектов.
## Блоки и условия: программа выбирает путь
Иногда программе нужно выполнить разные действия в зависимости от ситуации.
Для этого есть условие `if`. Оно проверяет логическое выражение и выполняет подходящую ветвь:
```fz
bool is_ready = true;
if (is_ready)
{
printline("Можно продолжать");
}
else
{
printline("Нужно подождать");
}
```
Если `is_ready` равно `true`, программа выведет первое сообщение; иначе — второе.
Ветвь `else` можно опустить, если для случая «нет» ничего делать не нужно.
Если вариантов больше двух, можно добавить `else if` с ещё одним условием.
Фигурные скобки `{}` объединяют инструкции в блок. Вы уже встречали их в примере с условием; такие блоки используются также в функциях.
Они помогают видеть, какие инструкции относятся к одной части программы.
Условие `if` должно иметь тип `bool` — например, результат сравнения или логическая переменная.
## Функции: соберём полезные действия
Когда одну и ту же последовательность действий хочется использовать в разных местах, её можно оформить как функцию.
Функция получает значения через параметры, выполняет инструкции и при необходимости возвращает результат.
```fz
fn sum(int left, int right) int
{
return left + right;
}
int result = sum(2, 3);
printline(result);
```
Объявление начинается с `fn`, затем идут имя функции, параметры и тип результата.
В этом примере `sum` принимает два значения типа `int`, складывает их и возвращает результат типа `int`.
Ключевое слово `return` передаёт этот результат туда, откуда функцию вызвали.
Поэтому вызов `sum(2, 3)` можно использовать как выражение — например, присвоить его результат переменной.
## Что запомнить
Вы уже познакомились с основными строительными блоками программы: инструкциями, комментариями, переменными, типами, выражениями, условиями и функциями.
Не обязательно запоминать все операторы сразу — к справочнику можно возвращаться по мере необходимости.
- Переменная хранит значение и имеет имя и тип.
- Для первых примеров пригодятся числа (`int`, `uint` и типы фиксированной ширины), логические значения (`bool`) и строки (`str`).
- Выражения позволяют вычислять новые значения, а сравнения помогают принимать решения.
- Условия `if` и блоки `{}` управляют тем, какие инструкции выполнит программа.
- Функции объединяют действия и могут возвращать результат.
- Инструкции обычно заканчиваются точкой с запятой, а `printline` помогает увидеть результат работы программы.
---
Далее: [Система типов](../reference/types_system.md)
Назад: [Начало работы](./01_getting-started.md)
+8
View File
@@ -0,0 +1,8 @@
# Книга по FZLang
Знакомство с FZLang: от подготовки к работе до программ с переменными, типами и функциями.
- [Вступление](./00_introduction.md)
- [Начало работы](./01_getting-started.md)
- [Основы и типы](./02_basics-and-types.md)
+12
View File
@@ -0,0 +1,12 @@
# Документация FZLang
Выберите раздел документации:
- [Книга](./book/index.md) — последовательное знакомство с языком.
- [Справочник](./reference/index.md) — типы, операторы, грамматика и диагностика выполнения.
- [Внутреннее устройство](./internals/index.md) — подробности о виртуальной машине.
- [Стандартная библиотека](./stdlib/index.md) — встроенные функции.
- [Философия языка](./philosopy.md)
[Read the documentation in English](../en/index.md)
+8
View File
@@ -0,0 +1,8 @@
# Внутреннее устройство
Материалы об устройстве виртуальной машины FZLang и скомпилированных программ.
- [Размещение объектов](./object_layout.md)
- [Опкоды](./opcodes.md)
- [Стеки виртуальной машины](./stacks.md)
+2
View File
@@ -0,0 +1,2 @@
# Размещение объектов
+119
View File
@@ -0,0 +1,119 @@
# Опкоды
Программы, написанные на FZLang, исполняются с помощью виртуальной машины, входящей в тулчейн FZLang и компилирующейся в паре с компилятором.
Компилятор FZC может выпускать следующие опкоды, которые понимает и исполняет ВМ:
## Загрузка данных и работа со стеком
Коды: `0xF00001`..`0xF00004`
| Номер | Название | Назначение | Аргумент |
| -------- | ----------------------- | ------------------------------- | --------------------- |
| 0xF00001 | FZL_OPCODE_LOADCONST | Загрузить константу `N` на стек | `N` - номер константы |
| 0xF00002 | FZL_OPCODE_CALL_BUILTIN | Вызвать встроенную функцию `N` | `N` - номер функции |
| 0xF00003 | FZL_OPCODE_PUSHNUM | Поместить на стек число `N` | `N` - значение |
| 0xF00004 | FZL_OPCODE_PUSHBOOL | Поместить на стек булеву `B` | `B` - значение |
## Управление контекстом и окружением
Коды: `0xF10001`..`0xF10005`
| Номер | Название | Назначение |
| -------- | ---------------------- | ------------------------------------------------------------------- |
| 0xF10001 | FZL_OPCODE_MK_ENVTABLE | Создать новое окружение |
| 0xF10002 | FZL_OPCODE_RM_ENVTABLE | Уничтожить окружение |
| 0xF10003 | FZL_OPCODE_STORE | Создать в окружении имя, лежащее на вершине стека |
| 0xF10004 | FZL_OPCODE_LOOKUP_ENV | Достать из стека имя, найти в окружении и положить на стек значение |
| 0xF10005 | FZL_OPCODE_WRITE | Достать со стека имя и значение, и записать в окружение |
## Управление потоком
Коды: `0xF20001`..`0xF20002`
| Номер | Название | Назначение | Аргумент |
| -------- | -------------- | ----------------------------------------------------------------- | ------------------------ |
| 0xF20001 | FZL_OPCODE_JMP | Прыгнуть на адрес `IP` | `IP` - адрес-цель прыжка |
| 0xF20002 | FZL_OPCODE_JZ | Прыгнуть на адрес `IP`, если значение на вершине стека равно нулю | `IP` - адрес-цель прыжка |
## Вызов функций
Коды: `0xF30001`..`0xF30002`
| Номер | Название | Назначение |
| -------- | --------------- | ----------------------------------------------------------------- |
| 0xF30001 | FZL_OPCODE_CALL | Положить на стек вызовов текущий адрес и перейти к началу функции |
| 0xF30002 | FZL_OPCODE_RET | Снять со стека вызовов последний адрес и перейти к нему |
## Работа с объектами
Коды: `0xFA0001`..`0xFA0005`
| Номер | Название | Назначение | Аргумент |
| -------- | ------------------------ | -------------------------------------------------------------------------------- | --------------------- |
| 0xFA0001 | FZL_OPCODE_ALLOC_OBJECT | Аллоцировать объект под `N` значений и поместить его на стек | `N` - кол-во значений |
| 0xFA0002 | FZL_OPCODE_GET_FIELD | Снять со стека ссылку на объект и получить значение поля `N` | `N` - номер поля |
| 0xFA0003 | FZL_OPCODE_SET_FIELD | Снять со стека ссылку на объект и значение, а затем записать значение в поле `N` | `N` - номер поля |
| 0xFA0004 | FZL_OPCODE_ESCAPE_OBJECT | Перенести объект (в т.ч. дочерние ему) в родительское окружение | отсутствует |
## Арифметические операции
Коды: `0xA00001`..`0xA00007`
| Номер | Название | Назначение |
| -------- | -------------- | --------------------------------------------------------------------------------------- |
| 0xA00001 | FZL_OPCODE_ADD | Снять два числа со стека и сложить их, положив результат на стек |
| 0xA00002 | FZL_OPCODE_SUB | Снять два числа со стека и вычесть первое от второго, положив результат на стек |
| 0xA00003 | FZL_OPCODE_MUL | Снять два числа со стека и переменожить их, положив результат на стек |
| 0xA00004 | FZL_OPCODE_DIV | Снять два числа со стека и поделить первое на второе, положив результат на стек |
| 0xA00005 | FZL_OPCODE_POW | Снять два числа со стека и возвести второе в степерь первого, положив результат на стек |
| 0xA00006 | FZL_OPCODE_MOD | Снять два числа со стека и положить на стек остаток от деления второго на первое |
| 0xA00007 | FZL_OPCODE_NEG | Снять число со стека, инвертировать, и положить обратно на стек |
## Сравнение чисел
Коды: `0xB00001`..`0xB00006`
| Номер | Название | Назначение |
| -------- | ------------------- | ------------------------------------------------------------------------------------------------------------ |
| 0xB00001 | FZL_OPCODE_CMP_EQ | Снять два числа со стека, проверить на равенство и положить на стек результат |
| 0xB00002 | FZL_OPCODE_CMP_NEQ | Снять два числа со стека, проверить на неравенство и сохранить результат на стеке |
| 0xB00003 | FZL_OPCODE_CMP_LT | Снять два числа со стека, проверить, что второе меньше первого, и затем положить результат на стек |
| 0xB00004 | FZL_OPCODE_CMP_LTEQ | Снять два числа со стека, проверить, что второе меньше или равно первому, и затем положить результат на стек |
| 0xB00005 | FZL_OPCODE_CMP_GT | Снять два числа со стека, проверить, что второе больше первого, и затем положить результат на стек |
| 0xB00006 | FZL_OPCODE_CMP_GTEQ | Снять два числа со стека, проверить, что второе больше или равно первому, и затем положить результат на стек |
## Сравнение строк
Коды: `0xB10001`..`0xB10002`
| Номер | Название | Назначение |
| -------- | ---------------------- | ------------------------------------------------------------------------------ |
| 0xB10001 | FZL_OPCODE_CMP_EQ_STR | Снять две строки со стека, сравнить на равенство, положить результат на стек |
| 0xB10002 | FZL_OPCODE_CMP_NEQ_STR | Снять две строки со стека, сравнить на неравенство, положить результат на стек |
## Сравнение объектов
Коды: `0xB20001`..`0xB20002`
| Номер | Название | Назначение |
| -------- | ---------------------- | ------------------------------------------------------------------------------- |
| 0xB20001 | FZL_OPCODE_CMP_EQ_OBJ | Снять два объекта со стека, сравнить на равенство, положить результат на стек |
| 0xB20002 | FZL_OPCODE_CMP_NEQ_OBJ | Снять два объекта со стека, сравнить на неравенство, положить результат на стек |
## Бинарные логические операции
Коды: `0xC00001`..`0xC00002`
| Номер | Название | Назначение |
| -------- | -------------- | ------------------------------------------------------------------------ |
| 0xC00001 | FZL_OPCODE_AND | Взять два значения со стека и положить на стек результат логического И |
| 0xC00002 | FZL_OPCODE_OR | Взять два значения со стека и положить на стек результат логического ИЛИ |
## Унарные логические операции
Коды: `0xD00001`
| Номер | Название | Назначение |
| -------- | -------------- | ------------------------------------------------------------------ |
| 0xD00001 | FZL_OPCODE_NOT | Взять значение со стека и положить инвертированный булев результат |
+102
View File
@@ -0,0 +1,102 @@
# Стеки виртуальной машины
Во время работы, виртуальная машина FZLang полагается на три стека: данных, окружений и вызовов.
Каждый стек инициализируется как массив из 256 значений, а затем зануляется.
Основное свойство, присущее всем трём стекам - последнее значение является верхом стека и сниматься в первую очередь должно оно, декрементируя указатель на вершину стека.
## Стек данных
Стек данных представляет из себя массив из 256 элементов типа `fzl_value_t`.
Структура `fzl_value_t` - это контейнер для хранения значений, используемых во время работы программы.
```c
typedef struct
{
fzl_type_t type;
union
{
void *obj;
struct
{
const char *value;
size_t length;
} s;
char c;
bool b;
int8_t i8;
uint8_t u8;
int16_t i16;
uint16_t u16;
int32_t i32;
uint32_t u32;
int64_t i64;
uint64_t u64;
intptr_t i;
uintptr_t u;
} as;
} fzl_value_t;
```
Стек используется во время работы программы виртуальной машиной, чтобы писать и читать промежуточные вычисления.
К концу работы программы, стек должен быть пуст.
## Стек окружений
Чтобы определять лексическое окружение, в котором на данный момент производятся вычисления и выполняются команды, используется стек окружений.
Стек окружений - это массив из 256 элементов типа `fzl_vm_env_t`.
```c
typedef struct
{
size_t size;
size_t remainder;
fzl_vm_env_entry_t *table;
fzl_vm_object_t **owned_objects;
size_t owned_objects_remainder;
size_t owned_objects_count;
} fzl_vm_env_t;
```
Каждый элемент стека хранит таблицу локальных имён `fzl_vm_env_entry_t`, а также объекты, которые принадлежат текущему окружению.
Локальное имя - это контейнер, оборачивающий `fzl_value_t` и дающий ему имя:
```c
typedef struct
{
char *name;
fzl_value_t value;
} fzl_vm_env_entry_t;
```
Объект же представляет из себя массив полей, хранимых в этом объекте.
Количество элементов определется при аллокации объекта (опкод `FZL_OPCODE_ALLOC_OBJECT N`, подробнее - в [описании опкодов](./opcodes.md)).
```c
typedef struct
{
fzl_value_t *values;
size_t values_count;
} fzl_vm_object_t;
```
Создание окружения или выход из него происходит при опкодах `FZL_OPCODE_MK_ENVTABLE` и `FZL_OPCODE_RM_ENVTABLE` соответственно.
При этом, если компилятор видит возвращаемое из функции значение типа `FZL_TYPE_OBJECT`, то перед опкодами `FZL_OPCODE_RET` и `FZL_OPCODE_RM_ENVTABLE` он вставляет `FZL_OPCODE_ESCAPE_OBJECT`,
которое позволяет переместить владение объектом в родительское окружение.
## Стек возврата
Стек возврата хранит адреса инструкций при входе в функцию (через опкод `FZL_OPCODE_CALL`).
Опкод `FZL_OPCODE_RET` снимает адрес со стека вызовов и прыгает на снятый адрес.
Сам по себе стек представляет из себя 256 элементов типа `size_t`.
+31
View File
@@ -0,0 +1,31 @@
# Философия языка
Язык FZLang преследует две цели при разработке: "быть удобным, как инструмент, но всегда оставаться предсказуемым".
Вот, какими путями язык пытается достичь этой философии:
## В отношении к памяти и ресурсам
В средах с GC программист точно не знает, в какой момент сборщик мусора решит произвести очистку:
объект может висеть секундами после завершения метода.
А в момент очистки рантайм может устроить микропаузу, что критично для аудио, игр, сети или встроенных систем.
В модели scope-based RAII, которая используется в том числе в FZLang, память детерминирована.
Любой объект имеет область, которая им владеет, а как только область уничтожается - объект уничтожается вместе с ней.
Время отклика таком подходе равное и предсказуемое.
Ещё один плюс, который предоставляет модель scope-based RAII - это работа с ресурсами (сокеты, файлы).
Проблема освобождения ресурсов решается чистой семантикой области видимости: умирает скоуп - очищается всё, что к нему привязано.
И последним аргументом в сторону scope-based RAII стало отстутствие оверхеда на рантайм.
Полноценному GC для работы нужны карты памяти, фоновые потоки, барьеры чтения и записи.
В FZLang виртуальная машина весит очень мало, а вся память освобождается по ходу чтения инструкций.
## В отношении синтаксиса
Язык FZLang намеренно не упрощается некоторые детали синтаксиса.
Например, при обращении к экземпляру класса в его методах, использование `this` является обязательным, как и указание типов при декларациях, - это строгая часть.
Однако, объявление функции без возвращаемого типа позволяет опускать тип, так как писать на месте типа постоянно `void` было бы избыточно.
Таким образом, FZLang старается балансировать между синтаксисом, позволяющим оставаться языку детерминированным и упрощениями, которые действительно уместны.
+120
View File
@@ -0,0 +1,120 @@
# Вывод ошибок
Если во время работы программы происходит ошибка рантайма, то ВМ выводит своё состояние на момент остановки.
Среди выводимой информации есть:
1. Указатель на адрес инструкции;
2. Стек данных;
3. Стек окружений;
4. Стек вызовов.
## Указатель на адрес инструкции (IP)
Он может быть полезен для отладки, в паре с выводом опкодов программы.
Если подать в ВМ флаг `--debug` (`-d`) вместе с путём к скомпилированной программой, то ВМ выведет список опкодов программы без её запуска.
Выглядит вывод программы примерно так:
```
0000: MK_ENVTABLE
0001: JMP IP: 53
0003: MK_ENVTABLE
0004: LOADCONST #0 ('a')
0006: STORE
0007: LOADCONST #0 ('a')
0009: WRITE
0010: MK_ENVTABLE
0011: LOADCONST #0 ('a')
0013: LOOKUP_ENV
0014: GET_FIELD #0
...
```
Порядковый номер позволяет быстро найти инструкцию, на которой произошло исключение.
Полный список опкодов и их описание можно найти в документе [списка опкодов](../internals/opcodes.md).
## Стек данных
Стек данных выводится как список значений в стиле `[T] V`, где `T` - номер типа, `V` - значение.
Если значение является строкой или объектом, то в начале дописывается `str: V` и `ref: V` соответственно.
Вот, как выглядит пример вывода стека:
```
> Data stack:
> [2] str: 'this'
```
Важно учесть, что значения выводятся сверху вниз. То есть, первыми будут выведены самые свежие значения.
## Стек окружения
Стек окружения выводится как два списка: объекты во владении и локальные для окружения имена.
Пример окружения:
```
> Environments:
> [1] 0x55a9d8196a20:
> Owned objects (0/2):
> Local entries (3/4):
> [14] b = 4
> [1] a = ref 0x55a9d81969e0
> [0] this = 0
> [0] 0x55a9d8196930:
> Owned objects (1/2):
> ref: 0x55a9d81969e0 (entries: 1)
> Local entries (1/2):
> [1] sum = ref 0x55a9d81969e0
```
`> [N] A` - это сама таблица окружения.
`N` - это уровень таблицы, который тем выше, чем глубже само окружение.
`A` - это адрес таблицы в памяти.
Внутри идёт первый список - "Owned objects", с указанием количества элементов и аллоцированным размером массива `(<кол-во>/<размер>)`.
Перечисление объектов идёт форматом `ref: A (entries: N)`, где `A` - адрес объекта в памяти, `N` - количество хранимых полей.
Далее идёт список локальных имён - "Local entries", с таким же указанием количества элементов к аллоцированному размеру.
Формат элемента - `[T] N = V`, где `T` - номер типа, `N` - имя элемента, а `V` - его значение.
## Стек вызовов
На стеке вызовов лежат точки возврата, которые попадают на стек при инструкции `CALL` и съедаются инструкцией `RET`.
```
> Call stack:
> [0] 53
```
Формат записи простой: `[L] IP`, где `L` - уровень вложенности (больше = глубже), а `IP` - указатель на номер инструкции, на которую нужно прыгнуть после возврата.
## Полный вывод
Итого, полный вывод ошибки выглядит следующим образом:
```
===== ERROR =====
> Caused on IP: 21
> Data stack:
> [2] str: 'this'
> Environments:
> [1] 0x55a9d8196a20:
> Owned objects (0/2):
> Local entries (3/4):
> [14] b = 4
> [1] a = ref 0x55a9d81969e0
> [0] this = 0
> [0] 0x55a9d8196930:
> Owned objects (1/2):
> ref: 0x55a9d81969e0 (entries: 1)
> Local entries (1/2):
> [1] sum = ref 0x55a9d81969e0
> Call stack:
> [0] 53
Error: runtime error - expected 2 values in data stack
```
+61
View File
@@ -0,0 +1,61 @@
/* FZL (FromZero Language) syntax */
/* Version 1 */
/* Authors: */
/* - Samsonium (https://git.samsonium.dev/Samsonium) */
<program> ::= <_> <statement>+
<statement> ::= <class_stmt>
| <fn_stmt>
| <if_stmt>
| <block_stmt>
| <ret_stmt>
| <expr_stmt>
<_> ::= " "*
<ws> ::= " "+
<expr_stmt> ::= <expr> <_> ";" <_>
<block_stmt> ::= "{" <_> <statement>* "}" <_>
<ret_stmt> ::= "return" (<ws> <expr>)? <_> ";" <_>
<class_stmt> ::= "class" <ws> <name> <_> "{" <_> (<declaration> | <fn_stmt>)* "}" <_>
<fn_stmt> ::= "fn" <ws> <name> <_> "(" <_> (<param> (<_> "," <_> <param>)*)? ")" <_> <block_stmt>
<param> ::= <type> <ws> <name>
<if_stmt> ::= "if" <_> "(" <_> <expr> <_> ")" <_> <statement> (<_> "else" <ws> <statement>)?
/* Assign (lowest epxression priority, right-associative) */
<expr> ::= <assign_expr>
<assign_expr> ::= <new_expr> (<_> "=" <_> <assign_expr>)?
<new_expr> ::= ("new" <ws> <name>) | <or_expr>
<or_expr> ::= <and_expr> (<_> "||" <_> <and_expr>)*
<and_expr> ::= <cond_expr> (<_> "&&" <_> <cond_expr>)*
<cond_expr> ::= <term_expr> (<_> <cond_op> <_> <term_expr>)*
<cond_op> ::= "==" | "!=" | "<=" | ">=" | "<" | ">"
<term_expr> ::= <factor_expr> (<_> ("+" | "-") <_> <factor_expr>)*
<factor_expr> ::= <power_expr> (<_> ("*" | "/" | "%") <_> <power_expr>)*
<power_expr> ::= <unary_expr> (<_> "^" <_> <power_expr>)?
<unary_expr> ::= (("!" | "-" | "+") <_>)* <postfix_expr>
/* Postfix: chain of calls and dots */
<postfix_expr> ::= <primary_expr> (<call_suffix> | <member_suffix>)*
<call_suffix> ::= <_> "(" <_> (<expr> (<_> "," <_> <expr>)*)? <_> ")"
<member_suffix> ::= <_> "." <_> <name>
<primary_expr> ::= <literal>
| <declaration>
| "(" <_> <expr> <_> ")"
| <name>
<declaration> ::= <type> <ws> <name>
<type> ::= "int" | "uint" | "bool" | "char" | "str"
| "i8" | "i16" | "i32" | "i64"
| "u8" | "u16" | "u32" | "u64"
<literal> ::= <number> | <string_literal> | "true" | "false" | "null"
<name> ::= ([A-z] | "_") | ([A-z] | [0-9] | "_")*
<string_literal> ::= ('"' [^"]* '"') | ("'" [^']* "'")
+2
View File
@@ -0,0 +1,2 @@
# Грамматика (EBNF)
+9
View File
@@ -0,0 +1,9 @@
# Справочник
Справочные материалы по языку и среде выполнения FZLang.
- [Операторы](./operators.md)
- [Система типов](./types_system.md)
- [Грамматика (EBNF)](./grammar_ebnf.md)
- [Вывод ошибок](./errors_print.md)
+40
View File
@@ -0,0 +1,40 @@
# Операторы
FZLang поддерживает арифметические, логические и унарные операции.
Во время работы бинарные операции достают со стека два значения, и обратно кладут результат на стек.
Унарные операции достают одно значение со стека, а затем кладут результат обратно на стек.
## Арифметические операции
| Операция | Допустимые типы | Описание |
| -------- | --------------- | -------- |
| `N + N` | *все числовые* | Сложение двух операндов |
| `N - N` | *все числовые* | Вычитание правого операнда из левого |
| `N * N` | *все числовые* | Переменожение двух операндов |
| `N / N` | *все числовые* | Деление левого операнда на правый |
| `N % N` | *все числовые* | Получение остатка от деления левого операнда на правый |
| `N ^ N` | *все числовые* | Возведение левого операнда в степень правого |
| `-N` | *все числовые* | Инверсия операнда |
## Операции сравнения
| Операция | Допустимые типы | Описание |
| -------- | --------------- | -------- |
| `N == N` | *все числовые*, булевы, объекты, строки | Сравнить равенство операндов |
| `N != N` | *все числовые*, булевы, объекты, строки | Сравнить неравенство операндов |
| `N > N` | *все числовые* | Проверить, что левый операнд больше правого |
| `N >= N` | *все числовые* | Проверить, что левый операнд больше или равен правому |
| `N < N` | *все числовые* | Проверить, что левый операнд меньше правого |
| `N <= N` | *все числовые* | Проверить, что левый операнд меньше или равен правому |
| `!N` | булевы | Инвертировать операнд |
> Важно:
> В случае сравнения объектов (`==`, `!=`), сравниваются адреса, а не значения!
## Логические операции
| Операция | Допустимые типы | Описание |
| -------- | --------------- | -------- |
| `N && N` | булевы | Логическое **И** двух операндов |
| `N || N` | булевы | Логическое **ИЛИ** двух операндов |
+54
View File
@@ -0,0 +1,54 @@
# Система типов
FZLang имеет две основы для типов: примитивы и объекты.
Примитивами являются строки, числовые типы, булевы и скрытый тип - "объект".
Объектами на данный момент являются экземпляры классов, а также литерал `null`.
## Таблица типов
| Внутреннее имя | Синтаксическое имя | Описание |
| --------------- | ------------------ | -------- |
| FZL_TYPE_NULL | *отстутсвует* | *на данный момент не используется* |
| FZL_TYPE_OBJECT | *отстутсвует* | экземпляр класса или литерал `null` |
| FZL_TYPE_STRING | `str` | строка |
| FZL_TYPE_CHAR | `char` | *на данный момент не используется* |
| FZL_TYPE_BOOL | `bool` | булева |
| FZL_TYPE_INT8 | `i8` | 8-битное знаковое число |
| FZL_TYPE_UINT8 | `u8` | 8-битное беззнаковое число |
| FZL_TYPE_INT16 | `i16` | 16-битное знаковое число |
| FZL_TYPE_UINT16 | `u16` | 16-битное беззнаковое число |
| FZL_TYPE_INT32 | `i32` | 32-битное знаковое число |
| FZL_TYPE_UINT32 | `u32` | 32-битное беззнаковое число |
| FZL_TYPE_INT64 | `i64` | 64-битное знаковое число |
| FZL_TYPE_UINT64 | `u64` | 64-битное беззнаковое число |
| FZL_TYPE_INT | `int` | знаковое число размером с машинное слово |
| FZL_TYPE_UINT | `uint` | беззнаковое число размером с машинное слово |
## Совместимость
При проверке совместимости типов работают следующие правила:
1. Если оба типа - объекты (`FZL_TYPE_OBJECT`), то:
1. Если один из типов имеет имя класса `<nullptr>`, проверка **пройдена**;
2. Если оба типа имеют одинаковое имя класса, проверка **пройдена**;
3. В ином случае проверка **не пройдена**.
2. Если типы равны, проверка **пройдена**;
3. Если оба типа - числовые (`u8`..`uint`, `i8`..`int`), проверка **пройдена**;
4. Если ни одно правило из вышеперечисленных не сработало, то проверка **не пройдена**.
**пройдена** = типы совместимы, **не пройдена** - не совместимы.
## Работа в рантайме
Для примитивов (т.е., не `FZL_TYPE_OBJECT`), значения используются прямо.
Это значит, что они все операции, работающие со значениями используют для работы то значение, что лежит в контейнере на стеке.
Объектные типы хранятся на стеке как **ссылки на объект**.
Для них валидные такие операции, как:
- `FZL_OPCODE_ESCAPE_OBJECT` (эвакуация объекта в родительское окружение)
- `FZL_OPCODE_GET_FIELD N` (чтение поля с индексом `N` из объекта)
- `FZL_OPCODE_SET_FIELD N` (запись значения в поле объекта с индексом `N`)
+23
View File
@@ -0,0 +1,23 @@
# Встроенные функции
В языке FZLang предусмотрены встроеные функции, доступные для использования в любой программе.
## Выход `exit`
```
fn exit(uint code)
```
Функция принимает аргументом беззнаковое число и выходит из программы с указанным кодом.
## Вывод в stdout `printline`
```
fn printline(value)
```
Принимает аргументом любое значение и выводит в стандартный вывод указанное значение.
Для примитивы выводятся чисто, на основе типа.
Объекты выводятся как `obj<...>`, где `...` - перечисление значений полей.
Поля выводятся по типу и значению в формате: `T<V>`, где `T` - название типа, `V` - текущее значение.
+6
View File
@@ -0,0 +1,6 @@
# Стандартная библиотека
Описание функций, доступных в программах FZLang без дополнительных подключений.
- [Встроенные функции](./builtins.md)
+32
View File
@@ -0,0 +1,32 @@
// There are two ways to work with variables
// 1. Declare variable:
str someString = "world, hello!";
// 2. Change variable value:
someString = "hello, world!";
// Variables help you store values to use it later
int a = 2;
int b = a + a * a;
int c = a * b;
printline("Now we got 12:");
printline(c);
// Also, there are many different variable types to help your program
// work with many things:
int signedInteger;
uint unsignedInteger;
str string;
bool boolean;
i8 integer1; // 8-byte int
i16 integer2; // 16-byte int
i32 integer3; // 32-byte int
i64 integer4; // 64-byte int
u8 integer5; // unsigned 8-byte integer
u16 integer6; // unsigned 16-byte integer
u32 integer7; // unsigned 32-byte integer
u64 integer8; // unsigned 64-byte integer
+30
View File
@@ -0,0 +1,30 @@
// When you need to check some value
// a conditional statement might help you
int valueToCheck = 2 + 2;
if (valueToCheck == 4)
{
printline("Yay! 2 + 2 = 4");
}
// Conditional statement uses condition in parenthesis
// after `if` keyword and executes command(s) after
// if condition turns out to be true
// Otherwise, to execute block when condition turns
// out to be false, you can use `else` keyword:
int age = 30;
if (age <= 20)
printline("You're too young");
else
{
age = 20;
printline("You look too young!");
}
// NOTE: If you need to execute only one statement
// inside `if` or `else` block, then the curly
// braces can be omitted.
+30
View File
@@ -0,0 +1,30 @@
// If you have some code section which is
// duplicated in program several times
// then it means your program needs some
// functions!
fn doSomeWork()
{
printline("Some work");
}
// If the function makes some kind of
// calculations then you should specify
// the result type
fn sum(int a, int b) int
{
return a + b;
}
// The `return` keyword can be used to
// actually return the value from the
// function. Also, you can specify list
// of arguments that you will use inside
// the function
int first = 5;
int second = 6;
int result = sum(first, second);
printline(result);
-46
View File
@@ -1,46 +0,0 @@
class A
{
uint some;
}
fn doSome(A a) A
{
// NOTE: section below won't change `a.some` value
if (a.some < 1)
{
a.some = 1;
return a;
}
return a;
}
fn doSomeNew() A
{
if (1 == 1)
{
return new A;
}
}
A a = new A;
a.some = 0;
if (a.some != 0)
{
printline("Test failed (first a.some != 0)");
exit(1);
}
else printline(a);
a = doSome(a);
if (a.some != 1)
{
printline("Test failed (2nd a.some != 1)");
exit(1);
}
else printline(a);
A b = doSomeNew();
printline(b);
-36
View File
@@ -1,36 +0,0 @@
class Point
{
int x;
int y;
// Move point by [dx,dy]
fn move(int dx, int dy) {
this.x = this.x + dx;
this.y = this.y + dy;
}
}
Point obj = new Point;
obj.x = 1;
obj.y = 2;
printline("Current X,Y:");
printline(obj.x);
printline(obj.y);
// NOTE: WHY THE FUCK THIS FUCKING COMPILER REQUIRES ';' AFTER '='?!
// |
// v
obj.move(9, 8);
printline("X,Y after move:");
printline(obj.x);
printline(obj.y);
if (obj.x == 10 && obj.y == 10)
printline("Test passed");
else
{
printline("Test failed");
exit(1);
}
-3
View File
@@ -1,3 +0,0 @@
// I'm a comment
printline("Comment is skipped");;;;;
-20
View File
@@ -1,20 +0,0 @@
bool flag = true;
if (!flag)
{
printline("buhhh, flag is not true");
exit(1);
}
else if (2 + 2 >= 4)
{
printline ("two plus two is four");
}
else {
printline("gotcha");
exit(1);
}
{
printline("block after");
}
-32
View File
@@ -1,32 +0,0 @@
fn fib(int n) int
{
if (n <= 1)
return n;
return fib(n - 1) + fib(n - 2);
}
fn sum(uint a, uint b) uint
{
return a + b;
}
printline("Test #1: fibonacci");
int result = fib(7);
if (result == 13)
printline("Recurse test passed");
else
{
printline("Recurse test failed");
exit(1);
}
printline("Test #2: sum");
if (sum(2, 2) == 4)
printline("Sum test passed");
else
{
printline("Sum test failed");
exit(1);
}
-2
View File
@@ -1,2 +0,0 @@
printline('hello, world');
+41
View File
@@ -0,0 +1,41 @@
extern "libglfw" {
// Opaque types
type GLFWwindow handle;
type GLFWmonitor handle;
// Struct definition
type GLFWvidmode struct {
int width;
int height;
int redBits;
int greenBits;
int blueBits;
int refreshRate;
};
/// GLFWvidmode*
type GLFWvidmodeRef ref GLFWvidmode;
/// Create window with specified [width], [height] and [title]
fn glfw_mkWindow(
int width,
int height,
str title,
GLFWmonitor mon,
GLFWwindow shr
) GLFWwindow = "glfwCreateWindow";
/// Get videomode from monitor [mon]
fn glfw_getVideoMode(
GLFWmonitor mon
) GLFWvidmodeRef = "glfwGetVideoMode";
/// Get framebuffer [width] and [height]
fn glfw_getFrameBufferSize(
GLFWwindow win,
out int width,
out int height
) = "glfwGetFramebufferSize";
}
-1
View File
@@ -1 +0,0 @@
printline("");
-1
View File
@@ -1 +0,0 @@
printline("hello);
-46
View File
@@ -1,46 +0,0 @@
class Animal
{
str type;
str name;
int age;
fn copy() Animal
{
Animal cp = new Animal;
cp.type = this.type;
cp.name = this.name;
cp.age = this.age;
return cp;
}
}
Animal a = new Animal;
a.type = "Cow";
a.name = "Buryatka";
a.age = 5;
Animal b = a.copy();
b.name = "Gavryusha";
b.age = 10;
printline(a);
printline(b);
printline("Types are same:");
printline(a.type == b.type);
printline("Names are NOT the same");
printline(a.name != b.name);
printline("1st age less than 2nd");
printline(a.age < b.age);
if (a.type == b.type && a.name != b.name && a.age < b.age)
printline("Test passed");
else
{
printline("Test failed");
exit(1);
}
+86
View File
@@ -0,0 +1,86 @@
#ifndef FZL_EXTERN_H
#define FZL_EXTERN_H
#include <common/fzl_types.h>
#include <stdbool.h>
#include <stddef.h>
typedef enum
{
FZL_EXTERN_KIND_HANDLE,
FZL_EXTERN_KIND_STRUCT,
FZL_EXTERN_KIND_REF,
FZL_EXTERN_KIND_ALIAS,
} fzl_extern_kind_t;
// Type reference descriptor
typedef struct
{
bool is_primitive;
union
{
fzl_type_t primitive;
size_t type_slot;
};
} fzl_extern_type_ref_t;
// Structure field descriptor
typedef struct
{
char *name;
fzl_extern_type_ref_t type;
} fzl_extern_struct_field_t;
// Type descriptor
typedef struct
{
size_t lib_slot;
char *name;
fzl_extern_kind_t kind;
union
{
// Structure
struct
{
fzl_extern_struct_field_t *fields;
size_t fields_count;
} structure;
// Pointer (ref)
fzl_extern_type_ref_t ref_target;
// Alias
fzl_extern_type_ref_t alias_target;
};
} fzl_extern_type_t;
// Function argument descriptor
typedef struct
{
char *name;
fzl_extern_type_ref_t type;
bool is_out;
} fzl_extern_arg_t;
// Function descriptor
typedef struct
{
size_t lib_slot;
char *symbol_name;
char *used_name;
bool has_return;
fzl_extern_type_ref_t ret_type;
fzl_extern_arg_t *args;
size_t args_count;
} fzl_extern_fn_t;
// Library descriptor
typedef struct
{
char *name;
} fzl_extern_lib_t;
#endif // FZL_EXTERN_H
+7 -2
View File
@@ -14,12 +14,15 @@ typedef enum
FZL_OPCODE_STORE = 0xF10003, // Push variable to environment table FZL_OPCODE_STORE = 0xF10003, // Push variable to environment table
FZL_OPCODE_LOOKUP_ENV = 0xF10004, // Lookup name in environment table FZL_OPCODE_LOOKUP_ENV = 0xF10004, // Lookup name in environment table
FZL_OPCODE_WRITE = 0xF10005, // Wirte into variable FZL_OPCODE_WRITE = 0xF10005, // Wirte into variable
FZL_OPCODE_LOAD_LOCAL = 0xF10006, // Load local variable
FZL_OPCODE_STORE_LOCAL = 0xF10007, // Store local variable
FZL_OPCODE_JMP = 0xF20001, // Jump to specified IP address FZL_OPCODE_JMP = 0xF20001, // Jump to specified IP address
FZL_OPCODE_JZ = 0xF20002, // Jump if top value in dstack is zero FZL_OPCODE_JZ = 0xF20002, // Jump if top value in dstack is zero
FZL_OPCODE_CALL = 0xF30001, // Call function FZL_OPCODE_CALL = 0xF30001, // Call function
FZL_OPCODE_RET = 0xF30002, // Return from function FZL_OPCODE_RET = 0xF30002, // Return from function
FZL_OPCODE_CALL_EXTERN = 0xF30003, // Call external function
FZL_OPCODE_ALLOC_OBJECT = 0xFA0001, // Allocate N slots FZL_OPCODE_ALLOC_OBJECT = 0xFA0001, // Allocate N slots
FZL_OPCODE_GET_FIELD = 0xFA0002, // Get field N of an object at dstack FZL_OPCODE_GET_FIELD = 0xFA0002, // Get field N of an object at dstack
@@ -35,6 +38,8 @@ typedef enum
FZL_OPCODE_MOD = 0xA00006, // Get module of two numbers in dstack FZL_OPCODE_MOD = 0xA00006, // Get module of two numbers in dstack
FZL_OPCODE_NEG = 0xA00007, // Negotiate number in dstack FZL_OPCODE_NEG = 0xA00007, // Negotiate number in dstack
FZL_OPCODE_BUILD_STR = 0xA10001, // Build a string from N values in dstack
FZL_OPCODE_CMP_EQ = 0xB00001, // Equal FZL_OPCODE_CMP_EQ = 0xB00001, // Equal
FZL_OPCODE_CMP_NEQ = 0xB00002, // Not equal FZL_OPCODE_CMP_NEQ = 0xB00002, // Not equal
FZL_OPCODE_CMP_LT = 0xB00003, // Less than FZL_OPCODE_CMP_LT = 0xB00003, // Less than
+4
View File
@@ -6,6 +6,10 @@ typedef enum
FZL_TYPE_NULL, FZL_TYPE_NULL,
FZL_TYPE_OBJECT, FZL_TYPE_OBJECT,
FZL_TYPE_EXTERN_FN,
FZL_TYPE_HANDLE,
FZL_TYPE_STRUCT,
FZL_TYPE_STRING, FZL_TYPE_STRING,
FZL_TYPE_CHAR, FZL_TYPE_CHAR,
FZL_TYPE_BOOL, FZL_TYPE_BOOL,
+79
View File
@@ -0,0 +1,79 @@
#ifndef FZL_ANALYZER_H
#define FZL_ANALYZER_H
#include <common/fzl_types.h>
#include <compiler/fzl_parser.h>
#include <compiler/fzl_token.h>
#include <compiler/utils/fzl_symtable.h>
#include <stdbool.h>
#include <stddef.h>
// Analyzing context type
typedef enum
{
FZL_AN_CTX_TYPE_UNNECESSARY, // default type
FZL_AN_CTX_TYPE_FUNCTION, // when analyzing function
FZL_AN_CTX_TYPE_LOOP, // when analyzing loop
FZL_AN_CTX_TYPE_CLASS, // when analyzing class
} fzl_an_ctx_type_t;
// Inner node analyzing result descriptor
typedef struct
{
bool error;
fzl_type_t type;
char *classname;
} fzl_an_res_t;
// Current node context descriptor
typedef struct fzl_an_ctx
{
struct fzl_an_ctx *parent;
fzl_an_ctx_type_t type;
fzl_symtable_t *symbols;
size_t symbols_level;
union
{
fzl_node_t *fn;
char *classname;
};
} fzl_an_ctx_t;
// Analyzer descriptor
typedef struct
{
char *warnings[256];
size_t warnings_top;
char *errors[256];
size_t errors_top;
fzl_symtable_t *symbols[256];
size_t symbols_top;
char *alloc_names[256];
size_t alloc_names_top;
fzl_node_t *root;
fzl_an_ctx_t *ctx;
} fzl_analyzer_t;
// Allocate analyzer
fzl_analyzer_t *fzl_mkanalyzer(fzl_node_t *node);
// Push error to errors list
bool an_push_error(fzl_analyzer_t *analyzer, fzl_token_t *tok, char *error);
// Push warning to warnings list
bool an_push_warning(fzl_analyzer_t *analyzer, fzl_token_t *tok, char *warning);
// Analyze AST
void fzl_analyze(fzl_analyzer_t *analyzer);
// Free analyzer
void fzl_freeanalyzer(fzl_analyzer_t *analyzer);
#endif // FZL_ANALYZER_H
+77
View File
@@ -0,0 +1,77 @@
#ifndef FZL_GENERATOR_H
#define FZL_GENERATOR_H
#include <common/fzl_extern.h>
#include <compiler/fzl_parser.h>
#include <compiler/utils/fzl_const_pool.h>
#include <compiler/utils/fzl_ctable.h>
#include <compiler/utils/fzl_symtable.h>
#include <stdbool.h>
#include <stddef.h>
// Generator context type
typedef enum
{
FZL_GEN_CTX_TYPE_UNECCESSARY,
FZL_GEN_CTX_TYPE_FUNCTION,
FZL_GEN_CTX_TYPE_CLASS,
FZL_GEN_CTX_TYPE_LOOP
} fzl_gen_ctx_type_t;
// Generator context descriptor
typedef struct fzl_gen_ctx
{
fzl_gen_ctx_type_t type;
struct fzl_gen_ctx *parent;
size_t level;
union
{
fzl_node_t *function;
struct
{
size_t breaks[256];
size_t breaks_count;
} loop;
};
} fzl_gen_ctx_t;
// Generator descriptor
typedef struct
{
fzl_node_t *root;
fzl_const_hash_pool_t *consts;
fzl_gen_ctx_t *ctx;
size_t *ops;
size_t ops_count;
size_t ops_remainder;
fzl_symtable_t *syms[256];
size_t syms_top;
fzl_ctable_t *classes[64];
size_t classes_count;
fzl_extern_lib_t extern_libs[64];
size_t extern_libs_count;
fzl_extern_type_t extern_types[256];
size_t extern_types_count;
fzl_extern_fn_t extern_fns[256];
size_t extern_fns_count;
} fzl_gen_t;
// Allocate new generator
fzl_gen_t *fzl_mkgen(fzl_node_t *root);
// Run program generation
bool fzl_gen_run(fzl_gen_t *gen);
// Free generator
void fzl_freegen(fzl_gen_t *gen);
#endif // FZL_GENERATOR_H
+70 -1
View File
@@ -3,22 +3,40 @@
#include <compiler/fzl_reader.h> #include <compiler/fzl_reader.h>
#include <compiler/fzl_token.h> #include <compiler/fzl_token.h>
#include <stdbool.h>
#include <stddef.h> #include <stddef.h>
// AST node type // AST node type
typedef enum typedef enum
{ {
FZL_NODETYPE_TYPEDEF,
FZL_NODETYPE_HANDLE,
FZL_NODETYPE_ALIAS,
FZL_NODETYPE_STRUCT,
FZL_NODETYPE_REFERENCE,
FZL_NODETYPE_EXTERN_FN,
FZL_NODETYPE_EXTERN,
FZL_NODETYPE_BLOCK, FZL_NODETYPE_BLOCK,
FZL_NODETYPE_CALL,
FZL_NODETYPE_LITERAL, FZL_NODETYPE_LITERAL,
FZL_NODETYPE_FMT_STRING,
FZL_NODETYPE_IDENTIFIER, FZL_NODETYPE_IDENTIFIER,
FZL_NODETYPE_CONDITIONAL, FZL_NODETYPE_CONDITIONAL,
FZL_NODETYPE_FUNCTION, FZL_NODETYPE_FUNCTION,
FZL_NODETYPE_RETURN, FZL_NODETYPE_RETURN,
FZL_NODETYPE_CALL,
FZL_NODETYPE_CLASS, FZL_NODETYPE_CLASS,
FZL_NODETYPE_FIELD, FZL_NODETYPE_FIELD,
FZL_NODETYPE_CONSTRUCT, FZL_NODETYPE_CONSTRUCT,
FZL_NODETYPE_LOOP,
FZL_NODETYPE_BREAK,
FZL_NODETYPE_ADD, FZL_NODETYPE_ADD,
FZL_NODETYPE_SUB, FZL_NODETYPE_SUB,
FZL_NODETYPE_MUL, FZL_NODETYPE_MUL,
@@ -89,6 +107,7 @@ typedef struct fzl_node
{ {
fzl_token_t *type; fzl_token_t *type;
struct fzl_node *identifier; struct fzl_node *identifier;
bool is_out;
} var; } var;
// Conditional blocks // Conditional blocks
@@ -140,6 +159,56 @@ typedef struct fzl_node
{ {
struct fzl_node *name; struct fzl_node *name;
} construct; } construct;
// Loop
struct
{
struct fzl_node *init;
struct fzl_node *cond;
struct fzl_node *step;
struct fzl_node *body;
} loop;
// Extern library
struct
{
char *libname;
struct fzl_node **typedefs;
size_t typedefs_count;
struct fzl_node **fns;
size_t fns_count;
} ext;
// Type definition
struct
{
char *typename;
struct fzl_node *type;
} typedefinition;
// Structure
struct
{
char *original_name;
struct fzl_node **entries;
size_t entries_count;
} structure;
// External function
struct
{
char *name;
char *original_name;
fzl_token_t *type;
struct fzl_node **args;
size_t args_count;
} ext_fn;
}; };
} fzl_node_t; } fzl_node_t;
+45 -30
View File
@@ -8,53 +8,68 @@
// Token type // Token type
typedef enum typedef enum
{ {
FZL_TOKTYPE_CLASS, // class FZL_TOKTYPE_IMPORT, // `import`
FZL_TOKTYPE_NEW, // new FZL_TOKTYPE_EXTERN, // `extern`
FZL_TOKTYPE_TYPEDEF, // `type`
FZL_TOKTYPE_HANDLE, // `handle`
FZL_TOKTYPE_STRUCT, // `struct`
FZL_TOKTYPE_REFERENCE, // `ref`
FZL_TOKTYPE_OUT, // `out`
FZL_TOKTYPE_FUNCTION, // fn FZL_TOKTYPE_CLASS, // `class`
FZL_TOKTYPE_RETURN, // return FZL_TOKTYPE_NEW, // `new`
FZL_TOKTYPE_IF, // if FZL_TOKTYPE_FUNCTION, // `fn`
FZL_TOKTYPE_ELSE, // else FZL_TOKTYPE_RETURN, // `return`
FZL_TOKTYPE_FOR, // `for`
FZL_TOKTYPE_WHILE, // `while`
FZL_TOKTYPE_BREAK, // `break`
FZL_TOKTYPE_IF, // `if`
FZL_TOKTYPE_ELSE, // `else`
FZL_TOKTYPE_NAME, // abc FZL_TOKTYPE_NAME, // abc
FZL_TOKTYPE_TYPE, // i16 FZL_TOKTYPE_TYPE, // i16
FZL_TOKTYPE_DDASH, // // :D FZL_TOKTYPE_DDASH, // `//` :D
FZL_TOKTYPE_LPAREN, // ( FZL_TOKTYPE_LPAREN, // `(`
FZL_TOKTYPE_RPAREN, // ) FZL_TOKTYPE_RPAREN, // `)`
FZL_TOKTYPE_LBRACE, // { FZL_TOKTYPE_LBRACE, // `{`
FZL_TOKTYPE_RBRACE, // } FZL_TOKTYPE_RBRACE, // `}`
FZL_TOKTYPE_COMMA, // , FZL_TOKTYPE_COMMA, // `,`
FZL_TOKTYPE_DOT, // . FZL_TOKTYPE_DOT, // `.`
FZL_TOKTYPE_SEMICOLON, // ; FZL_TOKTYPE_SEMICOLON, // `;`
FZL_TOKTYPE_STR_LITERAL, // "" FZL_TOKTYPE_STR_LITERAL, // ""
FZL_TOKTYPE_NUM_LITERAL, // 123 FZL_TOKTYPE_NUM_LITERAL, // 123
FZL_TOKTYPE_BOOL_LITERAL, // true FZL_TOKTYPE_BOOL_LITERAL, // true
FZL_TOKTYPE_NULL_LITERAL, // null FZL_TOKTYPE_NULL_LITERAL, // null
FZL_TOKTYPE_DAMPERSAND, // && FZL_TOKTYPE_STR_FORMAT_START, // `$` before `"`
FZL_TOKTYPE_DVBAR, // || FZL_TOKTYPE_STR_FORMAT_END, // end of string formatting sequence
FZL_TOKTYPE_EQEQ, // == FZL_TOKTYPE_DAMPERSAND, // `&&`
FZL_TOKTYPE_REQ, // >= FZL_TOKTYPE_DVBAR, // `||`
FZL_TOKTYPE_LEQ, // <=
FZL_TOKTYPE_NEQ, // !=
FZL_TOKTYPE_LANGLE, // <
FZL_TOKTYPE_RANGLE, // >
FZL_TOKTYPE_NOT, // !
FZL_TOKTYPE_STAR, // * FZL_TOKTYPE_EQEQ, // `==`
FZL_TOKTYPE_SLASH, // / FZL_TOKTYPE_REQ, // `>=`
FZL_TOKTYPE_PLUS, // + FZL_TOKTYPE_LEQ, // `<=`
FZL_TOKTYPE_MINUS, // - FZL_TOKTYPE_NEQ, // `!=`
FZL_TOKTYPE_CARET, // ^ FZL_TOKTYPE_LANGLE, // `<`
FZL_TOKTYPE_PERCENT, // % FZL_TOKTYPE_RANGLE, // `>`
FZL_TOKTYPE_NOT, // `!`
FZL_TOKTYPE_EQ // = FZL_TOKTYPE_STAR, // `*`
FZL_TOKTYPE_SLASH, // `/`
FZL_TOKTYPE_PLUS, // `+`
FZL_TOKTYPE_MINUS, // `-`
FZL_TOKTYPE_CARET, // `^`
FZL_TOKTYPE_PERCENT, // `%`
FZL_TOKTYPE_EQ // `=`
} fzl_toktype_t; } fzl_toktype_t;
// Token descriptor // Token descriptor
-76
View File
@@ -1,76 +0,0 @@
#ifndef FZL_WALKER_H
#define FZL_WALKER_H
#include <common/fzl_opcodes.h>
#include <common/fzl_types.h>
#include <compiler/fzl_parser.h>
#include <compiler/utils/fzl_const_pool.h>
#include <compiler/utils/fzl_ctable.h>
#include <compiler/utils/fzl_symtable.h>
#include <stdbool.h>
#include <stddef.h>
// Type of context
typedef enum
{
FZL_WALK_CTX_TYPE_GLOBAL,
FZL_WALK_CTX_TYPE_FN,
FZL_WALK_CTX_TYPE_CLASS,
FZL_WALK_CTX_TYPE_ASSIGN
} fzl_walk_ctx_type_t;
// Internal walk function result
typedef struct
{
fzl_type_t type;
char *error_msg;
bool error;
char *classname;
} fzl_walk_res_t;
// Function context descriptor
typedef struct fzl_walk_ctx
{
fzl_walk_ctx_type_t type;
struct fzl_walk_ctx *parent;
fzl_node_t *fn;
size_t initial_env_lvl;
} fzl_walk_ctx_t;
// Walker descriptor
typedef struct
{
fzl_node_t *root;
fzl_const_hash_pool_t *consts;
uint64_t *ops;
size_t op_count;
size_t op_remainder;
fzl_symtable_t *syms[64];
size_t sym_top;
fzl_walk_ctx_t *ctx;
fzl_ctable_t *classes[64];
size_t classes_count;
} fzl_walker_t;
// Allocate new walker
fzl_walker_t *fzl_mkwalker(fzl_node_t *root);
// Walk throught the entire AST and build:
// 1. Constants table
// 2. Operations
int fzl_walk(fzl_walker_t *walker);
// Get last error message
char *fzl_walker_geterror();
// Free walker
void fzl_freewalker(fzl_walker_t *walker);
#endif // FZL_WALKER_H
+2 -2
View File
@@ -1,7 +1,7 @@
#ifndef FZL_WRITER_H #ifndef FZL_WRITER_H
#define FZL_WRITER_H #define FZL_WRITER_H
#include <compiler/fzl_walker.h> #include <compiler/fzl_gen.h>
#include <compiler/utils/fzl_const_pool.h> #include <compiler/utils/fzl_const_pool.h>
// File writer descriptor // File writer descriptor
@@ -14,7 +14,7 @@ typedef struct
fzl_writer_t *fzl_mkwriter(const char *filepath); fzl_writer_t *fzl_mkwriter(const char *filepath);
// Write out a file // Write out a file
int fzl_write(fzl_writer_t *writer, fzl_walker_t *walker); int fzl_write(fzl_writer_t *writer, fzl_gen_t *gen);
// Get last writer error message // Get last writer error message
char *fzl_writer_geterror(); char *fzl_writer_geterror();
+45
View File
@@ -0,0 +1,45 @@
#ifndef INTERNALS_ANALYZER_H
#define INTERNALS_ANALYZER_H
#include <common/fzl_types.h>
#include <compiler/fzl_analyzer.h>
#include <compiler/fzl_parser.h>
#include <compiler/fzl_token.h>
#include <stdbool.h>
extern char an_problem[256];
bool an_is_compatible(fzl_type_t a, fzl_type_t b, char *ac, char *bc);
char *an_typetostr(fzl_type_t type, char *classname);
fzl_type_t an_toktotype(fzl_analyzer_t *analyzer, fzl_token_t *tok,
bool *sym_found);
fzl_an_res_t analyze_common(fzl_analyzer_t *analyzer, fzl_node_t *node);
fzl_an_res_t analyze_call(fzl_analyzer_t *analyzer, fzl_node_t *call_node);
fzl_an_res_t analyze_identifier(fzl_analyzer_t *analyzer, fzl_node_t *id);
fzl_an_res_t analyze_conditional(fzl_analyzer_t *analyzer, fzl_node_t *cond);
fzl_an_res_t analyze_function(fzl_analyzer_t *analyzer, fzl_node_t *fn_node);
fzl_an_res_t analyze_block(fzl_analyzer_t *analyzer, fzl_node_t *block_node);
fzl_an_res_t analyze_return(fzl_analyzer_t *analyzer, fzl_node_t *ret_node);
fzl_an_res_t analyze_class(fzl_analyzer_t *analyzer, fzl_node_t *class_node);
fzl_an_res_t analyze_field(fzl_analyzer_t *analyzer, fzl_node_t *field);
fzl_an_res_t analyze_declaration(fzl_analyzer_t *analyzer, fzl_node_t *decl);
fzl_an_res_t analyze_construct(fzl_analyzer_t *analyzer, fzl_node_t *construct);
fzl_an_res_t analyze_loop(fzl_analyzer_t *analyzer, fzl_node_t *loop_node);
fzl_an_res_t analyze_break(fzl_analyzer_t *analyzer, fzl_node_t *break_node);
fzl_an_res_t analyze_binmath(fzl_analyzer_t *analyzer, fzl_node_t *bin_node);
fzl_an_res_t analyze_unarymath(fzl_analyzer_t *analyzer, fzl_node_t *unary);
fzl_an_res_t analyze_condition(fzl_analyzer_t *analyzer, fzl_node_t *cond_node);
fzl_an_res_t analyze_unarycond(fzl_analyzer_t *analyzer, fzl_node_t *unary);
fzl_an_res_t analyze_assign(fzl_analyzer_t *analyzer, fzl_node_t *assign_node);
fzl_an_res_t analyze_strfmt(fzl_analyzer_t *analyzer, fzl_node_t *strfmt_node);
fzl_an_res_t analyze_typedef(fzl_analyzer_t *analyzer, fzl_node_t *td_node);
fzl_an_res_t analyze_struct(fzl_analyzer_t *analyzer, fzl_node_t *struct_node);
fzl_an_res_t analyze_ref(fzl_analyzer_t *analyzer, fzl_node_t *ref_node);
fzl_an_res_t analyze_externfn(fzl_analyzer_t *analyzer, fzl_node_t *externfn);
fzl_an_res_t analyze_extern(fzl_analyzer_t *analyzer, fzl_node_t *extern_node);
fzl_an_ctx_t *an_nest_ctx(fzl_analyzer_t *analyzer, fzl_an_ctx_type_t type);
fzl_an_ctx_t *an_fin_ctx(fzl_analyzer_t *analyzer);
#endif // INTERNALS_ANALYZER_H
+69
View File
@@ -0,0 +1,69 @@
#ifndef INTERNALS_GEN_H
#define INTERNALS_GEN_H
#include <common/fzl_extern.h>
#include <common/fzl_opcodes.h>
#include <common/fzl_types.h>
#include <compiler/fzl_gen.h>
#include <compiler/fzl_parser.h>
#include <compiler/fzl_token.h>
#include <compiler/utils/fzl_const_pool.h>
#include <compiler/utils/fzl_symtable.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
typedef struct
{
bool error;
fzl_type_t type;
char *classname;
} gen_res_t;
extern gen_res_t gen_error;
extern gen_res_t gen_ok_null;
extern char *null_name;
extern fzl_extern_type_t type_buf;
bool gen_resolve_name(fzl_gen_t *gen, char *name, fzl_sym_t *sym);
bool gen_resolve_type(fzl_gen_t *gen, fzl_token_t *tok, fzl_type_t *t, char *c);
ssize_t gen_resolve_extern_type(fzl_gen_t *gen, char *name);
bool gen_is_types_compatible(fzl_type_t t1, fzl_type_t t2, char *c1, char *c2);
size_t gen_add_const(fzl_gen_t *gen, fzl_const_t c);
void gen_op(fzl_gen_t *gen, fzl_opcode_t opcode);
void gen_word(fzl_gen_t *gen, size_t word);
fzl_gen_ctx_t *gen_ctx(fzl_gen_t *gen, fzl_gen_ctx_type_t type,
fzl_node_t *node);
fzl_gen_ctx_t *gen_fin_ctx(fzl_gen_t *gen);
gen_res_t gen_common(fzl_gen_t *gen, fzl_node_t *node);
gen_res_t gen_extern(fzl_gen_t *gen, fzl_node_t *node);
gen_res_t gen_externfn(fzl_gen_t *gen, fzl_node_t *node);
gen_res_t gen_reference(fzl_gen_t *gen, fzl_node_t *node);
gen_res_t gen_struct(fzl_gen_t *gen, fzl_node_t *node);
gen_res_t gen_alias(fzl_gen_t *gen, fzl_node_t *node);
gen_res_t gen_handle(fzl_gen_t *gen, fzl_node_t *node);
gen_res_t gen_typedef(fzl_gen_t *gen, fzl_node_t *node);
gen_res_t gen_fmtstr(fzl_gen_t *gen, fzl_node_t *node);
gen_res_t gen_break(fzl_gen_t *gen, fzl_node_t *node);
gen_res_t gen_loop(fzl_gen_t *gen, fzl_node_t *node);
gen_res_t gen_class(fzl_gen_t *gen, fzl_node_t *node);
gen_res_t gen_construct(fzl_gen_t *gen, fzl_node_t *node);
gen_res_t gen_field(fzl_gen_t *gen, fzl_node_t *node);
gen_res_t gen_fn(fzl_gen_t *gen, fzl_node_t *node);
gen_res_t gen_return(fzl_gen_t *gen, fzl_node_t *node);
gen_res_t gen_unary_logical(fzl_gen_t *gen, fzl_node_t *node);
gen_res_t gen_compare(fzl_gen_t *gen, fzl_node_t *node);
gen_res_t gen_logical(fzl_gen_t *gen, fzl_node_t *node);
gen_res_t gen_condition(fzl_gen_t *gen, fzl_node_t *node);
gen_res_t gen_unary_math(fzl_gen_t *gen, fzl_node_t *node);
gen_res_t gen_declaration(fzl_gen_t *gen, fzl_node_t *node);
gen_res_t gen_identifier(fzl_gen_t *gen, fzl_node_t *node);
gen_res_t gen_block(fzl_gen_t *gen, fzl_node_t *node);
gen_res_t gen_call(fzl_gen_t *gen, fzl_node_t *node);
gen_res_t gen_literal(fzl_gen_t *gen, fzl_node_t *node);
gen_res_t gen_math(fzl_gen_t *gen, fzl_node_t *node);
gen_res_t gen_assign(fzl_gen_t *gen, fzl_node_t *node);
#endif // FZL_INTERNALS_GEN_H
+51
View File
@@ -0,0 +1,51 @@
#ifndef INTERNALS_PARSER_H
#define INTERNALS_PARSER_H
#include "compiler/fzl_parser.h"
#include "compiler/fzl_token.h"
extern char plast_err[256];
extern fzl_token_t *last_known_tok;
extern fzl_token_t null_tok;
extern fzl_token_t this_tok;
fzl_token_t *peek(fzl_parser_t *parser);
fzl_token_t *consume(fzl_parser_t *parser);
bool is(fzl_parser_t *parser, fzl_toktype_t type);
fzl_nodetype_t math_op_to_type(fzl_toktype_t type);
fzl_nodetype_t unary_op_to_type(fzl_toktype_t type);
fzl_node_t *pa_primary(fzl_parser_t *parser);
fzl_node_t *pa_postfix(fzl_parser_t *parser);
fzl_node_t *pa_unary(fzl_parser_t *parser);
fzl_node_t *pa_power(fzl_parser_t *parser);
fzl_node_t *pa_factor(fzl_parser_t *parser);
fzl_node_t *pa_term(fzl_parser_t *parser);
fzl_node_t *pa_cond(fzl_parser_t *parser);
fzl_node_t *pa_and(fzl_parser_t *parser);
fzl_node_t *pa_or(fzl_parser_t *parser);
fzl_node_t *pa_new(fzl_parser_t *parser);
fzl_node_t *pa_assignment(fzl_parser_t *parser);
fzl_node_t *pa_expression(fzl_parser_t *parser);
fzl_node_t *pa_struct(fzl_parser_t *parser);
fzl_node_t *pa_ref(fzl_parser_t *parser);
fzl_node_t *pa_typedef(fzl_parser_t *parser);
fzl_node_t *pa_externfn(fzl_parser_t *parser);
fzl_node_t *pa_class(fzl_parser_t *parser);
fzl_node_t *pa_function(fzl_parser_t *parser);
fzl_node_t *pa_return(fzl_parser_t *parser);
fzl_node_t *pa_if(fzl_parser_t *parser);
fzl_node_t *pa_block(fzl_parser_t *parser);
fzl_node_t *pa_for(fzl_parser_t *parser);
fzl_node_t *pa_while(fzl_parser_t *parser);
fzl_node_t *pa_loop(fzl_parser_t *parser);
fzl_node_t *pa_break(fzl_parser_t *parser);
fzl_node_t *pa_extern(fzl_parser_t *parser);
fzl_node_t *pa_statement(fzl_parser_t *parser);
fzl_node_t *pa_program(fzl_parser_t *parser);
#endif // INTERNALS_PARSER_H
+19
View File
@@ -0,0 +1,19 @@
#ifndef INTERNALS_READER_H
#define INTERNALS_READER_H
#include <compiler/fzl_reader.h>
#include <stddef.h>
extern char last_err[256];
size_t move(fzl_reader_t *reader, size_t len);
void rd_comment(fzl_reader_t *reader);
fzl_token_t *rd_name(fzl_reader_t *reader);
fzl_token_t *rd_string_literal(fzl_reader_t *reader);
fzl_token_t *rd_number(fzl_reader_t *reader);
fzl_token_t *rd_single(fzl_reader_t *reader, fzl_toktype_t type);
fzl_token_t *rd_double(fzl_reader_t *reader, fzl_toktype_t type);
fzl_token_t *rd_fmtstr(fzl_reader_t *reader);
#endif // INTERNALS_READER_H
+16
View File
@@ -0,0 +1,16 @@
#ifndef INTERNALS_WRITER_H
#define INTERNALS_WRITER_H
#include <common/fzl_extern.h>
#include <compiler/utils/fzl_const_pool.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
bool wr_consts(FILE *fd, fzl_const_hash_pool_t *consts);
bool wr_extern_libs(FILE *fd, fzl_extern_lib_t *libs, size_t libs_count);
bool wr_extern_types(FILE *fd, fzl_extern_type_t *types, size_t types_count);
bool wr_extern_fns(FILE *fd, fzl_extern_fn_t *fns, size_t fns_count);
bool wr_ops(FILE *fd, size_t *ops, size_t ops_count);
#endif // INTERNALS_WRITER_H
+12 -2
View File
@@ -2,6 +2,7 @@
#define FZL_SYMTABLE_H #define FZL_SYMTABLE_H
#include <common/fzl_types.h> #include <common/fzl_types.h>
#include <compiler/fzl_parser.h>
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
@@ -19,8 +20,12 @@ typedef struct
{ {
char *name; char *name;
fzl_type_t type; fzl_type_t type;
fzl_node_t *node;
size_t ip; size_t ip;
bool slot_set;
size_t slot;
char *classname; char *classname;
} fzl_sym_t; } fzl_sym_t;
@@ -30,6 +35,8 @@ typedef struct
size_t size; size_t size;
size_t remainder; size_t remainder;
size_t free_slot;
fzl_sym_t *symbols; fzl_sym_t *symbols;
} fzl_symtable_t; } fzl_symtable_t;
@@ -37,13 +44,16 @@ typedef struct
fzl_symtable_t *fzl_mksymt(); fzl_symtable_t *fzl_mksymt();
// Put new entry into symtable // Put new entry into symtable
fzl_symtable_res_t fzl_symt_put(fzl_symtable_t *symt, char *name, fzl_symtable_res_t fzl_symt_put(fzl_symtable_t *symt, fzl_node_t *node,
fzl_type_t type, uint64_t ip); char *name, fzl_type_t type, uint64_t ip);
// Assign class name to object type // Assign class name to object type
fzl_symtable_res_t fzl_symt_setobjclass(fzl_symtable_t *symt, char *name, fzl_symtable_res_t fzl_symt_setobjclass(fzl_symtable_t *symt, char *name,
char *classname); char *classname);
// Assign a local slot to a variable
int fzl_symt_setslot(fzl_symtable_t *symt, char *name);
// Get entry from specified symtable // Get entry from specified symtable
bool fzl_symt_get(fzl_symtable_t *symt, char *name, fzl_sym_t *res); bool fzl_symt_get(fzl_symtable_t *symt, char *name, fzl_sym_t *res);
+9 -9
View File
@@ -1,24 +1,24 @@
#ifndef FZL_VM_H #ifndef FZL_VM_H
#define FZL_VM_H #define FZL_VM_H
#include <common/fzl_value.h> #include <stdbool.h>
#include <compiler/utils/fzl_const_pool.h> // NOTE: move outside compiler scope to common
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include <vm/fzl_vm_env.h> #include <vm/fzl_vm_env.h>
#include <vm/fzl_vm_object.h> #include <vm/fzl_vm_extern.h>
#include <vm/fzl_vm_reader.h>
// VM descriptor // VM descriptor
typedef struct typedef struct
{ {
fzl_const_t *consts; fzl_vm_reader_t *reader;
size_t consts_count; fzl_vm_extern_t *prep_fns;
uint64_t *ops;
size_t ops_count;
size_t ip; size_t ip;
bool allow_infinite;
size_t max_iter;
// Data stack // Data stack
fzl_value_t dstack[256]; fzl_value_t dstack[256];
size_t dtop; size_t dtop;
@@ -33,7 +33,7 @@ typedef struct
} fzl_vm_t; } fzl_vm_t;
// Allocate FZL VM // Allocate FZL VM
fzl_vm_t *fzl_mkvm(const char *filepath); fzl_vm_t *fzl_mkvm(fzl_vm_reader_t *reader);
// Load and run code // Load and run code
int fzl_vm_run(fzl_vm_t *vm); int fzl_vm_run(fzl_vm_t *vm);
+35
View File
@@ -0,0 +1,35 @@
#ifndef FZL_VM_EXTERN_H
#define FZL_VM_EXTERN_H
#include <ffi.h>
#include <stddef.h>
#include <vm/fzl_vm_reader.h>
// Loaded extern function definition
typedef struct
{
void *func_ptr;
ffi_cif cif;
ffi_type **arg_types;
} fzl_vm_sym_t;
// Extern object descriptor
typedef struct
{
void **libs;
size_t libs_loaded;
fzl_vm_sym_t *syms;
size_t syms_loaded;
} fzl_vm_extern_t;
// Load externs
fzl_vm_extern_t *fzl_vm_init_externs(fzl_vm_reader_t *reader);
// Get last error from externs loader
char *fzl_vm_externs_geterror();
// Close and free externs
void fzl_vm_free_externs(fzl_vm_extern_t *ex);
#endif // FZL_VM_EXTERN_H
+43
View File
@@ -0,0 +1,43 @@
#ifndef FZL_VM_READER_H
#define FZL_VM_READER_H
#include <common/fzl_extern.h>
#include <common/fzl_value.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
// Binary file reader descriptor
typedef struct
{
char *filepath;
fzl_value_t *consts;
uint16_t consts_count;
size_t *ops;
size_t ops_count;
char **extern_libs;
uint16_t extern_libs_count;
fzl_extern_type_t *extern_types;
uint16_t extern_types_count;
fzl_extern_fn_t *extern_fns;
uint16_t extern_fns_count;
} fzl_vm_reader_t;
// Allocate binary file reader
fzl_vm_reader_t *fzl_vm_mkreader(char *filepath);
// Read the file
bool fzl_vm_read(fzl_vm_reader_t *reader);
// Get last error message from the reader
char *fzl_vm_getreadererror();
// Free file reader
void fzl_vm_freereader(fzl_vm_reader_t *reader);
#endif // FZL_VM_READER_H
+112
View File
@@ -0,0 +1,112 @@
#ifndef INTERNALS_OPCODE_H
#define INTERNALS_OPCODE_H
#include "vm/fzl_vm.h"
#include <stddef.h>
// Opcode handler descriptor
typedef int (*opcode_handler)(fzl_vm_t *);
extern char last_err[512];
extern size_t last_jmp_ip;
extern size_t total_jmp_on_same;
void print_object(fzl_vm_object_t *obj);
void print_err_data(fzl_vm_t *vm);
void escape_recursive(fzl_vm_env_t *current, fzl_vm_env_t *parent,
fzl_vm_object_t *obj);
// --- stack ---
extern const opcode_handler group_stack[];
int op_loadconst(fzl_vm_t *vm);
int op_call_builtin(fzl_vm_t *vm);
int op_pushnum(fzl_vm_t *vm);
int op_pushbool(fzl_vm_t *vm);
// --- context ---
extern const opcode_handler group_ctx[];
int op_mk_envtable(fzl_vm_t *vm);
int op_rm_envtable(fzl_vm_t *vm);
int op_store(fzl_vm_t *vm);
int op_lookup_env(fzl_vm_t *vm);
int op_write(fzl_vm_t *vm);
int op_load_local(fzl_vm_t *vm);
int op_store_local(fzl_vm_t *vm);
// --- flow ---
extern const opcode_handler group_flow[];
int op_jmp(fzl_vm_t *vm);
int op_jz(fzl_vm_t *vm);
// --- functions ---
extern const opcode_handler group_fn[];
int op_call(fzl_vm_t *vm);
int op_ret(fzl_vm_t *vm);
int op_call_extern(fzl_vm_t *vm);
// --- objects ---
extern const opcode_handler group_obj[];
int op_alloc_object(fzl_vm_t *vm);
int op_get_field(fzl_vm_t *vm);
int op_set_field(fzl_vm_t *vm);
int op_escape_object(fzl_vm_t *vm);
int op_dereference(fzl_vm_t *vm);
// --- math ---
extern const opcode_handler group_math[];
int op_add(fzl_vm_t *vm);
int op_sub(fzl_vm_t *vm);
int op_mul(fzl_vm_t *vm);
int op_div(fzl_vm_t *vm);
int op_pow(fzl_vm_t *vm);
int op_mod(fzl_vm_t *vm);
int op_neg(fzl_vm_t *vm);
// --- strings ---
extern const opcode_handler group_str[];
int op_build_str(fzl_vm_t *vm);
// --- conditions ---
extern const opcode_handler group_cmp[];
int op_cmp_eq(fzl_vm_t *vm);
int op_cmp_neq(fzl_vm_t *vm);
int op_cmp_lt(fzl_vm_t *vm);
int op_cmp_lteq(fzl_vm_t *vm);
int op_cmp_gt(fzl_vm_t *vm);
int op_cmp_gteq(fzl_vm_t *vm);
extern const opcode_handler group_strcmp[];
int op_cmp_eq_str(fzl_vm_t *vm);
int op_cmp_neq_str(fzl_vm_t *vm);
extern const opcode_handler group_objcmp[];
int op_cmp_eq_obj(fzl_vm_t *vm);
int op_cmp_neq_obj(fzl_vm_t *vm);
extern const opcode_handler group_and_or[];
int op_and(fzl_vm_t *vm);
int op_or(fzl_vm_t *vm);
extern const opcode_handler group_not[];
int op_not(fzl_vm_t *vm);
// ------------------------
extern const opcode_handler *const master_tbl[];
#endif // INTERNALS_OPCODE_H
+24
View File
@@ -0,0 +1,24 @@
#ifndef INTERNALS_VM_READER_H
#define INTERNALS_VM_READER_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <vm/fzl_vm_reader.h>
extern char internal_error[256];
bool vm_rd_char(FILE *fd, size_t count, char *outbuf);
bool vm_rd_u16(FILE *fd, size_t count, uint16_t *outbuf);
bool vm_rd_word(FILE *fd, size_t count, size_t *outbuf);
bool vm_rd_header(fzl_vm_reader_t *reader, FILE *fd);
bool vm_rd_sections_sz(fzl_vm_reader_t *reader, FILE *fd);
bool vm_rd_consts(fzl_vm_reader_t *reader, FILE *fd);
bool vm_rd_extern_libs(fzl_vm_reader_t *reader, FILE *fd);
bool vm_rd_extern_types(fzl_vm_reader_t *reader, FILE *fd);
bool vm_rd_extern_fns(fzl_vm_reader_t *reader, FILE *fd);
bool vm_rd_ops(fzl_vm_reader_t *reader, FILE *fd);
#endif // INTERNALS_VM_READER_H
+53
View File
@@ -0,0 +1,53 @@
#!/usr/bin/env python3
"""Run an FZL VM benchmark and report wall-clock timing statistics."""
import argparse
import statistics
import subprocess
import sys
import time
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--runs", type=int, default=7, help="measured runs")
parser.add_argument("--warmup", type=int, default=2, help="warm-up runs")
parser.add_argument("--max-iter", type=int, default=2_000_000)
parser.add_argument("--vm", required=True, help="path to the fz executable")
parser.add_argument("--program", required=True, help="compiled .fzb program")
args = parser.parse_args()
if args.runs < 1 or args.warmup < 0 or args.max_iter < 1:
parser.error("runs/max-iter must be positive and warmup must be non-negative")
command = [args.vm, "--max-iter", str(args.max_iter), args.program]
for _ in range(args.warmup):
result = subprocess.run(command, capture_output=True, text=True)
if result.returncode != 0:
sys.stderr.write(result.stdout)
sys.stderr.write(result.stderr)
raise SystemExit(result.returncode)
measurements = []
for _ in range(args.runs):
start = time.perf_counter()
result = subprocess.run(
command, capture_output=True, text=True
)
elapsed = time.perf_counter() - start
if result.returncode != 0:
sys.stderr.write(result.stdout)
sys.stderr.write(result.stderr)
raise SystemExit(result.returncode)
measurements.append(elapsed)
print(f"Runs: {args.runs} (warm-up: {args.warmup})")
print(f"Median: {statistics.median(measurements):.6f} s")
print(f"Mean: {statistics.mean(measurements):.6f} s")
print(f"Min: {min(measurements):.6f} s")
print(f"Max: {max(measurements):.6f} s")
if __name__ == "__main__":
main()
+51
View File
@@ -0,0 +1,51 @@
#include <common/fzl_types.h>
#include <compiler/fzl_analyzer.h>
#include <compiler/fzl_parser.h>
#include <compiler/internals/analyzer.h>
#include <stdio.h>
// Analyze assigns
fzl_an_res_t analyze_assign(fzl_analyzer_t *analyzer, fzl_node_t *assign_node)
{
if (!analyzer)
return (fzl_an_res_t){.error = true};
if (!assign_node || assign_node->type != FZL_NODETYPE_ASSIGN)
{
an_push_error(analyzer, NULL, "node is not assignment");
return (fzl_an_res_t){.error = true};
}
fzl_node_t *left = assign_node->binary_op.left;
fzl_an_res_t leftside = analyze_common(analyzer, left);
if (leftside.error)
return (fzl_an_res_t){.error = true};
fzl_an_res_t rightside =
analyze_common(analyzer, assign_node->binary_op.right);
if (rightside.error)
return (fzl_an_res_t){.error = true};
if (left->type != FZL_NODETYPE_DECLARATION &&
left->type != FZL_NODETYPE_IDENTIFIER && left->type != FZL_NODETYPE_FIELD)
{
an_push_error(analyzer, left->token,
"left side must be one of these: declaration, identifier or "
"field access");
return (fzl_an_res_t){.error = true};
}
if (!an_is_compatible(leftside.type, rightside.type, leftside.classname,
rightside.classname))
{
snprintf(an_problem, sizeof(an_problem),
"incompatible types - '%s' is expected, got '%s'",
an_typetostr(leftside.type, leftside.classname),
an_typetostr(rightside.type, rightside.classname));
an_push_error(analyzer, assign_node->binary_op.right->token, an_problem);
return (fzl_an_res_t){.error = true};
}
return (fzl_an_res_t){
.error = false, .type = FZL_TYPE_NULL, .classname = NULL};
}
+51
View File
@@ -0,0 +1,51 @@
#include <common/fzl_types.h>
#include <compiler/fzl_analyzer.h>
#include <compiler/fzl_parser.h>
#include <compiler/internals/analyzer.h>
#include <stdio.h>
// Analyze binary arithmetics
fzl_an_res_t analyze_binmath(fzl_analyzer_t *analyzer, fzl_node_t *bin_node)
{
if (!analyzer)
return (fzl_an_res_t){.error = true};
if (!bin_node || (bin_node->type != FZL_NODETYPE_ADD &&
bin_node->type != FZL_NODETYPE_SUB &&
bin_node->type != FZL_NODETYPE_MUL &&
bin_node->type != FZL_NODETYPE_DIV &&
bin_node->type != FZL_NODETYPE_POW &&
bin_node->type != FZL_NODETYPE_MOD))
{
an_push_error(analyzer, NULL, "node is not binary math operator");
return (fzl_an_res_t){.error = true};
}
fzl_an_res_t left = analyze_common(analyzer, bin_node->binary_op.left);
if (left.error)
return (fzl_an_res_t){.error = true};
fzl_an_res_t right = analyze_common(analyzer, bin_node->binary_op.right);
if (right.error)
return (fzl_an_res_t){.error = true};
if (left.type < FZL_TYPE_INT8)
{
snprintf(an_problem, sizeof(an_problem),
"integer type is expected, got '%s'",
an_typetostr(left.type, left.classname));
an_push_error(analyzer, bin_node->binary_op.left->token, an_problem);
return (fzl_an_res_t){.error = true};
}
if (right.type < FZL_TYPE_INT8)
{
snprintf(an_problem, sizeof(an_problem),
"integer type is expected, got '%s'",
an_typetostr(right.type, right.classname));
an_push_error(analyzer, bin_node->binary_op.right->token, an_problem);
return (fzl_an_res_t){.error = true};
}
return (fzl_an_res_t){.error = false, .type = left.type, .classname = NULL};
}
+99
View File
@@ -0,0 +1,99 @@
#include <common/fzl_types.h>
#include <compiler/fzl_analyzer.h>
#include <compiler/fzl_parser.h>
#include <compiler/fzl_token.h>
#include <compiler/internals/analyzer.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
// Analyze block
fzl_an_res_t analyze_block(fzl_analyzer_t *analyzer, fzl_node_t *block_node)
{
if (!analyzer)
return (fzl_an_res_t){.error = true};
if (!block_node || block_node->type != FZL_NODETYPE_BLOCK)
{
snprintf(an_problem, sizeof(an_problem), "node is not a block");
an_push_error(analyzer, NULL, an_problem);
return (fzl_an_res_t){.error = true};
}
an_nest_ctx(analyzer, FZL_AN_CTX_TYPE_UNNECESSARY);
bool has_return = false;
bool error = false;
for (size_t i = 0; i < block_node->list.entry_count; i++)
{
if (has_return)
an_push_warning(analyzer, block_node->list.entries[i]->token,
"this statement will never execute");
fzl_an_res_t entry_res =
analyze_common(analyzer, block_node->list.entries[i]);
if (entry_res.error)
error = true;
if (block_node->list.entries[i]->type == FZL_NODETYPE_RETURN)
has_return = true;
}
fzl_an_ctx_t *fnctx = analyzer->ctx;
while (fnctx != NULL)
{
if (fnctx && fnctx->type == FZL_AN_CTX_TYPE_FUNCTION)
break;
fnctx = fnctx->parent;
}
if (fnctx && fnctx->type == FZL_AN_CTX_TYPE_FUNCTION)
{
bool found = false;
fzl_type_t rettype = an_toktotype(analyzer, fnctx->fn->fn.type, &found);
char *classname = NULL;
char *rettype_str = an_typetostr(rettype, classname);
if (!found)
{
snprintf(an_problem, sizeof(an_problem), "symbol '%s' not found",
(char *)fnctx->fn->fn.type->value);
an_push_error(analyzer, fnctx->fn->fn.type, an_problem);
return (fzl_an_res_t){.error = true};
}
if (rettype == FZL_TYPE_OBJECT)
classname = (char *)fnctx->fn->fn.type->value;
if (rettype != FZL_TYPE_NULL)
{
if (!has_return || block_node->list.entry_count < 1)
{
snprintf(an_problem, sizeof(an_problem),
"'return <%s>' is expected in the function with non-null "
"return type",
rettype_str);
an_push_error(analyzer, block_node->token, an_problem);
return (fzl_an_res_t){.error = true};
}
fzl_node_t *last_node =
block_node->list.entries[block_node->list.entry_count - 1];
if (last_node->type != FZL_NODETYPE_RETURN)
{
// NOTE: replace with ghost walker to check if all branch return an
// expected value type
snprintf(an_problem, sizeof(an_problem),
"expected last statement in block to be 'return <%s>'",
rettype_str);
an_push_error(analyzer, last_node->token, an_problem);
return (fzl_an_res_t){.error = true};
}
}
}
an_fin_ctx(analyzer);
return (fzl_an_res_t){.error = error, .type = FZL_TYPE_NULL};
}
+36
View File
@@ -0,0 +1,36 @@
#include <common/fzl_types.h>
#include <compiler/fzl_analyzer.h>
#include <compiler/fzl_parser.h>
#include <compiler/internals/analyzer.h>
// Analyze break node
fzl_an_res_t analyze_break(fzl_analyzer_t *analyzer, fzl_node_t *break_node)
{
if (!analyzer)
return (fzl_an_res_t){.error = true};
if (!break_node || break_node->type != FZL_NODETYPE_BREAK)
{
an_push_error(analyzer, NULL, "node is not a break statement");
return (fzl_an_res_t){.error = true};
}
fzl_an_ctx_t *ctx = analyzer->ctx;
while (ctx != NULL)
{
if (ctx && ctx->type == FZL_AN_CTX_TYPE_LOOP)
break;
ctx = ctx->parent;
}
if (!ctx || ctx->type != FZL_AN_CTX_TYPE_LOOP)
{
an_push_error(analyzer, break_node->token,
"cannot use 'break' outside the loop");
return (fzl_an_res_t){.error = true};
}
return (fzl_an_res_t){
.error = false, .type = FZL_TYPE_NULL, .classname = NULL};
}
+278
View File
@@ -0,0 +1,278 @@
#include <common/fzl_types.h>
#include <compiler/fzl_analyzer.h>
#include <compiler/fzl_parser.h>
#include <compiler/fzl_token.h>
#include <compiler/internals/analyzer.h>
#include <compiler/utils/fzl_symtable.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *tmp_method_name = NULL;
char *tmp_fnname = NULL;
char *tmp_classname = NULL;
// Try to get symbol name from callee
char *resolve_callee(fzl_analyzer_t *analyzer, fzl_node_t *callee, bool *method)
{
*method = false;
if (callee->type == FZL_NODETYPE_IDENTIFIER)
return (char *)callee->token->value;
if (callee->type != FZL_NODETYPE_FIELD)
{
an_push_error(analyzer, callee->token, "unknown call format");
return NULL;
}
*method = true;
fzl_an_res_t parent_res = analyze_common(analyzer, callee->memb.parent);
if (parent_res.type != FZL_TYPE_OBJECT || !parent_res.classname)
return NULL;
char *fn_name = callee->memb.field->token->value;
tmp_fnname = fn_name;
tmp_classname = parent_res.classname;
size_t classname_len = strlen(parent_res.classname);
size_t methodname_len = strlen(fn_name);
tmp_method_name = malloc(classname_len + methodname_len + 2);
if (!tmp_method_name)
{
an_push_error(analyzer, callee->token, "out of memory");
return NULL;
}
strcpy(tmp_method_name, parent_res.classname);
tmp_method_name[classname_len] = '_';
strcpy(tmp_method_name + classname_len + 1, fn_name);
tmp_method_name[classname_len + methodname_len + 1] = 0;
return tmp_method_name;
}
// Analyze call node
fzl_an_res_t analyze_call(fzl_analyzer_t *analyzer, fzl_node_t *call_node)
{
if (!call_node || call_node->type != FZL_NODETYPE_CALL)
{
an_push_error(analyzer, NULL, "node is not call");
return (fzl_an_res_t){.error = true};
}
fzl_node_t *callee = call_node->call.callee;
bool is_method = false;
char *callee_name = resolve_callee(analyzer, callee, &is_method);
if (!callee_name)
{
an_push_error(analyzer, callee->token, "expression is not callable");
if (tmp_method_name != NULL)
{
free(tmp_method_name);
tmp_method_name = NULL;
}
return (fzl_an_res_t){.error = true};
}
// NOTE: move into `compiler/analyzer/builtins.c`
if (strcmp(callee_name, "printline") == 0 || strcmp(callee_name, "exit") == 0)
{
if (call_node->call.args_count != 1)
{
snprintf(an_problem, sizeof(an_problem),
"'%s' builtin should be called with 1 argument instead of %zu",
callee_name, call_node->call.args_count);
an_push_error(analyzer, call_node->token, an_problem);
if (tmp_method_name != NULL)
{
free(tmp_method_name);
tmp_method_name = NULL;
}
return (fzl_an_res_t){.error = true};
}
if (tmp_method_name != NULL)
{
free(tmp_method_name);
tmp_method_name = NULL;
}
return (fzl_an_res_t){
.error = false, .type = FZL_TYPE_NULL, .classname = NULL};
}
fzl_sym_t sym;
bool sym_found = false;
for (ssize_t i = analyzer->symbols_top - 1; i >= 0; i--)
{
if ((sym_found = fzl_symt_get(analyzer->symbols[i], callee_name, &sym)))
break;
}
if (!sym_found)
{
snprintf(an_problem, sizeof(an_problem), "cannot resolve symbol '%s'",
callee_name);
an_push_error(analyzer, call_node->token, an_problem);
if (tmp_method_name != NULL)
{
free(tmp_method_name);
tmp_method_name = NULL;
}
return (fzl_an_res_t){.error = true};
}
if (sym.node->type != FZL_NODETYPE_FUNCTION &&
sym.node->type != FZL_NODETYPE_EXTERN_FN)
{
snprintf(an_problem, sizeof(an_problem), "'%s' is not callable",
callee_name);
an_push_error(analyzer, call_node->token, an_problem);
if (tmp_method_name != NULL)
{
free(tmp_method_name);
tmp_method_name = NULL;
}
return (fzl_an_res_t){.error = true};
}
size_t offset = is_method ? 1 : 0;
size_t args_count = sym.node->type == FZL_NODETYPE_EXTERN_FN
? sym.node->ext_fn.args_count
: sym.node->fn.args_count;
if ((args_count - offset) != call_node->call.args_count)
{
if (is_method)
{
snprintf(an_problem, sizeof(an_problem),
"method '%s.%s' is expecting %zu argument(s), got %zu",
tmp_classname, tmp_fnname, args_count,
call_node->call.args_count - offset);
}
else
{
snprintf(an_problem, sizeof(an_problem),
"function '%s' is expecting %zu argument(s), got %zu",
callee_name, args_count, call_node->call.args_count - offset);
}
an_push_error(analyzer, call_node->token, an_problem);
if (tmp_method_name != NULL)
{
free(tmp_method_name);
tmp_method_name = NULL;
}
return (fzl_an_res_t){.error = true};
}
fzl_node_t **args = sym.node->type == FZL_NODETYPE_EXTERN_FN
? sym.node->ext_fn.args
: sym.node->fn.args;
for (size_t i = offset; i < sym.node->fn.args_count; i++)
{
fzl_node_t *arg = args[i];
bool found = false;
fzl_type_t req_type = an_toktotype(analyzer, arg->var.type, &found);
char *req_classname = NULL;
if (!found)
{
snprintf(an_problem, sizeof(an_problem), "symbol '%s' not found",
(char *)arg->var.type->value);
an_push_error(analyzer, arg->var.type, an_problem);
return (fzl_an_res_t){.error = true};
}
if (req_type == FZL_TYPE_OBJECT)
req_classname = arg->var.type->value;
fzl_an_res_t got =
analyze_common(analyzer, call_node->call.args[i - offset]);
if (got.error)
{
if (tmp_method_name != NULL)
{
free(tmp_method_name);
tmp_method_name = NULL;
}
return (fzl_an_res_t){.error = true};
}
if (!an_is_compatible(req_type, got.type, req_classname, got.classname))
{
if (is_method)
{
snprintf(an_problem, sizeof(an_problem),
"method '%s.%s' is expecting %zu argument to have type '%s', "
"got '%s'",
tmp_classname, tmp_fnname, i + 1 - offset,
an_typetostr(req_type, req_classname),
an_typetostr(got.type, got.classname));
}
else
{
snprintf(an_problem, sizeof(an_problem),
"function '%s' is expecting %zu argument to have type '%s', "
"got '%s'",
callee_name, i + 1 - offset,
an_typetostr(req_type, req_classname),
an_typetostr(got.type, got.classname));
}
an_push_error(analyzer, call_node->token, an_problem);
if (tmp_method_name != NULL)
{
free(tmp_method_name);
tmp_method_name = NULL;
}
return (fzl_an_res_t){.error = true};
}
}
bool type_found = false;
fzl_token_t *ret_tok = NULL;
void *ret_name = NULL;
if (sym.node->type == FZL_NODETYPE_EXTERN_FN)
{
ret_tok = sym.node->ext_fn.type;
ret_name = sym.node->ext_fn.name;
}
else
{
ret_tok = sym.node->fn.type;
ret_name = sym.node->fn.name;
}
fzl_type_t type = an_toktotype(analyzer, ret_tok, &type_found);
char *classname = sym.classname;
if (!type_found)
{
snprintf(an_problem, sizeof(an_problem), "symbol '%s' not found",
(char *)ret_name);
an_push_error(analyzer, ret_tok, an_problem);
return (fzl_an_res_t){.error = true};
}
if (tmp_method_name != NULL)
{
free(tmp_method_name);
tmp_method_name = NULL;
}
return (fzl_an_res_t){.error = false, .type = type, .classname = classname};
}
+72
View File
@@ -0,0 +1,72 @@
#include <common/fzl_types.h>
#include <compiler/fzl_analyzer.h>
#include <compiler/fzl_parser.h>
#include <compiler/internals/analyzer.h>
#include <compiler/utils/fzl_symtable.h>
#include <stdio.h>
// Analyze class declaration
fzl_an_res_t analyze_class(fzl_analyzer_t *analyzer, fzl_node_t *class_node)
{
if (!analyzer)
return (fzl_an_res_t){.error = true};
if (!class_node || class_node->type != FZL_NODETYPE_CLASS)
{
an_push_error(analyzer, NULL, "node is not class");
return (fzl_an_res_t){.error = true};
}
char *classname = (char *)class_node->class.name->value;
fzl_an_ctx_t *ctx = an_nest_ctx(analyzer, FZL_AN_CTX_TYPE_CLASS);
ctx->classname = classname;
fzl_sym_t sym;
bool sym_found = false;
for (ssize_t i = analyzer->symbols_top - 1; i >= 0; i--)
{
if ((sym_found = fzl_symt_get(analyzer->symbols[i], classname, &sym)))
break;
}
fzl_symtable_res_t res =
fzl_symt_put(analyzer->symbols[analyzer->symbols_top - 2], class_node,
classname, FZL_TYPE_NULL, 0);
if (res != FZL_SYMTABLE_RESULT_OK)
{
if (res == FZL_SYMTABLE_RESULT_EXISTS)
{
snprintf(an_problem, sizeof(an_problem),
"name '%s' already has been declared", classname);
an_push_error(analyzer, class_node->token, an_problem);
}
else
{
snprintf(an_problem, sizeof(an_problem), "cannot store name '%s'",
classname);
an_push_error(analyzer, class_node->token, an_problem);
}
an_fin_ctx(analyzer);
return (fzl_an_res_t){.error = true};
}
bool error = false;
for (size_t i = 0; i < class_node->class.fields_count; i++)
{
fzl_an_res_t res = analyze_common(analyzer, class_node->class.fields[i]);
if (res.error)
error = true;
}
for (size_t i = 0; i < class_node->class.methods_count; i++)
{
fzl_an_res_t res = analyze_common(analyzer, class_node->class.methods[i]);
if (res.error)
error = true;
}
an_fin_ctx(analyzer);
return (fzl_an_res_t){.error = error, .type = FZL_TYPE_NULL};
}
+161
View File
@@ -0,0 +1,161 @@
#include <common/fzl_types.h>
#include <compiler/fzl_analyzer.h>
#include <compiler/fzl_parser.h>
#include <compiler/fzl_token.h>
#include <compiler/internals/analyzer.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
// Analyzer main function
fzl_an_res_t analyze_common(fzl_analyzer_t *analyzer, fzl_node_t *node)
{
if (!analyzer || !node)
return (fzl_an_res_t){.error = true};
fzl_an_res_t res = {.error = false, .type = FZL_TYPE_NULL, .classname = NULL};
switch (node->type)
{
case FZL_NODETYPE_EXTERN:
res = analyze_extern(analyzer, node);
break;
case FZL_NODETYPE_EXTERN_FN:
res = analyze_externfn(analyzer, node);
break;
case FZL_NODETYPE_REFERENCE:
res = analyze_ref(analyzer, node);
break;
case FZL_NODETYPE_STRUCT:
res = analyze_struct(analyzer, node);
break;
case FZL_NODETYPE_ALIAS:
res.type = an_toktotype(analyzer, node->token, NULL);
break;
case FZL_NODETYPE_HANDLE:
res.type = FZL_TYPE_HANDLE;
break;
case FZL_NODETYPE_TYPEDEF:
res = analyze_typedef(analyzer, node);
break;
case FZL_NODETYPE_FMT_STRING:
res = analyze_strfmt(analyzer, node);
break;
case FZL_NODETYPE_ASSIGN:
res = analyze_assign(analyzer, node);
break;
case FZL_NODETYPE_NOT:
res = analyze_unarycond(analyzer, node);
break;
case FZL_NODETYPE_OR:
case FZL_NODETYPE_AND:
case FZL_NODETYPE_LESS:
case FZL_NODETYPE_LESS_EQUAL:
case FZL_NODETYPE_GREATER:
case FZL_NODETYPE_GREATER_EQUAL:
case FZL_NODETYPE_EQUAL:
case FZL_NODETYPE_NEQUAL:
res = analyze_condition(analyzer, node);
break;
case FZL_NODETYPE_UNARY_MINUS:
case FZL_NODETYPE_UNARY_PLUS:
res = analyze_unarymath(analyzer, node);
break;
case FZL_NODETYPE_ADD:
case FZL_NODETYPE_SUB:
case FZL_NODETYPE_MUL:
case FZL_NODETYPE_DIV:
case FZL_NODETYPE_POW:
case FZL_NODETYPE_MOD:
res = analyze_binmath(analyzer, node);
break;
case FZL_NODETYPE_BREAK:
res = analyze_break(analyzer, node);
break;
case FZL_NODETYPE_LOOP:
res = analyze_loop(analyzer, node);
break;
case FZL_NODETYPE_CONSTRUCT:
res = analyze_construct(analyzer, node);
break;
case FZL_NODETYPE_DECLARATION:
res = analyze_declaration(analyzer, node);
break;
case FZL_NODETYPE_FIELD:
res = analyze_field(analyzer, node);
break;
case FZL_NODETYPE_CLASS:
res = analyze_class(analyzer, node);
break;
case FZL_NODETYPE_RETURN:
res = analyze_return(analyzer, node);
break;
case FZL_NODETYPE_FUNCTION:
res = analyze_function(analyzer, node);
break;
case FZL_NODETYPE_CONDITIONAL:
res = analyze_conditional(analyzer, node);
break;
case FZL_NODETYPE_IDENTIFIER:
res = analyze_identifier(analyzer, node);
break;
case FZL_NODETYPE_BLOCK:
res = analyze_block(analyzer, node);
break;
case FZL_NODETYPE_CALL:
res = analyze_call(analyzer, node);
break;
case FZL_NODETYPE_LITERAL:
switch (node->token->type)
{
case FZL_TOKTYPE_STR_LITERAL:
res.type = FZL_TYPE_STRING;
break;
case FZL_TOKTYPE_NUM_LITERAL:
res.type = FZL_TYPE_INT;
break;
case FZL_TOKTYPE_BOOL_LITERAL:
res.type = FZL_TYPE_BOOL;
break;
case FZL_TOKTYPE_NULL_LITERAL:
res.type = FZL_TYPE_OBJECT;
res.classname = "null";
break;
default:
an_push_error(analyzer, node->token, "unknown literal type");
res.error = true;
break;
}
break;
}
return res;
}
+115
View File
@@ -0,0 +1,115 @@
#include <common/fzl_types.h>
#include <compiler/fzl_analyzer.h>
#include <compiler/fzl_parser.h>
#include <compiler/internals/analyzer.h>
#include <stdio.h>
// Analyze binary conditional nodes
fzl_an_res_t analyze_condition(fzl_analyzer_t *analyzer, fzl_node_t *cond_node)
{
if (!analyzer)
return (fzl_an_res_t){.error = true};
if (!cond_node || (cond_node->type != FZL_NODETYPE_OR &&
cond_node->type != FZL_NODETYPE_AND &&
cond_node->type != FZL_NODETYPE_LESS &&
cond_node->type != FZL_NODETYPE_LESS_EQUAL &&
cond_node->type != FZL_NODETYPE_GREATER &&
cond_node->type != FZL_NODETYPE_GREATER_EQUAL &&
cond_node->type != FZL_NODETYPE_EQUAL &&
cond_node->type != FZL_NODETYPE_NEQUAL))
{
an_push_error(analyzer, NULL, "node is not a binary conditional operator");
return (fzl_an_res_t){.error = true};
}
fzl_an_res_t left = analyze_common(analyzer, cond_node->binary_op.left);
if (left.error)
return (fzl_an_res_t){.error = true};
fzl_an_res_t right = analyze_common(analyzer, cond_node->binary_op.right);
if (right.error)
return (fzl_an_res_t){.error = true};
if (cond_node->type == FZL_NODETYPE_OR || cond_node->type == FZL_NODETYPE_AND)
{
if (left.type != FZL_TYPE_BOOL)
an_push_warning(analyzer, cond_node->binary_op.left->token,
"left operand is not resulting with boolean type");
if (right.type != FZL_TYPE_BOOL)
an_push_warning(analyzer, cond_node->binary_op.right->token,
"right operand is not resulting with boolean type");
}
else if (cond_node->type == FZL_NODETYPE_LESS ||
cond_node->type == FZL_NODETYPE_LESS_EQUAL ||
cond_node->type == FZL_NODETYPE_GREATER ||
cond_node->type == FZL_NODETYPE_GREATER_EQUAL)
{
if (left.type < FZL_TYPE_INT8)
{
an_push_error(analyzer, cond_node->binary_op.left->token,
"left operand must have integer type");
return (fzl_an_res_t){.error = true};
}
if (left.type < FZL_TYPE_INT8)
{
an_push_error(analyzer, cond_node->binary_op.left->token,
"right operand must have integer type");
return (fzl_an_res_t){.error = true};
}
}
else if (left.type >= FZL_TYPE_INT8)
{
if (right.type < FZL_TYPE_INT8)
{
an_push_error(analyzer, cond_node->binary_op.right->token,
"right operand must have integer type");
return (fzl_an_res_t){.error = true};
}
return (fzl_an_res_t){
.error = false, .type = FZL_TYPE_BOOL, .classname = NULL};
}
else if (left.type == FZL_TYPE_OBJECT)
{
if (!left.classname || !right.classname)
{
an_push_error(analyzer, cond_node->binary_op.left->token,
"unhandled error :(");
return (fzl_an_res_t){.error = true};
}
if (!an_is_compatible(left.type, right.type, left.classname,
right.classname))
{
snprintf(an_problem, sizeof(an_problem), "'%s' and '%s' are incompatible",
an_typetostr(left.type, left.classname),
an_typetostr(right.type, right.classname));
an_push_error(analyzer, cond_node->binary_op.right->token, an_problem);
return (fzl_an_res_t){.error = true};
}
}
else if (left.type == FZL_TYPE_STRING)
{
if (right.type != FZL_TYPE_STRING)
{
an_push_error(analyzer, cond_node->binary_op.right->token,
"right operand must be a string");
return (fzl_an_res_t){.error = true};
}
}
else if (!an_is_compatible(left.type, right.type, left.classname,
right.classname))
{
snprintf(an_problem, sizeof(an_problem),
"equality not supported between %s and %s",
an_typetostr(left.type, left.classname),
an_typetostr(right.type, right.classname));
return (fzl_an_res_t){.error = true};
}
return (fzl_an_res_t){
.error = false, .type = FZL_TYPE_BOOL, .classname = NULL};
}
+45
View File
@@ -0,0 +1,45 @@
#include <common/fzl_types.h>
#include <compiler/fzl_analyzer.h>
#include <compiler/fzl_parser.h>
#include <compiler/fzl_token.h>
#include <compiler/internals/analyzer.h>
#include <stdio.h>
// Analyze conditional statement
fzl_an_res_t analyze_conditional(fzl_analyzer_t *analyzer, fzl_node_t *cond)
{
if (!analyzer)
return (fzl_an_res_t){.error = true};
if (!cond || cond->type != FZL_NODETYPE_CONDITIONAL)
{
an_push_error(analyzer, NULL, "provided node is a condition");
return (fzl_an_res_t){.error = true};
}
fzl_an_res_t cond_res = analyze_common(analyzer, cond->cond.condition);
fzl_an_res_t true_res = analyze_common(analyzer, cond->cond.true_block);
if (cond->cond.else_block)
{
fzl_an_res_t else_res = analyze_common(analyzer, cond->cond.else_block);
if (else_res.error)
return (fzl_an_res_t){.error = true};
}
if (cond_res.error || true_res.error)
return (fzl_an_res_t){.error = true};
if (cond_res.type != FZL_TYPE_BOOL)
an_push_warning(analyzer, cond->cond.condition->token,
"resulting type is not bool");
if (cond->cond.condition->token->type == FZL_TOKTYPE_BOOL_LITERAL)
{
snprintf(an_problem, sizeof(an_problem), "condition is always %s",
(char *)cond->cond.condition->token->value);
an_push_warning(analyzer, cond->cond.condition->token, an_problem);
}
return (fzl_an_res_t){.error = false, .type = FZL_TYPE_NULL};
}
+39
View File
@@ -0,0 +1,39 @@
#include <common/fzl_types.h>
#include <compiler/fzl_analyzer.h>
#include <compiler/fzl_parser.h>
#include <compiler/internals/analyzer.h>
#include <compiler/utils/fzl_symtable.h>
#include <stdbool.h>
#include <stdio.h>
// Analyze class construct
fzl_an_res_t analyze_construct(fzl_analyzer_t *analyzer, fzl_node_t *construct)
{
if (!analyzer)
return (fzl_an_res_t){.error = true};
if (!construct || construct->type != FZL_NODETYPE_CONSTRUCT)
{
an_push_error(analyzer, NULL, "node is not a construct");
return (fzl_an_res_t){.error = true};
}
char *classname = (char *)construct->construct.name->token->value;
fzl_sym_t sym;
bool sym_found = false;
for (ssize_t i = analyzer->symbols_top - 1; i >= 0; i--)
{
if ((sym_found = fzl_symt_get(analyzer->symbols[i], classname, &sym)))
break;
}
if (!sym_found)
{
snprintf(an_problem, sizeof(an_problem), "class '%s' not found", classname);
an_push_error(analyzer, construct->token, an_problem);
return (fzl_an_res_t){.error = true};
}
return (fzl_an_res_t){
.error = false, .type = FZL_TYPE_OBJECT, .classname = classname};
}
+76
View File
@@ -0,0 +1,76 @@
#include <common/fzl_types.h>
#include <compiler/fzl_analyzer.h>
#include <compiler/fzl_parser.h>
#include <compiler/fzl_token.h>
#include <compiler/internals/analyzer.h>
#include <compiler/utils/fzl_symtable.h>
#include <stdbool.h>
#include <stdio.h>
// Analyze variable/field/argument declaration
fzl_an_res_t analyze_declaration(fzl_analyzer_t *analyzer, fzl_node_t *decl)
{
if (!analyzer)
return (fzl_an_res_t){.error = true};
if (!decl || decl->type != FZL_NODETYPE_DECLARATION)
{
an_push_error(analyzer, NULL, "node is not a declaration");
return (fzl_an_res_t){.error = true};
}
char *name = (char *)decl->var.identifier->token->value;
bool type_found = false;
fzl_type_t type = an_toktotype(analyzer, decl->var.type, &type_found);
if (!type_found)
{
snprintf(an_problem, sizeof(an_problem), "symbol '%s' not found",
(char *)decl->var.type->value);
an_push_error(analyzer, decl->var.type, an_problem);
return (fzl_an_res_t){.error = true};
}
char *classname = NULL;
if (type == FZL_TYPE_OBJECT)
classname = decl->var.type->value;
fzl_symtable_res_t res = fzl_symt_put(
analyzer->symbols[analyzer->symbols_top - 1], decl, name, type, 0);
if (res != FZL_SYMTABLE_RESULT_OK)
{
if (res == FZL_SYMTABLE_RESULT_EXISTS)
snprintf(an_problem, sizeof(an_problem), "'%s' already has been declared",
name);
else
snprintf(an_problem, sizeof(an_problem), "cannot store '%s'", name);
an_push_error(analyzer, decl->token, an_problem);
return (fzl_an_res_t){.error = true};
}
if (type == FZL_TYPE_OBJECT)
{
fzl_sym_t class;
bool class_found = false;
for (ssize_t i = analyzer->symbols_top - 1; i >= 0; i--)
{
if ((class_found = fzl_symt_get(analyzer->symbols[i],
decl->var.type->value, &class)))
break;
}
if (!class_found || class.node->type != FZL_NODETYPE_CLASS)
{
snprintf(an_problem, sizeof(an_problem), "class '%s' does not exists",
(char *)decl->var.type->value);
an_push_error(analyzer, decl->var.type, an_problem);
return (fzl_an_res_t){.error = true};
}
fzl_symt_setobjclass(analyzer->symbols[analyzer->symbols_top - 1], name,
classname);
}
return (fzl_an_res_t){.error = false, .type = type, .classname = classname};
}
+50
View File
@@ -0,0 +1,50 @@
#include <common/fzl_types.h>
#include <compiler/fzl_analyzer.h>
#include <compiler/fzl_parser.h>
#include <compiler/internals/analyzer.h>
#include <stddef.h>
#include <stdio.h>
// Analyze extern block definition
fzl_an_res_t analyze_extern(fzl_analyzer_t *analyzer, fzl_node_t *extern_node)
{
if (!analyzer)
return (fzl_an_res_t){.error = true};
if (!extern_node || extern_node->type != FZL_NODETYPE_EXTERN)
{
an_push_error(analyzer, NULL, "node is not an extern block");
return (fzl_an_res_t){.error = true};
}
if (extern_node->ext.typedefs_count < 1 && extern_node->ext.fns_count < 1)
an_push_warning(analyzer, extern_node->token, "extern is empty");
for (size_t i = 0; i < extern_node->ext.typedefs_count; i++)
{
fzl_node_t *node = extern_node->ext.typedefs[i];
fzl_an_res_t res = analyze_common(analyzer, node);
if (res.error)
return (fzl_an_res_t){.error = true};
if (node->type != FZL_NODETYPE_TYPEDEF &&
node->type != FZL_NODETYPE_EXTERN_FN)
{
snprintf(an_problem, sizeof(an_problem),
"unexpected statement '%s' inside extern block",
(char *)node->token->value);
an_push_error(analyzer, node->token, an_problem);
return (fzl_an_res_t){.error = true};
}
}
for (size_t i = 0; i < extern_node->ext.fns_count; i++)
{
fzl_an_res_t res = analyze_common(analyzer, extern_node->ext.fns[i]);
if (res.error)
return (fzl_an_res_t){.error = true};
}
return (fzl_an_res_t){
.error = false, .type = FZL_TYPE_NULL, .classname = NULL};
}
+79
View File
@@ -0,0 +1,79 @@
#include <common/fzl_types.h>
#include <compiler/fzl_analyzer.h>
#include <compiler/fzl_parser.h>
#include <compiler/fzl_token.h>
#include <compiler/internals/analyzer.h>
#include <compiler/utils/fzl_symtable.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
// Analyze external function definition
fzl_an_res_t analyze_externfn(fzl_analyzer_t *analyzer, fzl_node_t *externfn)
{
if (!analyzer)
return (fzl_an_res_t){.error = true};
if (!externfn || externfn->type != FZL_NODETYPE_EXTERN_FN)
{
an_push_error(analyzer, NULL, "node is not an external function");
return (fzl_an_res_t){.error = true};
}
an_nest_ctx(analyzer, FZL_AN_CTX_TYPE_UNNECESSARY);
for (size_t i = 0; i < externfn->ext_fn.args_count; i++)
{
fzl_an_res_t res = analyze_common(analyzer, externfn->ext_fn.args[i]);
if (res.error)
return (fzl_an_res_t){.error = true};
}
if (externfn->ext_fn.type->type == FZL_TOKTYPE_NAME)
{
fzl_sym_t sym;
bool found = false;
for (ssize_t i = analyzer->symbols_top - 1; i >= 0; i--)
{
if ((found = fzl_symt_get(analyzer->symbols[i],
(char *)externfn->ext_fn.type->value, &sym)))
break;
}
if (!found)
{
snprintf(an_problem, sizeof(an_problem),
"cannot find '%s' used as return type",
(char *)externfn->ext_fn.type->value);
an_push_error(analyzer, externfn->ext_fn.type, an_problem);
return (fzl_an_res_t){.error = true};
}
}
fzl_symtable_res_t wrres =
fzl_symt_put(analyzer->symbols[0], externfn,
(char *)externfn->ext_fn.name, FZL_TYPE_EXTERN_FN, 0);
if (wrres != FZL_SYMTABLE_RESULT_OK)
{
if (wrres == FZL_SYMTABLE_RESULT_EXISTS)
{
snprintf(an_problem, sizeof(an_problem),
"symbol '%s' already has been declared", externfn->ext_fn.name);
an_push_error(analyzer, externfn->token, an_problem);
return (fzl_an_res_t){.error = true};
}
else
{
snprintf(an_problem, sizeof(an_problem), "cannot store symbol '%s'",
externfn->ext_fn.name);
}
an_push_error(analyzer, externfn->token, an_problem);
return (fzl_an_res_t){.error = true};
}
an_fin_ctx(analyzer);
return (fzl_an_res_t){
.error = false, .type = FZL_TYPE_EXTERN_FN, .classname = NULL};
}
+90
View File
@@ -0,0 +1,90 @@
#include <common/fzl_types.h>
#include <compiler/fzl_analyzer.h>
#include <compiler/fzl_parser.h>
#include <compiler/internals/analyzer.h>
#include <compiler/utils/fzl_symtable.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
// Analyze field access
fzl_an_res_t analyze_field(fzl_analyzer_t *analyzer, fzl_node_t *field)
{
if (!analyzer)
return (fzl_an_res_t){.error = true};
if (!field || field->type != FZL_NODETYPE_FIELD)
{
an_push_error(analyzer, NULL, "node is not field access");
return (fzl_an_res_t){.error = true};
}
fzl_an_res_t parent = analyze_common(analyzer, field->memb.parent);
if (parent.error)
return (fzl_an_res_t){.error = true};
if (parent.type != FZL_TYPE_OBJECT || parent.classname == NULL)
{
snprintf(an_problem, sizeof(an_problem), "access to non-object value (%s)",
an_typetostr(parent.type, parent.classname));
an_push_error(analyzer, field->token, an_problem);
return (fzl_an_res_t){.error = true};
}
char *field_name = field->memb.field->token->value;
fzl_sym_t sym;
bool sym_found = false;
for (ssize_t i = analyzer->symbols_top - 1; i >= 0; i--)
{
if ((sym_found =
fzl_symt_get(analyzer->symbols[i], parent.classname, &sym)))
break;
}
if (!sym_found)
{
snprintf(an_problem, sizeof(an_problem), "class '%s' not found",
parent.classname);
an_push_error(analyzer, field->parent->token, an_problem);
return (fzl_an_res_t){.error = true};
}
fzl_node_t *field_node = NULL;
for (size_t i = 0; i < sym.node->class.fields_count; i++)
{
if (strcmp(sym.node->class.fields[i]->var.identifier->token->value,
field_name) == 0)
{
field_node = sym.node->class.fields[i];
break;
}
}
if (field_node == NULL)
{
snprintf(an_problem, sizeof(an_problem), "field '%s.%s' does not exists",
parent.classname, field_name);
an_push_error(analyzer, field->memb.field->token, an_problem);
return (fzl_an_res_t){.error = true};
}
bool type_found = false;
fzl_type_t field_type =
an_toktotype(analyzer, field_node->var.type, &type_found);
if (!type_found)
{
snprintf(an_problem, sizeof(an_problem), "symbol '%s' not found",
(char *)field_node->var.type->value);
an_push_error(analyzer, field_node->var.type, an_problem);
return (fzl_an_res_t){.error = true};
}
char *classname = NULL;
if (field_type == FZL_TYPE_OBJECT)
classname = field_node->var.type->value;
return (fzl_an_res_t){
.error = false, .type = field_type, .classname = classname};
}
+139
View File
@@ -0,0 +1,139 @@
#include <common/fzl_types.h>
#include <compiler/fzl_analyzer.h>
#include <compiler/fzl_parser.h>
#include <compiler/internals/analyzer.h>
#include <compiler/utils/fzl_symtable.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
fzl_an_res_t analyze_function(fzl_analyzer_t *analyzer, fzl_node_t *fn_node)
{
if (!analyzer)
return (fzl_an_res_t){.error = true};
if (!fn_node || fn_node->type != FZL_NODETYPE_FUNCTION)
{
an_push_error(analyzer, NULL, "node is not a function");
return (fzl_an_res_t){.error = true};
}
fzl_an_ctx_t *fnctx = an_nest_ctx(analyzer, FZL_AN_CTX_TYPE_FUNCTION);
fnctx->fn = fn_node;
for (size_t i = 0; i < fn_node->fn.args_count; i++)
{
fzl_an_res_t res = analyze_common(analyzer, fn_node->fn.args[i]);
if (res.error)
return (fzl_an_res_t){.error = true};
}
fzl_an_ctx_t *ctx = analyzer->ctx;
while (ctx != NULL)
{
if (ctx->type == FZL_AN_CTX_TYPE_CLASS)
break;
ctx = ctx->parent;
}
bool type_found = false;
fzl_type_t rettype = an_toktotype(analyzer, fn_node->fn.type, &type_found);
char *classname = NULL;
if (!type_found)
{
snprintf(an_problem, sizeof(an_problem), "symbol '%s' not found",
(char *)fn_node->fn.type->value);
an_push_error(analyzer, fn_node->fn.type, an_problem);
return (fzl_an_res_t){.error = true};
}
if (rettype == FZL_TYPE_OBJECT)
classname = (char *)fn_node->fn.type->value;
if (ctx != NULL && ctx->type == FZL_AN_CTX_TYPE_CLASS)
{
for (size_t i = 0; i < fn_node->fn.args_count; i++)
{
fzl_node_t *decl = fn_node->fn.args[i];
if (decl->type != FZL_NODETYPE_DECLARATION)
{
an_push_error(analyzer, decl->token, "argument must be a declaration");
an_fin_ctx(analyzer);
return (fzl_an_res_t){.error = true};
}
char *argname = (char *)decl->var.identifier->token->value;
if (strcmp(argname, "this") == 0 && i > 0)
{
an_push_error(analyzer, decl->token,
"manual declaration of 'this' is not allowed in methods");
an_fin_ctx(analyzer);
return (fzl_an_res_t){.error = true};
}
}
size_t classname_len = strlen(ctx->classname);
size_t methodname_len = strlen((char *)fn_node->fn.name->value);
char *methodname = malloc(classname_len + methodname_len + 2);
strcpy(methodname, ctx->classname);
methodname[classname_len] = '_';
strcpy(methodname + classname_len + 1, fn_node->fn.name->value);
methodname[classname_len + methodname_len + 1] = 0;
analyzer->alloc_names[analyzer->alloc_names_top++] = methodname;
fzl_symtable_res_t putres =
fzl_symt_put(analyzer->symbols[ctx->symbols_level - 1], fn_node,
methodname, rettype, 0);
if (putres != FZL_SYMTABLE_RESULT_OK)
{
if (putres == FZL_SYMTABLE_RESULT_EXISTS)
{
snprintf(an_problem, sizeof(an_problem),
"method '%s.%s' already has been declared", classname,
(char *)fn_node->fn.name->value);
an_push_error(analyzer, fn_node->token, an_problem);
}
else
an_push_error(analyzer, fn_node->token, "failed to store method");
free(analyzer->alloc_names[--analyzer->alloc_names_top]);
an_fin_ctx(analyzer);
return (fzl_an_res_t){.error = true};
}
fzl_symt_setobjclass(analyzer->symbols[ctx->symbols_level - 1], methodname,
classname);
}
else
{
fzl_symtable_res_t putres =
fzl_symt_put(analyzer->symbols[analyzer->symbols_top - 2], fn_node,
(char *)fn_node->fn.name->value, rettype, 0);
if (putres != FZL_SYMTABLE_RESULT_OK)
{
if (putres == FZL_SYMTABLE_RESULT_EXISTS)
{
snprintf(an_problem, sizeof(an_problem),
"function '%s' already has been declared",
(char *)fn_node->fn.name->value);
an_push_error(analyzer, fn_node->token, an_problem);
}
else
an_push_error(analyzer, fn_node->token, "failed to store function");
free(analyzer->alloc_names[--analyzer->alloc_names_top]);
an_fin_ctx(analyzer);
return (fzl_an_res_t){.error = true};
}
}
fzl_an_res_t body_res = analyze_common(analyzer, fn_node->fn.body);
an_fin_ctx(analyzer);
return (fzl_an_res_t){
.error = body_res.error, .type = rettype, .classname = classname};
}
+38
View File
@@ -0,0 +1,38 @@
#include <compiler/fzl_analyzer.h>
#include <compiler/fzl_parser.h>
#include <compiler/internals/analyzer.h>
#include <compiler/utils/fzl_symtable.h>
#include <stdio.h>
// Analyze identifier
fzl_an_res_t analyze_identifier(fzl_analyzer_t *analyzer, fzl_node_t *id)
{
if (!analyzer)
return (fzl_an_res_t){.error = true};
if (!id || id->type != FZL_NODETYPE_IDENTIFIER)
{
an_push_error(analyzer, NULL, "node is not an identifier");
return (fzl_an_res_t){.error = true};
}
char *symname = id->token->value;
fzl_sym_t sym;
bool sym_found = false;
for (ssize_t i = analyzer->symbols_top - 1; i >= 0; i--)
{
if ((sym_found = fzl_symt_get(analyzer->symbols[i], symname, &sym)))
break;
}
if (!sym_found)
{
snprintf(an_problem, sizeof(an_problem), "cannot find symbol '%s'",
symname);
an_push_error(analyzer, id->token, an_problem);
return (fzl_an_res_t){.error = true};
}
return (fzl_an_res_t){
.error = false, .type = sym.type, .classname = sym.classname};
}
+465
View File
@@ -0,0 +1,465 @@
#include <common/fzl_types.h>
#include <compiler/fzl_analyzer.h>
#include <compiler/fzl_parser.h>
#include <compiler/fzl_token.h>
#include <compiler/internals/analyzer.h>
#include <compiler/utils/fzl_symtable.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
// Init block analysis result
typedef struct
{
bool has_var;
char *name;
fzl_type_t type;
char *classname;
} init_analysis;
// Check init block
init_analysis check_init(fzl_analyzer_t *analyzer, fzl_node_t *init)
{
if (!analyzer || !init)
return (init_analysis){.has_var = false};
if (init->type == FZL_NODETYPE_IDENTIFIER)
{
char *name = init->token->value;
fzl_sym_t sym;
bool sym_found = false;
for (ssize_t i = analyzer->symbols_top - 1; i >= 0; i--)
{
if ((sym_found = fzl_symt_get(analyzer->symbols[i], name, &sym)))
break;
}
if (!sym_found)
return (init_analysis){.has_var = false};
char *classname = NULL;
if (sym.type == FZL_TYPE_OBJECT)
classname = sym.classname;
return (init_analysis){.has_var = true,
.name = name,
.type = sym.type,
.classname = classname};
}
else if (init->type == FZL_NODETYPE_DECLARATION)
{
char *name = init->var.identifier->token->value;
bool type_found = false;
fzl_type_t type = an_toktotype(analyzer, init->var.type, &type_found);
char *classname = NULL;
if (!type_found)
{
snprintf(an_problem, sizeof(an_problem), "symbol '%s' not found",
(char *)init->var.type->value);
}
if (type == FZL_TYPE_OBJECT)
classname = init->var.type->value;
return (init_analysis){
.has_var = true, .name = name, .type = type, .classname = classname};
}
else if (init->type == FZL_NODETYPE_ASSIGN)
{
return (init_analysis)check_init(analyzer, init->binary_op.left);
}
return (init_analysis){.has_var = false};
}
// Condition block analysis result type
typedef enum
{
COND_TYPE_CONST_TRUE, // `true` is used as condition
COND_TYPE_CONST_FALSE, // `false` is used as condition
COND_TYPE_VAR_LESS, // until variable value is less
COND_TYPE_VAR_GREATER, // until variable value is greater
COND_TYPE_UNKNOWN, // cannot determine in compilation time
} cond_type;
// Condition block analysis result
typedef struct
{
cond_type type;
bool result_bool;
char *varname;
fzl_type_t vartype;
char *classname;
} cond_analysis;
// Check condition block
cond_analysis check_condition(fzl_analyzer_t *analyzer, fzl_node_t *cond,
bool report)
{
if (!cond)
return (cond_analysis){.type = COND_TYPE_UNKNOWN, .result_bool = false};
switch (cond->type)
{
case FZL_NODETYPE_LITERAL:
if (cond->token->type == FZL_TOKTYPE_STR_LITERAL)
{
if (report)
an_push_warning(analyzer, cond->token,
"string literal is used as condition");
return (cond_analysis){.type = COND_TYPE_UNKNOWN, .result_bool = false};
}
else if (cond->token->type == FZL_TOKTYPE_NULL_LITERAL)
{
if (report)
an_push_warning(analyzer, cond->token,
"null literal is used as condition");
return (cond_analysis){.type = COND_TYPE_UNKNOWN, .result_bool = false};
}
else if (cond->token->type == FZL_TOKTYPE_BOOL_LITERAL)
{
if (strcmp(cond->token->value, "true") == 0)
return (cond_analysis){.type = COND_TYPE_CONST_TRUE,
.result_bool = true};
else
return (cond_analysis){.type = COND_TYPE_CONST_FALSE,
.result_bool = true};
}
else if (cond->token->type == FZL_TOKTYPE_NUM_LITERAL)
{
if (strcmp(cond->token->value, "0") == 0)
return (cond_analysis){.type = COND_TYPE_CONST_FALSE,
.result_bool = false};
else
return (cond_analysis){.type = COND_TYPE_CONST_TRUE,
.result_bool = false};
}
break;
case FZL_NODETYPE_LESS:
case FZL_NODETYPE_LESS_EQUAL:
{
fzl_node_t *comp_left = cond->binary_op.left;
cond_analysis left_check = check_condition(analyzer, comp_left, false);
if (left_check.varname != NULL)
return (cond_analysis){.type = COND_TYPE_VAR_LESS,
.result_bool = true,
.varname = left_check.varname,
.vartype = left_check.vartype,
.classname = left_check.classname};
fzl_node_t *comp_right = cond->binary_op.right;
cond_analysis right_check = check_condition(analyzer, comp_right, false);
if (right_check.varname != NULL)
return (cond_analysis){.type = COND_TYPE_VAR_GREATER,
.result_bool = true,
.varname = right_check.varname,
.vartype = right_check.vartype,
.classname = right_check.classname};
return (cond_analysis){.type = COND_TYPE_UNKNOWN};
}
case FZL_NODETYPE_GREATER:
case FZL_NODETYPE_GREATER_EQUAL:
{
fzl_node_t *comp_left = cond->binary_op.left;
cond_analysis left_check = check_condition(analyzer, comp_left, false);
if (left_check.varname != NULL)
return (cond_analysis){.type = COND_TYPE_VAR_GREATER,
.result_bool = true,
.varname = left_check.varname,
.vartype = left_check.vartype,
.classname = left_check.classname};
fzl_node_t *comp_right = cond->binary_op.right;
cond_analysis right_check = check_condition(analyzer, comp_right, false);
if (right_check.varname != NULL)
return (cond_analysis){.type = COND_TYPE_VAR_LESS,
.result_bool = true,
.varname = right_check.varname,
.vartype = right_check.vartype,
.classname = right_check.classname};
return (cond_analysis){.type = COND_TYPE_UNKNOWN};
}
case FZL_NODETYPE_IDENTIFIER:
{
char *name = cond->token->value;
fzl_sym_t sym;
bool sym_found = false;
for (ssize_t i = analyzer->symbols_top - 1; i >= 0; i--)
{
if ((sym_found = fzl_symt_get(analyzer->symbols[i], name, &sym)))
break;
}
if (!sym_found)
return (cond_analysis){.type = COND_TYPE_UNKNOWN, .varname = NULL};
return (cond_analysis){.type = COND_TYPE_UNKNOWN,
.varname = name,
.vartype = sym.type,
.classname = sym.classname};
}
default:
break;
}
fzl_an_res_t custom_res = analyze_common(analyzer, cond);
if (custom_res.type == FZL_TYPE_BOOL)
return (cond_analysis){.type = COND_TYPE_UNKNOWN, .result_bool = true};
return (cond_analysis){.type = COND_TYPE_UNKNOWN, .result_bool = false};
}
// Step block change direction
typedef enum
{
STEP_DIR_TYPE_INCREASE,
STEP_DIR_TYPE_DECREASE,
STEP_DIR_TYPE_STALL,
STEP_DIR_UNKNOWN,
} step_dir_type;
// Step block analysis
typedef struct
{
bool changing;
char *changing_var;
step_dir_type dir;
} step_analysis;
// Check step block
step_analysis check_step(fzl_node_t *step)
{
if (!step)
return (step_analysis){.changing = false};
if (step->type == FZL_NODETYPE_ASSIGN)
{
if (step->binary_op.left->type != FZL_NODETYPE_IDENTIFIER)
return (step_analysis){
.changing = true, .changing_var = "-", .dir = STEP_DIR_UNKNOWN};
int dir = 0;
fzl_node_t *right = step->binary_op.right;
fzl_node_t *num = NULL;
char *varname = NULL;
if (right->binary_op.left->type != FZL_NODETYPE_IDENTIFIER)
{
num = right->binary_op.left;
if (right->binary_op.right->type == FZL_NODETYPE_IDENTIFIER)
varname = right->binary_op.right->token->value;
}
else
{
num = right->binary_op.right;
if (right->binary_op.left->type == FZL_NODETYPE_IDENTIFIER)
varname = right->binary_op.left->token->value;
}
if (right->type == FZL_NODETYPE_ADD || right->type == FZL_NODETYPE_MUL ||
right->type == FZL_NODETYPE_POW)
dir = 1;
else if (right->type == FZL_NODETYPE_SUB ||
right->type == FZL_NODETYPE_DIV || right->type == FZL_NODETYPE_MOD)
dir = -1;
if (num && num->type == FZL_NODETYPE_UNARY_MINUS)
dir *= -1;
step_dir_type dirtype = varname == NULL ? STEP_DIR_UNKNOWN
: dir > 0 ? STEP_DIR_TYPE_INCREASE
: dir < 0 ? STEP_DIR_TYPE_DECREASE
: STEP_DIR_TYPE_STALL;
return (step_analysis){.changing = varname != NULL && dir != 0,
.changing_var = varname,
.dir = dirtype};
}
return (step_analysis){.changing = false, .dir = STEP_DIR_UNKNOWN};
}
// Body analysis result
typedef struct
{
bool has_exit;
} body_analysis;
body_analysis check_body(fzl_node_t *body, bool nested_loop)
{
body_analysis result = {.has_exit = false};
switch (body->type)
{
case FZL_NODETYPE_RETURN:
result.has_exit = true;
break;
case FZL_NODETYPE_BREAK:
if (!nested_loop)
result.has_exit = true;
break;
case FZL_NODETYPE_LOOP:
if (body->loop.body)
{
body_analysis res = check_body(body->loop.body, true);
if (res.has_exit)
result.has_exit = true;
}
break;
case FZL_NODETYPE_CONDITIONAL:
if (check_body(body->cond.true_block, nested_loop).has_exit)
{
result.has_exit = true;
break;
}
break;
case FZL_NODETYPE_BLOCK:
for (size_t i = 0; i < body->list.entry_count; i++)
{
body_analysis res = check_body(body->list.entries[i], nested_loop);
if (res.has_exit)
{
result.has_exit = true;
break;
}
}
break;
default:
break;
}
return result;
}
// Analyze loop
fzl_an_res_t analyze_loop(fzl_analyzer_t *analyzer, fzl_node_t *loop_node)
{
if (!analyzer)
return (fzl_an_res_t){.error = true};
if (!loop_node || loop_node->type != FZL_NODETYPE_LOOP)
{
an_push_error(analyzer, NULL, "node is not typeof LOOP");
return (fzl_an_res_t){.error = true};
}
an_nest_ctx(analyzer, FZL_AN_CTX_TYPE_LOOP);
init_analysis init_data = {
.has_var = false, .type = FZL_TYPE_NULL, .name = NULL, .classname = NULL};
if (loop_node->loop.init)
{
fzl_an_res_t init_res = analyze_common(analyzer, loop_node->loop.init);
if (init_res.error)
return (fzl_an_res_t){.error = true};
init_data = check_init(analyzer, loop_node->loop.init);
}
cond_analysis cond_data = {.type = COND_TYPE_CONST_TRUE,
.varname = NULL,
.vartype = FZL_TYPE_NULL,
.classname = NULL};
if (loop_node->loop.cond)
{
fzl_an_res_t cond_res = analyze_common(analyzer, loop_node->loop.cond);
if (cond_res.error)
return (fzl_an_res_t){.error = true};
cond_data = check_condition(analyzer, loop_node->loop.cond, true);
if (!cond_data.result_bool && cond_data.type != COND_TYPE_UNKNOWN)
an_push_warning(analyzer, loop_node->loop.cond->token,
"resulting type is not boolean");
if (cond_data.type == COND_TYPE_CONST_FALSE)
an_push_warning(analyzer, loop_node->loop.cond->token,
"loop will never execute");
if (init_data.name && cond_data.varname &&
strcmp(init_data.name, cond_data.varname) != 0)
{
snprintf(an_problem, sizeof(an_problem),
"'%s' is used in condition but '%s' is declared. Maybe there is "
"mistake?",
cond_data.varname, init_data.name);
an_push_warning(analyzer, loop_node->loop.cond->token, an_problem);
}
}
step_analysis step_data = {
.changing = false, .changing_var = NULL, .dir = STEP_DIR_UNKNOWN};
if (loop_node->loop.step)
{
fzl_an_res_t step_res = analyze_common(analyzer, loop_node->loop.step);
if (step_res.error)
return (fzl_an_res_t){.error = true};
step_data = check_step(loop_node->loop.step);
if (step_data.dir != STEP_DIR_UNKNOWN)
{
if (step_data.changing_var && cond_data.varname &&
strcmp(step_data.changing_var, cond_data.varname) != 0)
{
snprintf(an_problem, sizeof(an_problem),
"changing '%s' but condition checking '%s'",
step_data.changing_var, cond_data.varname);
an_push_warning(analyzer, loop_node->loop.step->token, an_problem);
}
if (cond_data.type == COND_TYPE_VAR_GREATER &&
step_data.dir == STEP_DIR_TYPE_INCREASE)
{
snprintf(an_problem, sizeof(an_problem),
"'%s' is increasing while condition is 'greater than'",
step_data.changing_var);
an_push_warning(analyzer, loop_node->loop.step->token, an_problem);
}
else if (cond_data.type == COND_TYPE_VAR_LESS &&
step_data.dir == STEP_DIR_TYPE_DECREASE)
{
snprintf(an_problem, sizeof(an_problem),
"'%s' is decreasing while condition is 'less than'",
step_data.changing_var);
an_push_warning(analyzer, loop_node->loop.step->token, an_problem);
}
else if ((cond_data.type == COND_TYPE_VAR_LESS ||
cond_data.type == COND_TYPE_VAR_GREATER) &&
step_data.dir == STEP_DIR_TYPE_STALL)
{
snprintf(an_problem, sizeof(an_problem), "'%s' is stalled",
cond_data.varname);
an_push_warning(analyzer, loop_node->loop.step->token, an_problem);
}
}
}
body_analysis body_data = {.has_exit = false};
if (cond_data.type != COND_TYPE_CONST_FALSE)
{
fzl_an_res_t body_res = analyze_common(analyzer, loop_node->loop.body);
if (body_res.error)
return (fzl_an_res_t){.error = true};
body_data = check_body(loop_node->loop.body, false);
if (cond_data.type == COND_TYPE_CONST_TRUE && !body_data.has_exit)
an_push_warning(analyzer, loop_node->token, "possibly infinite loop");
}
an_fin_ctx(analyzer);
return (fzl_an_res_t){.error = false, .type = FZL_TYPE_NULL};
}
+39
View File
@@ -0,0 +1,39 @@
#include <common/fzl_types.h>
#include <compiler/fzl_analyzer.h>
#include <compiler/fzl_parser.h>
#include <compiler/internals/analyzer.h>
#include <compiler/utils/fzl_symtable.h>
#include <stdbool.h>
#include <stdio.h>
// Analyze reference
fzl_an_res_t analyze_ref(fzl_analyzer_t *analyzer, fzl_node_t *ref_node)
{
if (!analyzer)
return (fzl_an_res_t){.error = true};
if (!ref_node || ref_node->type != FZL_NODETYPE_REFERENCE)
{
an_push_error(analyzer, NULL, "node is not a reference");
return (fzl_an_res_t){.error = true};
}
char *refto = (char *)ref_node->token->value;
fzl_sym_t sym;
bool found = false;
for (ssize_t i = analyzer->symbols_top - 1; i >= 0; i--)
{
if ((found = fzl_symt_get(analyzer->symbols[i], refto, &sym)))
break;
}
if (!found)
{
snprintf(an_problem, sizeof(an_problem), "cannot find name '%s'", refto);
an_push_error(analyzer, ref_node->token, an_problem);
return (fzl_an_res_t){.error = true};
}
return (fzl_an_res_t){
.error = false, .type = FZL_TYPE_HANDLE, .classname = refto};
}
+113
View File
@@ -0,0 +1,113 @@
#include <common/fzl_types.h>
#include <compiler/fzl_analyzer.h>
#include <compiler/fzl_parser.h>
#include <compiler/fzl_token.h>
#include <compiler/internals/analyzer.h>
#include <stdbool.h>
#include <stdio.h>
// Analyze return statement
fzl_an_res_t analyze_return(fzl_analyzer_t *analyzer, fzl_node_t *ret_node)
{
if (!analyzer)
return (fzl_an_res_t){.error = true};
if (!ret_node || ret_node->type != FZL_NODETYPE_RETURN)
{
an_push_error(analyzer, NULL, "node is not a return statement");
return (fzl_an_res_t){.error = false};
}
fzl_an_ctx_t *fnctx = analyzer->ctx;
while (fnctx != NULL)
{
if (fnctx->type == FZL_AN_CTX_TYPE_FUNCTION)
break;
fnctx = fnctx->parent;
}
fzl_an_ctx_t *classctx = analyzer->ctx;
while (classctx != NULL)
{
if (classctx && classctx->type == FZL_AN_CTX_TYPE_CLASS)
break;
classctx = classctx->parent;
}
bool is_in_method =
classctx != NULL && classctx->type == FZL_AN_CTX_TYPE_CLASS;
if (!fnctx || fnctx->type != FZL_AN_CTX_TYPE_FUNCTION)
{
an_push_error(analyzer, ret_node->token,
"cannot use 'return' outside the function");
return (fzl_an_res_t){.error = true};
}
bool type_found = false;
fzl_type_t rettype = an_toktotype(analyzer, fnctx->fn->fn.type, &type_found);
char *classname = NULL;
if (!type_found)
{
snprintf(an_problem, sizeof(an_problem), "symbol '%s' not found",
(char *)fnctx->fn->fn.type->value);
an_push_error(analyzer, fnctx->fn->fn.type, an_problem);
return (fzl_an_res_t){.error = true};
}
if (rettype == FZL_TYPE_OBJECT)
classname = (char *)fnctx->fn->fn.type->value;
char *rettype_str = an_typetostr(rettype, classname);
if (rettype != FZL_TYPE_NULL)
{
if (ret_node->ret.value == NULL)
{
if (is_in_method)
{
snprintf(an_problem, sizeof(an_problem),
"cannot return null as method '%s.%s' have return type '%s'",
classctx->classname, (char *)fnctx->fn->fn.name->value,
rettype_str);
}
else
{
snprintf(an_problem, sizeof(an_problem),
"cannot return null as function '%s' have return type '%s'",
(char *)fnctx->fn->fn.name->value, rettype_str);
}
an_push_error(analyzer, ret_node->token, an_problem);
return (fzl_an_res_t){.error = true};
}
fzl_an_res_t ret_res = analyze_common(analyzer, ret_node->ret.value);
if (ret_res.error)
return (fzl_an_res_t){.error = true};
if (!an_is_compatible(ret_res.type, rettype, ret_res.classname, classname))
{
if (is_in_method)
{
snprintf(an_problem, sizeof(an_problem),
"method '%s.%s' is expecting return type '%s', got '%s'",
classctx->classname, (char *)fnctx->fn->fn.name->value,
rettype_str, an_typetostr(ret_res.type, ret_res.classname));
}
else
{
snprintf(an_problem, sizeof(an_problem),
"function '%s' is expecting return type '%s', got '%s'",
(char *)fnctx->fn->fn.name->value, rettype_str,
an_typetostr(ret_res.type, ret_res.classname));
}
an_push_error(analyzer, ret_node->token, an_problem);
return (fzl_an_res_t){.error = true};
}
}
return (fzl_an_res_t){
.error = false, .type = rettype, .classname = classname};
}

Some files were not shown because too many files have changed in this diff Show More