├── .clang-format ├── .clangd ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── Makefile ├── README.md ├── asdf.png ├── asdf.sap ├── asdf2.png ├── asdf3.png ├── external ├── ankerl │ └── unordered_dense.h ├── libdeflate │ ├── LICENSE │ ├── common_defs.h │ ├── lib │ │ ├── adler32.c │ │ ├── adler32_vec_template.h │ │ ├── arm │ │ │ ├── adler32_impl.h │ │ │ ├── cpu_features.c │ │ │ ├── cpu_features.h │ │ │ ├── crc32_impl.h │ │ │ ├── crc32_pmull_helpers.h │ │ │ ├── crc32_pmull_wide.h │ │ │ └── matchfinder_impl.h │ │ ├── bt_matchfinder.h │ │ ├── cpu_features_common.h │ │ ├── crc32.c │ │ ├── crc32_multipliers.h │ │ ├── crc32_tables.h │ │ ├── decompress_template.h │ │ ├── deflate_compress.c │ │ ├── deflate_compress.h │ │ ├── deflate_constants.h │ │ ├── deflate_decompress.c │ │ ├── gzip_compress.c │ │ ├── gzip_constants.h │ │ ├── gzip_decompress.c │ │ ├── hc_matchfinder.h │ │ ├── ht_matchfinder.h │ │ ├── lib_common.h │ │ ├── matchfinder_common.h │ │ ├── utils.c │ │ ├── x86 │ │ │ ├── adler32_impl.h │ │ │ ├── cpu_features.c │ │ │ ├── cpu_features.h │ │ │ ├── crc32_impl.h │ │ │ ├── crc32_pclmul_template.h │ │ │ ├── decompress_impl.h │ │ │ └── matchfinder_impl.h │ │ ├── zlib_compress.c │ │ ├── zlib_constants.h │ │ └── zlib_decompress.c │ └── libdeflate.h ├── stb_image.c ├── stb_image.h ├── utf8proc │ ├── LICENSE.md │ ├── NEWS.md │ ├── README.md │ ├── utf8proc.c │ ├── utf8proc.h │ └── utf8proc_data.c ├── xxhash.c ├── xxhash.h ├── zarg.h ├── zpr.h └── zst │ ├── SharedPtr.h │ └── zst.h ├── lib └── sap │ ├── data │ ├── colour │ │ └── sRGB2014.icc │ ├── hyphenation │ │ └── hyph-en-gb.tex │ └── microtype │ │ ├── charter.cfg │ │ └── lppl.txt │ └── std │ ├── bibliography.sap │ ├── colour.sap │ ├── lists.sap │ ├── state.sap │ ├── std.sap │ └── table.sap ├── notes.md ├── source ├── compile.cpp ├── configs │ └── config_parser.cpp ├── defs.h ├── error.h ├── font │ ├── aat.h │ ├── aat_feature.h │ ├── cff.h │ ├── cff │ │ ├── cff.cpp │ │ ├── cff_builder.cpp │ │ ├── charset.cpp │ │ ├── charstring.cpp │ │ ├── dict.cpp │ │ ├── operands.cpp │ │ ├── strings.cpp │ │ └── subset.cpp │ ├── cmap.cpp │ ├── features.h │ ├── font_file.cpp │ ├── font_file.h │ ├── font_scalar.h │ ├── font_source.h │ ├── handle.h │ ├── loader.cpp │ ├── mac_encodings.cpp │ ├── metrics.cpp │ ├── metrics.h │ ├── misc.h │ ├── off.h │ ├── search.h │ ├── search │ │ ├── coretext.cpp │ │ ├── fontconfig.cpp │ │ └── search.cpp │ ├── subset.cpp │ ├── tables │ │ ├── aat │ │ │ ├── feature.cpp │ │ │ ├── kern.cpp │ │ │ ├── lookup_table.cpp │ │ │ ├── morx.cpp │ │ │ └── state_table.cpp │ │ └── off │ │ │ ├── common.cpp │ │ │ ├── context_lookup.cpp │ │ │ ├── coverage.cpp │ │ │ ├── gdef.cpp │ │ │ ├── gpos.cpp │ │ │ └── gsub.cpp │ ├── tag.h │ ├── truetype.h │ └── truetype │ │ ├── subset.cpp │ │ └── truetype.cpp ├── frontend │ ├── lexer.cpp │ ├── parser.cpp │ └── parser_type.cpp ├── interp │ ├── ast.h │ ├── ast │ │ ├── assign_op.cpp │ │ ├── binary_op.cpp │ │ ├── block.cpp │ │ ├── call.cpp │ │ ├── cast.cpp │ │ ├── compare_op.cpp │ │ ├── deref.cpp │ │ ├── dotop.cpp │ │ ├── enum.cpp │ │ ├── for.cpp │ │ ├── fstring.cpp │ │ ├── func.cpp │ │ ├── hook.cpp │ │ ├── ident.cpp │ │ ├── if.cpp │ │ ├── if_let.cpp │ │ ├── import.cpp │ │ ├── literal.cpp │ │ ├── logical_op.cpp │ │ ├── misc.cpp │ │ ├── move.cpp │ │ ├── optional.cpp │ │ ├── return.cpp │ │ ├── struct.cpp │ │ ├── struct_literal.cpp │ │ ├── subscript_op.cpp │ │ ├── tree_expr.cpp │ │ ├── type_expr.cpp │ │ ├── unary_op.cpp │ │ ├── union.cpp │ │ ├── using.cpp │ │ ├── variable.cpp │ │ └── while.cpp │ ├── basedefs.h │ ├── builtin_fns.h │ ├── builtin_types.h │ ├── builtins.cpp │ ├── builtins │ │ ├── be_alignment.cpp │ │ ├── bs_border_style.cpp │ │ ├── bs_colour.cpp │ │ ├── bs_doc_settings.cpp │ │ ├── bs_document_proxy.cpp │ │ ├── bs_font.cpp │ │ ├── bs_glyph_adjustment.cpp │ │ ├── bs_link_annotation.cpp │ │ ├── bs_outline_item.cpp │ │ ├── bs_path_style.cpp │ │ ├── bs_pos2d.cpp │ │ ├── bs_position.cpp │ │ ├── bs_size2d.cpp │ │ ├── bs_state.cpp │ │ ├── bs_style.cpp │ │ ├── bu_path_segment.cpp │ │ ├── font_finder.cpp │ │ ├── include.cpp │ │ ├── object_refs.cpp │ │ ├── positioning.cpp │ │ ├── print.cpp │ │ ├── start.cpp │ │ ├── struct_maker.cpp │ │ ├── styling.cpp │ │ └── tree_manip.cpp │ ├── cst.h │ ├── cst │ │ ├── assign_op.cpp │ │ ├── binary_op.cpp │ │ ├── block.cpp │ │ ├── call.cpp │ │ ├── cast.cpp │ │ ├── compare_op.cpp │ │ ├── deref.cpp │ │ ├── dotop.cpp │ │ ├── enum.cpp │ │ ├── for.cpp │ │ ├── fstring.cpp │ │ ├── func.cpp │ │ ├── hook.cpp │ │ ├── ident.cpp │ │ ├── if.cpp │ │ ├── if_let.cpp │ │ ├── import.cpp │ │ ├── literal.cpp │ │ ├── logical_op.cpp │ │ ├── misc.cpp │ │ ├── move.cpp │ │ ├── optional.cpp │ │ ├── return.cpp │ │ ├── struct.cpp │ │ ├── subscript_op.cpp │ │ ├── tree_expr.cpp │ │ ├── unary_op.cpp │ │ ├── union.cpp │ │ ├── variable.cpp │ │ └── while.cpp │ ├── defn_tree.cpp │ ├── destructors.cpp │ ├── eval_result.h │ ├── evaluator.cpp │ ├── evaluator.h │ ├── interp.cpp │ ├── interp.h │ ├── overload_resolution.cpp │ ├── overload_resolution.h │ ├── parser_type.h │ ├── polymorph.cpp │ ├── polymorph.h │ ├── tc_result.h │ ├── type.h │ ├── typechecker.cpp │ ├── typechecker.h │ ├── types │ │ ├── array.cpp │ │ ├── enum.cpp │ │ ├── function.cpp │ │ ├── optional.cpp │ │ ├── pointer.cpp │ │ ├── struct.cpp │ │ ├── type.cpp │ │ └── union.cpp │ ├── value.cpp │ └── value.h ├── layout │ ├── base.h │ ├── container.cpp │ ├── container.h │ ├── cursor.cpp │ ├── cursor.h │ ├── default_settings.cpp │ ├── document.cpp │ ├── document.h │ ├── image.cpp │ ├── image.h │ ├── layout_object.cpp │ ├── layout_object.h │ ├── line.cpp │ ├── line.h │ ├── linebreak.cpp │ ├── linebreak.h │ ├── page_layout.cpp │ ├── paragraph.cpp │ ├── paragraph.h │ ├── path.cpp │ ├── path.h │ ├── spacer.cpp │ ├── spacer.h │ ├── style.cpp │ ├── word.cpp │ └── word.h ├── location.h ├── main.cpp ├── misc │ ├── dijkstra.h │ ├── dyn_length.cpp │ ├── error.cpp │ ├── hyph.cpp │ ├── hyphenator.h │ ├── path_segment.cpp │ ├── paths.cpp │ ├── short_string.h │ ├── unicode.cpp │ └── util.cpp ├── pdf │ ├── annotation.cpp │ ├── annotation.h │ ├── builtin_font.h │ ├── destination.h │ ├── file.cpp │ ├── file.h │ ├── font.h │ ├── font │ │ ├── .builtin_font.cpp.swp │ │ ├── builtin_afms │ │ │ ├── courier.h │ │ │ ├── courier_bold.h │ │ │ ├── courier_bold_oblique.h │ │ │ ├── courier_oblique.h │ │ │ ├── helvetica.h │ │ │ ├── helvetica_bold.h │ │ │ ├── helvetica_bold_oblique.h │ │ │ ├── helvetica_oblique.h │ │ │ ├── symbol.h │ │ │ ├── times_bold.h │ │ │ ├── times_bold_italic.h │ │ │ ├── times_italic.h │ │ │ ├── times_roman.h │ │ │ └── zapf_dingbats.h │ │ ├── builtin_font.cpp │ │ ├── cidset.cpp │ │ ├── cmap.cpp │ │ ├── glyph_names.cpp │ │ ├── misc.cpp │ │ ├── pdf_font.cpp │ │ └── serialise.cpp │ ├── graphics.cpp │ ├── image.cpp │ ├── indirect.cpp │ ├── misc.h │ ├── object.cpp │ ├── object.h │ ├── outlines.cpp │ ├── page.cpp │ ├── page.h │ ├── page_object.h │ ├── path.cpp │ ├── path.h │ ├── pdf.h │ ├── resource.cpp │ ├── resource.h │ ├── stream.cpp │ ├── text.cpp │ ├── text.h │ ├── units.h │ ├── util │ │ ├── misc.cpp │ │ └── writer.cpp │ ├── win_ansi_encoding.h │ ├── writer.h │ ├── xobject.cpp │ └── xobject.h ├── pool.h ├── precompile.h ├── sap │ ├── annotation.h │ ├── colour.h │ ├── config.h │ ├── document_settings.h │ ├── font_family.h │ ├── frontend.h │ ├── path.h │ ├── style.h │ └── units.h ├── tree │ ├── base.cpp │ ├── base.h │ ├── container.cpp │ ├── container.h │ ├── document.h │ ├── image.cpp │ ├── image.h │ ├── paragraph.cpp │ ├── paragraph.h │ ├── path.cpp │ ├── path.h │ ├── raw.cpp │ ├── raw.h │ ├── scripts.cpp │ ├── separators.cpp │ ├── spacer.cpp │ ├── spacer.h │ ├── wrappers.cpp │ └── wrappers.h ├── types.h ├── units.h ├── util.h └── watch │ ├── kqueue.h │ └── watch.cpp ├── test.sap ├── tests ├── lang │ ├── generics │ │ └── 01-basic.sap │ └── parser │ │ ├── 01-text.sap │ │ └── 02-asdf.sap └── source │ ├── dump_ast.cpp │ ├── picojson.h │ ├── test-parser.cpp │ ├── tester.cpp │ └── tester.h └── tools ├── sap.sbnf └── sap.sublime-syntax /.clangd: -------------------------------------------------------------------------------- 1 | Diagnostics: 2 | UnusedIncludes: None 3 | ClangTidy: 4 | Remove: misc-unused-using-decls 5 | CompileFlags: 6 | Add: [-ferror-limit=0, -Xclang, -fno-cxx-modules] 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | PLAN.md 2 | fonts.zip 3 | .cache 4 | 5 | *.o 6 | *.c.d 7 | *.h.d 8 | *.cpp.d 9 | *.exe 10 | *.pch 11 | *.gch 12 | *.pch.d 13 | *.gch.d 14 | 15 | *.sublime-* 16 | build/ 17 | fonts/ 18 | specs/ 19 | tmp/ 20 | 21 | *.pdf 22 | *.ttf 23 | *.ttc 24 | *.otf 25 | *.otc 26 | *.ttx 27 | *.xml 28 | *.json 29 | *.cff 30 | 31 | big.sap 32 | test2.sap 33 | ClangBuildAnalyzer.ini 34 | report.sap 35 | 36 | !tools/sap.sublime-syntax 37 | -------------------------------------------------------------------------------- /asdf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhiayang/sap/012a780bcd4ba901253146b67596528875cfa039/asdf.png -------------------------------------------------------------------------------- /asdf.sap: -------------------------------------------------------------------------------- 1 | import "std"; 2 | 3 | builtin::println(f"hello, world!"); 4 | -------------------------------------------------------------------------------- /asdf2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhiayang/sap/012a780bcd4ba901253146b67596528875cfa039/asdf2.png -------------------------------------------------------------------------------- /asdf3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhiayang/sap/012a780bcd4ba901253146b67596528875cfa039/asdf3.png -------------------------------------------------------------------------------- /external/libdeflate/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016 Eric Biggers 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation files 5 | (the "Software"), to deal in the Software without restriction, 6 | including without limitation the rights to use, copy, modify, merge, 7 | publish, distribute, sublicense, and/or sell copies of the Software, 8 | and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 18 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 19 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /external/libdeflate/lib/deflate_compress.h: -------------------------------------------------------------------------------- 1 | #ifndef LIB_DEFLATE_COMPRESS_H 2 | #define LIB_DEFLATE_COMPRESS_H 3 | 4 | #include "lib_common.h" 5 | 6 | /* 7 | * DEFLATE compression is private to deflate_compress.c, but we do need to be 8 | * able to query the compression level for zlib and gzip header generation. 9 | */ 10 | 11 | struct libdeflate_compressor; 12 | 13 | unsigned int libdeflate_get_compression_level(struct libdeflate_compressor *c); 14 | 15 | #endif /* LIB_DEFLATE_COMPRESS_H */ 16 | -------------------------------------------------------------------------------- /external/libdeflate/lib/deflate_constants.h: -------------------------------------------------------------------------------- 1 | /* 2 | * deflate_constants.h - constants for the DEFLATE compression format 3 | */ 4 | 5 | #ifndef LIB_DEFLATE_CONSTANTS_H 6 | #define LIB_DEFLATE_CONSTANTS_H 7 | 8 | /* Valid block types */ 9 | #define DEFLATE_BLOCKTYPE_UNCOMPRESSED 0 10 | #define DEFLATE_BLOCKTYPE_STATIC_HUFFMAN 1 11 | #define DEFLATE_BLOCKTYPE_DYNAMIC_HUFFMAN 2 12 | 13 | /* Minimum and maximum supported match lengths (in bytes) */ 14 | #define DEFLATE_MIN_MATCH_LEN 3 15 | #define DEFLATE_MAX_MATCH_LEN 258 16 | 17 | /* Maximum supported match offset (in bytes) */ 18 | #define DEFLATE_MAX_MATCH_OFFSET 32768 19 | 20 | /* log2 of DEFLATE_MAX_MATCH_OFFSET */ 21 | #define DEFLATE_WINDOW_ORDER 15 22 | 23 | /* Number of symbols in each Huffman code. Note: for the literal/length 24 | * and offset codes, these are actually the maximum values; a given block 25 | * might use fewer symbols. */ 26 | #define DEFLATE_NUM_PRECODE_SYMS 19 27 | #define DEFLATE_NUM_LITLEN_SYMS 288 28 | #define DEFLATE_NUM_OFFSET_SYMS 32 29 | 30 | /* The maximum number of symbols across all codes */ 31 | #define DEFLATE_MAX_NUM_SYMS 288 32 | 33 | /* Division of symbols in the literal/length code */ 34 | #define DEFLATE_NUM_LITERALS 256 35 | #define DEFLATE_END_OF_BLOCK 256 36 | #define DEFLATE_FIRST_LEN_SYM 257 37 | 38 | /* Maximum codeword length, in bits, within each Huffman code */ 39 | #define DEFLATE_MAX_PRE_CODEWORD_LEN 7 40 | #define DEFLATE_MAX_LITLEN_CODEWORD_LEN 15 41 | #define DEFLATE_MAX_OFFSET_CODEWORD_LEN 15 42 | 43 | /* The maximum codeword length across all codes */ 44 | #define DEFLATE_MAX_CODEWORD_LEN 15 45 | 46 | /* Maximum possible overrun when decoding codeword lengths */ 47 | #define DEFLATE_MAX_LENS_OVERRUN 137 48 | 49 | /* 50 | * Maximum number of extra bits that may be required to represent a match 51 | * length or offset. 52 | */ 53 | #define DEFLATE_MAX_EXTRA_LENGTH_BITS 5 54 | #define DEFLATE_MAX_EXTRA_OFFSET_BITS 13 55 | 56 | #endif /* LIB_DEFLATE_CONSTANTS_H */ 57 | -------------------------------------------------------------------------------- /external/libdeflate/lib/gzip_constants.h: -------------------------------------------------------------------------------- 1 | /* 2 | * gzip_constants.h - constants for the gzip wrapper format 3 | */ 4 | 5 | #ifndef LIB_GZIP_CONSTANTS_H 6 | #define LIB_GZIP_CONSTANTS_H 7 | 8 | #define GZIP_MIN_HEADER_SIZE 10 9 | #define GZIP_FOOTER_SIZE 8 10 | #define GZIP_MIN_OVERHEAD (GZIP_MIN_HEADER_SIZE + GZIP_FOOTER_SIZE) 11 | 12 | #define GZIP_ID1 0x1F 13 | #define GZIP_ID2 0x8B 14 | 15 | #define GZIP_CM_DEFLATE 8 16 | 17 | #define GZIP_FTEXT 0x01 18 | #define GZIP_FHCRC 0x02 19 | #define GZIP_FEXTRA 0x04 20 | #define GZIP_FNAME 0x08 21 | #define GZIP_FCOMMENT 0x10 22 | #define GZIP_FRESERVED 0xE0 23 | 24 | #define GZIP_MTIME_UNAVAILABLE 0 25 | 26 | #define GZIP_XFL_SLOWEST_COMPRESSION 0x02 27 | #define GZIP_XFL_FASTEST_COMPRESSION 0x04 28 | 29 | #define GZIP_OS_FAT 0 30 | #define GZIP_OS_AMIGA 1 31 | #define GZIP_OS_VMS 2 32 | #define GZIP_OS_UNIX 3 33 | #define GZIP_OS_VM_CMS 4 34 | #define GZIP_OS_ATARI_TOS 5 35 | #define GZIP_OS_HPFS 6 36 | #define GZIP_OS_MACINTOSH 7 37 | #define GZIP_OS_Z_SYSTEM 8 38 | #define GZIP_OS_CP_M 9 39 | #define GZIP_OS_TOPS_20 10 40 | #define GZIP_OS_NTFS 11 41 | #define GZIP_OS_QDOS 12 42 | #define GZIP_OS_RISCOS 13 43 | #define GZIP_OS_UNKNOWN 255 44 | 45 | #endif /* LIB_GZIP_CONSTANTS_H */ 46 | -------------------------------------------------------------------------------- /external/libdeflate/lib/x86/decompress_impl.h: -------------------------------------------------------------------------------- 1 | #ifndef LIB_X86_DECOMPRESS_IMPL_H 2 | #define LIB_X86_DECOMPRESS_IMPL_H 3 | 4 | #include "cpu_features.h" 5 | 6 | /* 7 | * BMI2 optimized version 8 | * 9 | * FIXME: with MSVC, this isn't actually compiled with BMI2 code generation 10 | * enabled yet. That would require that this be moved to its own .c file. 11 | */ 12 | #if HAVE_BMI2_INTRIN 13 | # define deflate_decompress_bmi2 deflate_decompress_bmi2 14 | # define FUNCNAME deflate_decompress_bmi2 15 | # if !HAVE_BMI2_NATIVE 16 | # define ATTRIBUTES _target_attribute("bmi2") 17 | # endif 18 | /* 19 | * Even with __attribute__((target("bmi2"))), gcc doesn't reliably use the 20 | * bzhi instruction for 'word & BITMASK(count)'. So use the bzhi intrinsic 21 | * explicitly. EXTRACT_VARBITS() is equivalent to 'word & BITMASK(count)'; 22 | * EXTRACT_VARBITS8() is equivalent to 'word & BITMASK((u8)count)'. 23 | * Nevertheless, their implementation using the bzhi intrinsic is identical, 24 | * as the bzhi instruction truncates the count to 8 bits implicitly. 25 | */ 26 | # ifndef __clang__ 27 | # include 28 | # ifdef ARCH_X86_64 29 | # define EXTRACT_VARBITS(word, count) _bzhi_u64((word), (count)) 30 | # define EXTRACT_VARBITS8(word, count) _bzhi_u64((word), (count)) 31 | # else 32 | # define EXTRACT_VARBITS(word, count) _bzhi_u32((word), (count)) 33 | # define EXTRACT_VARBITS8(word, count) _bzhi_u32((word), (count)) 34 | # endif 35 | # endif 36 | # include "../decompress_template.h" 37 | #endif /* HAVE_BMI2_INTRIN */ 38 | 39 | #if defined(deflate_decompress_bmi2) && HAVE_BMI2_NATIVE 40 | #define DEFAULT_IMPL deflate_decompress_bmi2 41 | #else 42 | static inline decompress_func_t 43 | arch_select_decompress_func(void) 44 | { 45 | #ifdef deflate_decompress_bmi2 46 | if (HAVE_BMI2(get_x86_cpu_features())) 47 | return deflate_decompress_bmi2; 48 | #endif 49 | return NULL; 50 | } 51 | #define arch_select_decompress_func arch_select_decompress_func 52 | #endif 53 | 54 | #endif /* LIB_X86_DECOMPRESS_IMPL_H */ 55 | -------------------------------------------------------------------------------- /external/libdeflate/lib/zlib_constants.h: -------------------------------------------------------------------------------- 1 | /* 2 | * zlib_constants.h - constants for the zlib wrapper format 3 | */ 4 | 5 | #ifndef LIB_ZLIB_CONSTANTS_H 6 | #define LIB_ZLIB_CONSTANTS_H 7 | 8 | #define ZLIB_MIN_HEADER_SIZE 2 9 | #define ZLIB_FOOTER_SIZE 4 10 | #define ZLIB_MIN_OVERHEAD (ZLIB_MIN_HEADER_SIZE + ZLIB_FOOTER_SIZE) 11 | 12 | #define ZLIB_CM_DEFLATE 8 13 | 14 | #define ZLIB_CINFO_32K_WINDOW 7 15 | 16 | #define ZLIB_FASTEST_COMPRESSION 0 17 | #define ZLIB_FAST_COMPRESSION 1 18 | #define ZLIB_DEFAULT_COMPRESSION 2 19 | #define ZLIB_SLOWEST_COMPRESSION 3 20 | 21 | #endif /* LIB_ZLIB_CONSTANTS_H */ 22 | -------------------------------------------------------------------------------- /external/stb_image.c: -------------------------------------------------------------------------------- 1 | // stb_image.cpp 2 | // Copyright (c) 2022, yuki 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #if defined(__GNUC__) 6 | #pragma GCC diagnostic push 7 | #pragma GCC diagnostic ignored "-Wsign-conversion" 8 | 9 | #if defined(__clang__) 10 | #pragma GCC diagnostic ignored "-Wimplicit-int-conversion" 11 | #endif 12 | #endif 13 | 14 | #define STB_IMAGE_IMPLEMENTATION 15 | #include "stb_image.h" 16 | -------------------------------------------------------------------------------- /external/xxhash.c: -------------------------------------------------------------------------------- 1 | /* 2 | * xxHash - Extremely Fast Hash algorithm 3 | * Copyright (C) 2012-2021 Yann Collet 4 | * 5 | * BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php) 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are 9 | * met: 10 | * 11 | * * Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * * Redistributions in binary form must reproduce the above 14 | * copyright notice, this list of conditions and the following disclaimer 15 | * in the documentation and/or other materials provided with the 16 | * distribution. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | * 30 | * You can contact the author at: 31 | * - xxHash homepage: https://www.xxhash.com 32 | * - xxHash source repository: https://github.com/Cyan4973/xxHash 33 | */ 34 | 35 | 36 | /* 37 | * xxhash.c instantiates functions defined in xxhash.h 38 | */ 39 | 40 | #define XXH_STATIC_LINKING_ONLY /* access advanced declarations */ 41 | #define XXH_IMPLEMENTATION /* access definitions */ 42 | 43 | #include "xxhash.h" 44 | -------------------------------------------------------------------------------- /lib/sap/data/colour/sRGB2014.icc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhiayang/sap/012a780bcd4ba901253146b67596528875cfa039/lib/sap/data/colour/sRGB2014.icc -------------------------------------------------------------------------------- /lib/sap/std/colour.sap: -------------------------------------------------------------------------------- 1 | # std/colour.sap 2 | 3 | namespace builtin 4 | { 5 | fn to_string(colour: &Colour) -> string 6 | { 7 | if(colour!.type == ColourType::RGB) 8 | return f"rgb({colour!.rgb.r}, {colour!.rgb.g}, {colour!.rgb.b})"; 9 | else if(colour!.type == ColourType::CMYK) 10 | return f"cmyk({colour!.cmyk.c}, {colour!.cmyk.m}, {colour!.cmyk.y}, {colour!.cmyk.k})"; 11 | else 12 | return f"colour(???)"; 13 | } 14 | } 15 | 16 | namespace std 17 | { 18 | fn rgb(r: float, g: float, b: float) -> builtin::Colour 19 | { 20 | return builtin::Colour { 21 | type: .RGB, 22 | rgb: { r, g, b } 23 | }; 24 | } 25 | 26 | fn cmyk(c: float, m: float, y: float, k: float) -> builtin::Colour 27 | { 28 | return builtin::Colour { 29 | type: .CMYK, 30 | cmyk: { c, m, y, k } 31 | }; 32 | } 33 | 34 | namespace impl 35 | { 36 | fn parse_hex_char(a: char) -> int 37 | { 38 | if('0' <= a <= '9') 39 | return cast(a, $int) - cast('0', $int); 40 | else if('a' <= a <= 'f') 41 | return cast(a, $int) - cast('a', $int) + 10; 42 | else if('A' <= a <= 'F') 43 | return cast(a, $int) - cast('A', $int) + 10; 44 | else 45 | { 46 | builtin::println(f"invalid hex digit '{a}'"); 47 | return 0; 48 | } 49 | } 50 | } 51 | 52 | fn hexcolour(a: string) -> builtin::Colour 53 | { 54 | using impl::parse_hex_char; 55 | 56 | var r = 0.0; 57 | var g = 0.0; 58 | var b = 0.0; 59 | 60 | # my god, we need slices / substrings / anything 61 | var x = 0; 62 | if(a[0] == '#') 63 | x = 1; 64 | 65 | if(a.size() == x + 3) 66 | { 67 | r = parse_hex_char(a[x + 0]); 68 | g = parse_hex_char(a[x + 1]); 69 | b = parse_hex_char(a[x + 2]); 70 | 71 | # convert F to FF, etc. 72 | r = 16.0 * r + r; 73 | g = 16.0 * g + g; 74 | b = 16.0 * b + b; 75 | } 76 | else 77 | { 78 | r = 16 * parse_hex_char(a[x + 0]) + parse_hex_char(a[x + 1]); 79 | g = 16 * parse_hex_char(a[x + 2]) + parse_hex_char(a[x + 3]); 80 | b = 16 * parse_hex_char(a[x + 4]) + parse_hex_char(a[x + 5]); 81 | } 82 | 83 | return rgb(r / 255.0, g / 255.0, b / 255.0); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/sap/std/lists.sap: -------------------------------------------------------------------------------- 1 | # std/lists.sap 2 | 3 | namespace std 4 | { 5 | global var g_bulleted_list_item_spacing: Length = 0.5em; 6 | global var g_numbered_list_item_spacing: Length = 0.5em; 7 | 8 | fn numbered_list(items: [Inline...]) -> ?Block 9 | { 10 | var lines = [:Block]; 11 | var i = 0; 12 | while(i < items.size()) 13 | { 14 | lines += [builtin::make_hbox( 15 | builtin::hspace(0.8em), 16 | builtin::make_line(builtin::make_text(f"{i + 1}.")).apply_style({ horz_alignment: .Right }), 17 | builtin::hspace(0.6em), 18 | builtin::make_paragraph(*items[i]).apply_style({ line_spacing: 1.2 }) 19 | )]; 20 | i += 1; 21 | } 22 | 23 | return builtin::make_vbox(...*lines) 24 | .apply_style({ paragraph_spacing: g_numbered_list_item_spacing, }); 25 | } 26 | 27 | fn numbered_list(items: [string...]) -> ?Block 28 | { 29 | var inlines = [:Inline]; 30 | for(var i = 0; i < items.size(); i += 1) 31 | inlines += [builtin::make_text(items[i])]; 32 | 33 | return numbered_list(...*inlines); 34 | } 35 | 36 | fn bulleted_list(items: [Inline...]) -> Block 37 | { 38 | var lines = [:Block]; 39 | for(var i = 0; i < items.size(); i += 1) 40 | { 41 | lines += [builtin::make_hbox( 42 | builtin::hspace(0.8em), 43 | builtin::make_line(builtin::make_text(f"•").raise(-0.15em)).apply_style({ horz_alignment: .Right }), 44 | builtin::hspace(0.6em), 45 | builtin::make_paragraph(*items[i]).apply_style({ line_spacing: 1.2 }) 46 | )]; 47 | } 48 | 49 | return builtin::make_vbox(...*lines) 50 | .apply_style({ paragraph_spacing: g_bulleted_list_item_spacing, }); 51 | } 52 | 53 | fn bulleted_list(items: [string...]) -> Block 54 | { 55 | var inlines = [:Inline]; 56 | for(var i = 0; i < items.size(); i += 1) 57 | inlines += [builtin::make_text(items[i])]; 58 | 59 | return bulleted_list(...*inlines); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/sap/std/state.sap: -------------------------------------------------------------------------------- 1 | # std/state.sap 2 | -------------------------------------------------------------------------------- /notes.md: -------------------------------------------------------------------------------- 1 | ## notes 2 | 3 | 4 | ### language design 5 | 6 | Not sure if we want to support multiple trailing blocks... probably? might be useful for certain "environment" situations. 7 | 8 | some care is needed for verbatim environments; I think there's no alternative to explicit begin/end tokens. we can 9 | have something like `\rawbegin_foo ... \rawend_foo` where `foo` is just any alphanumeric thing. 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ### on CFF subsetting 23 | 24 | So we got OpenType-CFF subsetting mostly working, but it doesn't work consistently in all viewers. So, instead 25 | of embedding the whole OTF, we only embed the raw CFF data. This follows what TeX does, which means it's 26 | probably fine. 27 | 28 | 29 | 30 | ### on font size optimisation 31 | 32 | There are quite a number of potential size savings that we haven't taken advantage of, in 33 | increasing order of complexity: 34 | 35 | 1. remove the `vhea`/`vmtx` table if we did not use the font in vertical writing mode 36 | 37 | 2. chopping off glyph ids after the last one that we use, letting us: 38 | - cut the size of the `loca` table (esp for large fonts that need 4 bytes per entry) 39 | - cut the size of the `hmtx` table 40 | 41 | 3. renumber the glyphs entirely: 42 | - completely eliminates the bulk of the `cmap` table 43 | - also cuts `loca` and `hmtx` dramatically 44 | - also drastically shrinks the size of the text itself (since we no longer need 2 bytes for glyph ids) 45 | 46 | 47 | ### on unicode (de)composition 48 | 49 | The problem is that unicode combining characters let people write arbitrary things that combine with 50 | arbitrary other things. Normally, this isn't a problem (if your font doesn't have it, then it won't show). 51 | 52 | However, combining diacritic marks are a (potentially common) thing, but fonts (eg. Myriad Pro) might not 53 | actually contain a glyph for the diacritic mark --- instead having one glyph for each (valid) combination 54 | of base character + mark. 55 | 56 | This means that, for maximum flexibility, if a font doesn't contain a glyph for a combining character, we 57 | should attempt to compose the codepoint, see if the font contains a glyph for that codepoint, and if 58 | so replace it. The conerse is also true --- if for some reason the font has the combining character glyph 59 | but not one for the composed form, then we should attempt to decompose the codepoint. 60 | 61 | 62 | update: it probably makes more sense to maximally compose codepoints (i think we can do that with utf8proc), 63 | since we want to prefer an "actual" glyph eg. for an accented character if that exists. If that fails, then 64 | we should try the decomposed version, and if that also fails, `.notdef` time. 65 | 66 | 67 | -------------------------------------------------------------------------------- /source/compile.cpp: -------------------------------------------------------------------------------- 1 | // compile.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "sap/config.h" 6 | #include "sap/frontend.h" 7 | 8 | #include "interp/interp.h" 9 | 10 | #include "tree/document.h" 11 | #include "tree/container.h" 12 | 13 | #include "layout/document.h" 14 | 15 | #include "pdf/font.h" 16 | #include "pdf/writer.h" 17 | 18 | namespace sap 19 | { 20 | bool compile(zst::str_view input_file, zst::str_view output_file) 21 | { 22 | auto interp = interp::Interpreter(); 23 | auto file = interp.loadFile(input_file); 24 | 25 | auto document = frontend::parse(input_file, file.chars()); 26 | if(document.is_err()) 27 | return document.error().display(), false; 28 | 29 | // if we do not have a start_document, we should just run the "preamble" 30 | // as a script and then exit. 31 | if(not document->haveDocStart()) 32 | { 33 | auto r = document->runPreamble(&interp); 34 | if(r.is_err()) 35 | return r.error().display(), false; 36 | 37 | return true; 38 | } 39 | 40 | // load microtype configs 41 | for(auto& lib_path : paths::librarySearchPaths()) 42 | { 43 | auto tmp = stdfs::path(lib_path) / "data" / "microtype"; 44 | if(not stdfs::exists(tmp)) 45 | continue; 46 | 47 | for(auto& de : stdfs::directory_iterator(tmp)) 48 | { 49 | if(not de.is_regular_file() || de.path().extension() != ".cfg") 50 | continue; 51 | 52 | util::log("loading microtype cfg '{}'", de.path().filename().string()); 53 | 54 | auto cfg = interp.loadFile(de.path().string()).chars(); 55 | while(not cfg.empty()) 56 | { 57 | auto ret = config::parseMicrotypeConfig(cfg); 58 | if(ret.is_err()) 59 | { 60 | ErrorMessage(Location::builtin(), 61 | zpr::sprint("failed to load {}: {}", de.path().filename().string(), ret.error())) 62 | .display(); 63 | return false; 64 | } 65 | 66 | interp.addMicrotypeConfig(std::move(*ret)); 67 | } 68 | } 69 | } 70 | 71 | auto layout_doc = document.unwrap().layout(&interp); 72 | if(layout_doc.is_err()) 73 | return layout_doc.error().display(), false; 74 | 75 | interp.setCurrentPhase(ProcessingPhase::Render); 76 | 77 | auto writer = pdf::Writer(output_file); 78 | layout_doc.unwrap()->write(&writer); 79 | writer.close(); 80 | 81 | return true; 82 | } 83 | 84 | static bool g_draft_mode = false; 85 | bool isDraftMode() 86 | { 87 | return g_draft_mode; 88 | } 89 | 90 | void set_draft_mode(bool draft) 91 | { 92 | g_draft_mode = draft; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /source/defs.h: -------------------------------------------------------------------------------- 1 | // defs.h 2 | // Copyright (c) 2021, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #include "error.h" 14 | 15 | namespace sap 16 | { 17 | template 18 | using StrErrorOr = zst::Result; 19 | 20 | using zst::Ok; 21 | using zst::Err; 22 | using zst::ErrFmt; 23 | 24 | using zst::Left; 25 | using zst::Right; 26 | using zst::Either; 27 | 28 | using zst::Result; 29 | using zst::Failable; 30 | 31 | bool isDraftMode(); 32 | bool compile(zst::str_view input_file, zst::str_view output_file); 33 | 34 | template 35 | auto OkMove(T& x) 36 | { 37 | return Ok(std::move(x)); 38 | } 39 | 40 | std::filesystem::path getInvocationCWD(); 41 | 42 | namespace watch 43 | { 44 | bool isWatching(); 45 | bool isSupportedPlatform(); 46 | 47 | void start(zst::str_view main_file, zst::str_view output_file); 48 | StrErrorOr addFileToWatchList(zst::str_view path); 49 | } 50 | } 51 | 52 | #define __TRY(x, L) \ 53 | __extension__({ \ 54 | auto&& __r##L = x; \ 55 | using R##L = std::decay_t; \ 56 | using V##L = typename R##L::value_type; \ 57 | using E##L = typename R##L::error_type; \ 58 | if((__r##L).is_err()) \ 59 | return Err(std::move((__r##L).error())); \ 60 | util::impl::extract_value_or_return_void().extract(__r##L); \ 61 | }) 62 | 63 | #define _TRY(x, L) __TRY(x, L) 64 | #define TRY(x) _TRY(x, __COUNTER__) 65 | 66 | inline void zst::error_and_exit(const char* str, size_t len) 67 | { 68 | sap::internal_error("{}", zst::str_view(str, len)); 69 | } 70 | -------------------------------------------------------------------------------- /source/font/aat_feature.h: -------------------------------------------------------------------------------- 1 | // aat_feature.h 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | namespace font::aat 8 | { 9 | struct Feature 10 | { 11 | uint16_t type; 12 | uint16_t selector; 13 | 14 | constexpr bool operator==(Feature f) const { return type == f.type && selector == f.selector; } 15 | constexpr bool operator!=(Feature f) const { return !(*this == f); } 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /source/font/font_scalar.h: -------------------------------------------------------------------------------- 1 | // font_scalar.h 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include "units.h" 8 | 9 | 10 | struct FONT_UNIT_DESIGN_SPACE; 11 | 12 | // this is not compatible with anybody 13 | DEFINE_UNIT_IN_NAMESPACE(font_design_space, 1, FONT_UNIT_DESIGN_SPACE, pdf, design_space); 14 | 15 | namespace font 16 | { 17 | struct FONT_COORD_SPACE; 18 | 19 | using FontScalar = dim::Scalar; 20 | using FontVector2d = dim::Vector2; 21 | } 22 | -------------------------------------------------------------------------------- /source/font/font_source.h: -------------------------------------------------------------------------------- 1 | // font_source.h 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include "util.h" 8 | 9 | #include "font/metrics.h" 10 | #include "font/features.h" 11 | 12 | namespace font 13 | { 14 | struct CharacterMapping 15 | { 16 | util::hashmap forward; 17 | util::hashmap reverse; 18 | }; 19 | 20 | struct FontSource 21 | { 22 | virtual ~FontSource() { } 23 | 24 | virtual std::string name() const = 0; 25 | 26 | size_t numGlyphs() const { return m_num_glyphs; } 27 | const FontMetrics& metrics() const { return m_metrics; } 28 | const CharacterMapping& characterMapping() const { return m_character_mapping; } 29 | 30 | GlyphId getGlyphIndexForCodepoint(char32_t codepoint) const; 31 | GlyphMetrics getGlyphMetrics(GlyphId glyphId) const; 32 | 33 | bool isGlyphUsed(GlyphId glyph_id) const; 34 | void markGlyphAsUsed(GlyphId glyph_id) const; 35 | const util::hashset& usedGlyphs() const; 36 | 37 | virtual bool isBuiltin() const = 0; 38 | 39 | virtual util::hashmap 40 | getPositioningAdjustmentsForGlyphSequence(zst::span glyphs, 41 | const font::FeatureSet& features) const = 0; 42 | 43 | virtual std::optional 44 | performSubstitutionsForGlyphSequence(zst::span glyphs, const font::FeatureSet& features) const = 0; 45 | 46 | protected: 47 | virtual GlyphMetrics get_glyph_metrics_impl(GlyphId glyphid) const = 0; 48 | 49 | size_t m_num_glyphs = 0; 50 | FontMetrics m_metrics {}; 51 | 52 | CharacterMapping m_character_mapping {}; 53 | 54 | mutable util::hashset m_used_glyphs {}; 55 | mutable util::hashmap m_glyph_metrics {}; 56 | }; 57 | } 58 | -------------------------------------------------------------------------------- /source/font/handle.h: -------------------------------------------------------------------------------- 1 | // handle.h 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | namespace font 8 | { 9 | enum class FontStyle 10 | { 11 | NORMAL = 0, 12 | ITALIC = 1, 13 | }; 14 | 15 | enum class FontWeight : int 16 | { 17 | THIN = 100, 18 | EXTRA_LIGHT = 200, 19 | LIGHT = 300, 20 | NORMAL = 400, 21 | MEDIUM = 500, 22 | SEMIBOLD = 600, 23 | BOLD = 700, 24 | EXTRA_BOLD = 800, 25 | BLACK = 900, 26 | }; 27 | 28 | namespace FontStretch 29 | { 30 | constexpr inline double ULTRA_CONDENSED = 0.5; 31 | constexpr inline double EXTRA_CONDENSED = 0.625; 32 | constexpr inline double CONDENSED = 0.75; 33 | constexpr inline double SEMI_CONDENSED = 0.875; 34 | constexpr inline double NORMAL = 1.0; 35 | constexpr inline double SEMI_EXPANDED = 1.125; 36 | constexpr inline double EXPANDED = 1.25; 37 | constexpr inline double EXTRA_EXPANDED = 1.5; 38 | constexpr inline double ULTRA_EXPANDED = 2.0; 39 | } 40 | 41 | struct FontProperties 42 | { 43 | FontStyle style = FontStyle::NORMAL; 44 | FontWeight weight = FontWeight::NORMAL; 45 | double stretch = FontStretch::NORMAL; 46 | 47 | bool operator==(const FontProperties&) const = default; 48 | size_t hash() const { return util::hasher::combine(0, this->style, this->weight, this->stretch); } 49 | }; 50 | 51 | struct FontHandle 52 | { 53 | std::string display_name; 54 | std::string postscript_name; 55 | FontProperties properties; 56 | 57 | stdfs::path path; 58 | 59 | size_t hash() const 60 | { 61 | return util::hasher::combine(0, this->display_name, this->postscript_name, this->properties, this->path); 62 | } 63 | 64 | bool operator==(const FontHandle&) const = default; 65 | }; 66 | 67 | struct FontFamily 68 | { 69 | FontHandle normal; 70 | FontHandle bold; 71 | FontHandle italic; 72 | FontHandle bold_italic; 73 | }; 74 | } 75 | -------------------------------------------------------------------------------- /source/font/metrics.cpp: -------------------------------------------------------------------------------- 1 | // metrics.cpp 2 | // Copyright (c) 2021, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "util.h" // for convertBEU16 6 | #include "types.h" // for GlyphId 7 | 8 | #include "font/truetype.h" // for BoundingBox, getGlyphBoundingBox 9 | #include "font/font_file.h" // for GlyphMetrics, FontFile, FontMetrics 10 | #include "font/font_scalar.h" // for FontScalar, font_design_space 11 | 12 | namespace font 13 | { 14 | GlyphMetrics FontSource::getGlyphMetrics(GlyphId glyph_id) const 15 | { 16 | if(auto it = m_glyph_metrics.find(glyph_id); it != m_glyph_metrics.end()) 17 | return it->second; 18 | 19 | auto metrics = this->get_glyph_metrics_impl(glyph_id); 20 | return m_glyph_metrics.emplace(glyph_id, std::move(metrics)).first->second; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /source/font/misc.h: -------------------------------------------------------------------------------- 1 | // misc.h 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | namespace font 10 | { 11 | struct FontFile; 12 | 13 | uint8_t peek_u8(const zst::byte_span& s); 14 | int16_t peek_i16(const zst::byte_span& s); 15 | uint16_t peek_u16(const zst::byte_span& s); 16 | uint32_t peek_u32(const zst::byte_span& s); 17 | uint64_t peek_u64(const zst::byte_span& s); 18 | 19 | uint8_t consume_u8(zst::byte_span& s); 20 | uint16_t consume_u16(zst::byte_span& s); 21 | int16_t consume_i16(zst::byte_span& s); 22 | uint32_t consume_u24(zst::byte_span& s); 23 | uint32_t consume_u32(zst::byte_span& s); 24 | uint64_t consume_u64(zst::byte_span& s); 25 | 26 | std::string generateSubsetName(const FontFile* font); 27 | } 28 | -------------------------------------------------------------------------------- /source/font/search.h: -------------------------------------------------------------------------------- 1 | // search.h 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "font/handle.h" 12 | 13 | namespace font 14 | { 15 | /* 16 | returns all the installed fonts on the system. 17 | */ 18 | std::vector getAllFonts(); 19 | 20 | /* 21 | Returns the names of all typefaces installed on the system. This is distinct from getAllFonts(), 22 | which might return separate FontHandles for each variation (eg. bold, italic) of a typeface. 23 | 24 | This can also be thought of as returning a list of font family names. 25 | */ 26 | std::vector getAllTypefaces(); 27 | 28 | /* 29 | search for a font in the given typeface (family) with the given properties 30 | */ 31 | std::optional searchForFont(zst::str_view typeface_name, FontProperties properties); 32 | 33 | /* 34 | search for a specific font by its unique postscript name 35 | */ 36 | std::optional searchForFontWithPostscriptName(zst::str_view postscript_name); 37 | 38 | /* 39 | select the best font matching the properties 40 | */ 41 | std::optional getBestFontWithProperties(std::vector fonts, FontProperties properties); 42 | 43 | 44 | constexpr inline const char* GENERIC_SERIF = "serif"; 45 | constexpr inline const char* GENERIC_SANS_SERIF = "sans-serif"; 46 | constexpr inline const char* GENERIC_MONOSPACE = "monospace"; 47 | constexpr inline const char* GENERIC_EMOJI = "emoji"; 48 | 49 | std::optional findFont(const std::vector& typefaces, FontProperties properties); 50 | } 51 | -------------------------------------------------------------------------------- /source/font/tables/aat/state_table.cpp: -------------------------------------------------------------------------------- 1 | // aat_state.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "font/aat.h" 6 | #include "font/misc.h" 7 | 8 | namespace font::aat 9 | { 10 | static void parse_class_subtable_stx(StateTable& state_table, zst::byte_span buf, size_t num_glyphs) 11 | { 12 | auto lookup = *parseLookupTable(buf, num_glyphs); 13 | for(auto& [a, b] : lookup.map) 14 | state_table.glyph_classes[a] = static_cast(b); 15 | } 16 | 17 | static void parse_class_subtable(StateTable& state_table, zst::byte_span buf) 18 | { 19 | auto first_glyph = consume_u16(buf); 20 | auto num_glyphs = consume_u16(buf); 21 | 22 | for(auto i = 0u; i < num_glyphs; i++) 23 | state_table.glyph_classes[GlyphId(first_glyph + i)] = consume_u8(buf); 24 | } 25 | 26 | static StateTable parse_state_table(zst::byte_span& buf, bool is_stx, size_t num_font_glyphs) 27 | { 28 | auto table_start = buf; 29 | 30 | StateTable ret; 31 | 32 | zst::byte_span class_table; 33 | zst::byte_span state_table; 34 | zst::byte_span entry_table; 35 | 36 | if(is_stx) 37 | { 38 | ret.num_classes = consume_u32(buf); 39 | class_table = table_start.drop(consume_u32(buf)); 40 | state_table = table_start.drop(consume_u32(buf)); 41 | entry_table = table_start.drop(consume_u32(buf)); 42 | } 43 | else 44 | { 45 | ret.num_classes = consume_u16(buf); 46 | class_table = table_start.drop(consume_u16(buf)); 47 | state_table = table_start.drop(consume_u16(buf)); 48 | entry_table = table_start.drop(consume_u16(buf)); 49 | } 50 | 51 | if(is_stx) 52 | parse_class_subtable_stx(ret, class_table, num_font_glyphs); 53 | else 54 | parse_class_subtable(ret, class_table); 55 | 56 | ret.entry_array = entry_table; 57 | ret.state_array = state_table; 58 | ret.state_row_size = (is_stx ? sizeof(uint16_t) : sizeof(uint8_t)) * ret.num_classes; 59 | ret.is_extended = is_stx; 60 | 61 | return ret; 62 | } 63 | 64 | 65 | StateTable parseStateTable(zst::byte_span& buf, size_t num_font_glyphs) 66 | { 67 | return parse_state_table(buf, /* is_stx: */ false, num_font_glyphs); 68 | } 69 | 70 | StateTable parseExtendedStateTable(zst::byte_span& buf, size_t num_font_glyphs) 71 | { 72 | return parse_state_table(buf, /* is_stx: */ true, num_font_glyphs); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /source/font/tag.h: -------------------------------------------------------------------------------- 1 | // tag.h 2 | // Copyright (c) 2021, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | namespace font 8 | { 9 | struct Tag 10 | { 11 | constexpr Tag() : value(0) { } 12 | constexpr explicit Tag(uint32_t sp) : value(sp) { } 13 | constexpr explicit Tag(const char (&f)[5]) : Tag((uint8_t) f[0], (uint8_t) f[1], (uint8_t) f[2], (uint8_t) f[3]) { } 14 | 15 | constexpr Tag(uint8_t a, uint8_t b, uint8_t c, uint8_t d) 16 | : value(((uint32_t) a << 24) | ((uint32_t) b << 16) | ((uint32_t) c << 8) | ((uint32_t) d << 0)) 17 | { 18 | } 19 | 20 | constexpr bool operator==(const Tag& t) const { return this->value == t.value; } 21 | constexpr bool operator!=(const Tag& t) const { return !(*this == t); }; 22 | 23 | inline std::string str() const 24 | { 25 | return std::string({ 26 | (char) ((value & 0xff000000) >> 24), 27 | (char) ((value & 0x00ff0000) >> 16), 28 | (char) ((value & 0x0000ff00) >> 8), 29 | (char) ((value & 0x000000ff) >> 0), 30 | }); 31 | } 32 | 33 | inline size_t hash() const { return std::hash()(value); } 34 | 35 | constexpr bool operator<(const Tag& t) const { return this->value < t.value; } 36 | 37 | uint32_t value; 38 | }; 39 | } 40 | 41 | template <> 42 | struct std::hash 43 | { 44 | size_t operator()(font::Tag t) const { return t.hash(); } 45 | }; 46 | -------------------------------------------------------------------------------- /source/font/truetype.h: -------------------------------------------------------------------------------- 1 | // truetype.h 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include "types.h" // for GlyphId 8 | 9 | namespace font 10 | { 11 | struct FontFile; 12 | struct GlyphMetrics; 13 | } 14 | 15 | namespace font::truetype 16 | { 17 | struct Glyph 18 | { 19 | uint16_t gid = 0; 20 | std::set component_gids {}; 21 | 22 | zst::byte_span glyph_data {}; 23 | }; 24 | 25 | struct TTData 26 | { 27 | size_t loca_bytes_per_entry = 0; 28 | 29 | std::vector glyphs {}; 30 | zst::byte_span glyf_data {}; 31 | }; 32 | 33 | struct BoundingBox 34 | { 35 | double xmin; 36 | double ymin; 37 | double xmax; 38 | double ymax; 39 | }; 40 | 41 | struct TTSubset 42 | { 43 | zst::byte_buffer loca_table {}; 44 | zst::byte_buffer glyf_table {}; 45 | }; 46 | 47 | /* 48 | Return the bounding box data for the given glyph id by inspecting its glyf data. 49 | */ 50 | BoundingBox getGlyphBoundingBox(TTData* tt, GlyphId glyph_id); 51 | } 52 | -------------------------------------------------------------------------------- /source/font/truetype/subset.cpp: -------------------------------------------------------------------------------- 1 | // subset.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "util.h" // for checked_cast, convertBEU16, convertBEU32 6 | #include "types.h" // for GlyphId 7 | 8 | #include "font/truetype.h" // for TTData, TTSubset, createTTSubset 9 | #include "font/font_file.h" // for FontFile 10 | 11 | namespace font 12 | { 13 | truetype::TTSubset FontFile::createTTSubset() 14 | { 15 | auto& tt = m_truetype_data; 16 | assert(tt != nullptr); 17 | 18 | // needs to be sorted. always insert 0. 19 | std::set used_gids {}; 20 | used_gids.insert(0); 21 | used_gids.insert(tt->glyphs[0].component_gids.begin(), tt->glyphs[0].component_gids.end()); 22 | 23 | for(auto& gid : m_used_glyphs) 24 | { 25 | auto gid16 = util::checked_cast(static_cast(gid)); 26 | auto& comps = tt->glyphs[gid16].component_gids; 27 | 28 | used_gids.insert(gid16); 29 | used_gids.insert(comps.begin(), comps.end()); 30 | } 31 | 32 | truetype::TTSubset subset {}; 33 | 34 | // the loca table must contain an entry for every glyph id in the font. since we're not 35 | // changing the glyph ids themselves, we must iterate over every glyph id. 36 | { 37 | bool half = tt->loca_bytes_per_entry == 2; 38 | 39 | zst::byte_buffer loca {}; 40 | zst::byte_buffer glyf {}; 41 | 42 | for(uint16_t gid = 0; gid < m_num_glyphs; gid++) 43 | { 44 | if(half) 45 | loca.append_bytes(util::convertBEU16(util::checked_cast(glyf.size() / 2))); 46 | else 47 | loca.append_bytes(util::convertBEU32(util::checked_cast(glyf.size()))); 48 | 49 | if(used_gids.find(gid) != used_gids.end()) 50 | glyf.append(tt->glyphs[gid].glyph_data); 51 | } 52 | 53 | subset.loca_table = std::move(loca); 54 | subset.glyf_table = std::move(glyf); 55 | } 56 | 57 | return subset; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /source/interp/ast/block.cpp: -------------------------------------------------------------------------------- 1 | // block.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/ast.h" 6 | #include "interp/cst.h" 7 | #include "interp/interp.h" 8 | #include "interp/eval_result.h" 9 | 10 | namespace sap::interp::ast 11 | { 12 | ErrorOr Block::typecheck_impl(Typechecker* ts, const Type* infer, bool keep_lvalue) const 13 | { 14 | DefnTree* tree = nullptr; 15 | 16 | if(this->target_scope.has_value()) 17 | { 18 | // zpr::println("scope = '{}'", *this->target_scope); 19 | tree = ts->current()->lookupOrDeclareScope(this->target_scope->parents, this->target_scope->top_level); 20 | } 21 | else 22 | { 23 | tree = ts->current()->declareAnonymousNamespace(); 24 | } 25 | 26 | std::vector> stmts {}; 27 | 28 | auto _ = ts->pushTree(tree); 29 | 30 | // declare all bois first 31 | for(auto& stmt : this->body) 32 | { 33 | if(auto defn = dynamic_cast(stmt.get()); defn) 34 | TRY(defn->declare(ts)); 35 | } 36 | 37 | for(auto& stmt : this->body) 38 | stmts.push_back(TRY(stmt->typecheck(ts)).take_stmt()); 39 | 40 | return TCResult::ofVoid(m_location, std::move(stmts)); 41 | } 42 | 43 | bool Block::checkAllPathsReturn(const Type* return_type) 44 | { 45 | for(auto& stmt : this->body) 46 | { 47 | // if this returns, we're good already (since we're looking from the back) 48 | if(auto ret = dynamic_cast(stmt.get()); ret != nullptr) 49 | { 50 | return true; 51 | } 52 | else if(auto if_stmt = dynamic_cast(stmt.get()); if_stmt != nullptr && if_stmt->else_case) 53 | { 54 | if(if_stmt->true_case->checkAllPathsReturn(return_type) 55 | && if_stmt->else_case->checkAllPathsReturn(return_type)) 56 | { 57 | return true; 58 | } 59 | } 60 | } 61 | 62 | return false; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /source/interp/ast/cast.cpp: -------------------------------------------------------------------------------- 1 | // cast.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/ast.h" 6 | #include "interp/cst.h" 7 | #include "interp/type.h" 8 | #include "interp/value.h" 9 | #include "interp/interp.h" 10 | #include "interp/eval_result.h" 11 | 12 | namespace sap::interp::ast 13 | { 14 | ErrorOr CastExpr::typecheck_impl(Typechecker* ts, const Type* infer, bool keep_lvalue) const 15 | { 16 | auto to = TRY(this->target_type->typecheck(ts)).type(); 17 | auto from = TRY(this->expr->typecheck(ts, /* infer: */ to)); 18 | 19 | cst::CastExpr::CastKind cast_kind; 20 | 21 | // TODO: warn for redundant casts (ie. where we already have implicit conversions, 22 | // or where to == from) 23 | if(ts->canImplicitlyConvert(from.type(), to)) 24 | { 25 | cast_kind = cst::CastExpr::CastKind::Implicit; 26 | } 27 | else 28 | { 29 | if(from.type()->isFloating() && to->isInteger()) 30 | cast_kind = cst::CastExpr::CastKind::FloatToInteger; 31 | else if(from.type()->isChar() && to->isInteger()) 32 | cast_kind = cst::CastExpr::CastKind::CharToInteger; 33 | else if(from.type()->isInteger() && to->isChar()) 34 | cast_kind = cst::CastExpr::CastKind::IntegerToChar; 35 | else if(from.type()->isPointer() && to->isPointer()) 36 | cast_kind = cst::CastExpr::CastKind::Pointer; 37 | else 38 | return ErrMsg(ts, "cannot cast from expression with type '{}' to type '{}'", from.type(), to); 39 | } 40 | 41 | return TCResult::ofRValue(m_location, std::move(from).take_expr(), cast_kind, to); 42 | } 43 | 44 | ErrorOr 45 | ImplicitUnionVariantCastExpr::typecheck_impl(Typechecker* ts, const Type* infer, bool keep_lvalue) const 46 | { 47 | // the expression must have a type already 48 | auto from = TRY(this->expr->typecheck(ts, /* infer: */ nullptr, /* keep_lvalue: */ keep_lvalue)); 49 | if(not from.type()->isUnion()) 50 | return ErrMsg(ts, "cannot cast an expression with non-union type '{}' to a union variant", from.type()); 51 | 52 | const auto union_type = from.type()->toUnion(); 53 | if(not union_type->hasCaseNamed(this->variant_name)) 54 | return ErrMsg(ts, "union '{}' has no variant named '{}'", union_type->name(), this->variant_name); 55 | 56 | const auto to = union_type->getCaseNamed(this->variant_name); 57 | 58 | auto result = std::make_unique(m_location, std::move(from).take_expr(), to, 59 | union_type, union_type->getCaseIndex(this->variant_name)); 60 | 61 | if(keep_lvalue && from.isLValue()) 62 | return TCResult::ofLValue(std::move(result), from.isMutable()); 63 | else 64 | return TCResult::ofRValue(std::move(result)); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /source/interp/ast/deref.cpp: -------------------------------------------------------------------------------- 1 | // deref.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/ast.h" 6 | #include "interp/cst.h" 7 | #include "interp/interp.h" 8 | 9 | namespace sap::interp::ast 10 | { 11 | ErrorOr DereferenceOp::typecheck_impl(Typechecker* ts, const Type* infer, bool keep_lvalue) const 12 | { 13 | auto inside = TRY(this->expr->typecheck(ts, infer)); 14 | auto inside_type = inside.type(); 15 | 16 | if(not inside_type->isOptional() && not inside_type->isPointer()) 17 | return ErrMsg(ts, "invalid use of '!' on non-pointer, non-optional type '{}'", inside_type); 18 | 19 | if(inside_type->isPointer() && inside_type->pointerElement()->isVoid()) 20 | return ErrMsg(ts, "cannot dereference a pointer-to-void"); 21 | 22 | if(inside_type->isOptional()) 23 | { 24 | return TCResult::ofRValue(m_location, inside_type->optionalElement(), 25 | std::move(inside).take_expr()); 26 | } 27 | else if(inside_type->isMutablePointer()) 28 | { 29 | return TCResult::ofMutableLValue(m_location, inside_type->pointerElement(), 30 | std::move(inside).take_expr()); 31 | } 32 | else 33 | { 34 | return TCResult::ofImmutableLValue(m_location, inside_type->pointerElement(), 35 | std::move(inside).take_expr()); 36 | } 37 | } 38 | 39 | ErrorOr AddressOfOp::typecheck_impl(Typechecker* ts, const Type* infer, bool keep_lvalue) const 40 | { 41 | auto inside = TRY(this->expr->typecheck(ts, infer, /* keep_lvalue: */ true)); 42 | if(not inside.isLValue()) 43 | return ErrMsg(ts, "cannot take the address of a non-lvalue"); 44 | 45 | if(this->is_mutable && not inside.isMutable()) 46 | return ErrMsg(ts, "cannot create a mutable pointer to an immutable value"); 47 | 48 | auto out_type = inside.type()->pointerTo(/* mutable: */ this->is_mutable); 49 | return TCResult::ofRValue(m_location, out_type, this->is_mutable, 50 | std::move(inside).take_expr()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /source/interp/ast/dotop.cpp: -------------------------------------------------------------------------------- 1 | // dotop.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/ast.h" 6 | #include "interp/cst.h" 7 | #include "interp/interp.h" 8 | 9 | namespace sap::interp::ast 10 | { 11 | ErrorOr DotOp::typecheck_impl(Typechecker* ts, const Type* infer, bool keep_lvalue) const 12 | { 13 | auto lhs_res = TRY(this->lhs->typecheck(ts, /* infer: */ nullptr, /* keep_lvalue: */ keep_lvalue)); 14 | auto ltype = lhs_res.type(); 15 | 16 | const StructType* struct_type = nullptr; 17 | if(this->is_optional) 18 | { 19 | if(not ltype->isOptional() && not ltype->isPointer()) 20 | return ErrMsg(ts, "invalid use of '?.' operator on a non-pointer, non-optional type '{}'", ltype); 21 | 22 | auto lelm_type = ltype->isPointer() ? ltype->pointerElement() : ltype->optionalElement(); 23 | if(not lelm_type->isStruct()) 24 | return ErrMsg(ts, "invalid use of '?.' operator on a non-struct type '{}'", lelm_type); 25 | 26 | struct_type = lelm_type->toStruct(); 27 | } 28 | else 29 | { 30 | if(not ltype->isStruct()) 31 | return ErrMsg(ts, "invalid use of '.' operator on a non-struct type '{}'", ltype); 32 | 33 | struct_type = ltype->toStruct(); 34 | } 35 | 36 | assert(struct_type != nullptr); 37 | if(not struct_type->hasFieldNamed(this->rhs)) 38 | return ErrMsg(ts, "type '{}' has no field named '{}'", ltype, this->rhs); 39 | 40 | auto field_type = struct_type->getFieldNamed(this->rhs); 41 | const Type* result_type = [&]() { 42 | if(this->is_optional) 43 | { 44 | return ltype->isOptional() // 45 | ? (const Type*) field_type->optionalOf() 46 | : (const Type*) field_type->pointerTo(ltype->isMutablePointer()); 47 | } 48 | else 49 | { 50 | return field_type; 51 | } 52 | }(); 53 | 54 | bool is_lvalue = lhs_res.isLValue(); 55 | bool is_mutable = lhs_res.isMutable(); 56 | 57 | auto dot_op = std::make_unique(m_location, result_type, this->is_optional, struct_type, 58 | std::move(lhs_res).take_expr(), this->rhs); 59 | 60 | if(this->is_optional) 61 | { 62 | // if we're optional, make an rvalue always. 63 | // this means we can't do a?.b = ... , but that's fine probably. 64 | return TCResult::ofRValue(std::move(dot_op)); 65 | } 66 | else 67 | { 68 | // otherwise, we copy the lvalue-ness and mutability of the lhs. 69 | if(is_lvalue) 70 | return TCResult::ofLValue(std::move(dot_op), is_mutable); 71 | else 72 | return TCResult::ofRValue(std::move(dot_op)); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /source/interp/ast/for.cpp: -------------------------------------------------------------------------------- 1 | // for.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/ast.h" 6 | #include "interp/cst.h" 7 | #include "interp/interp.h" 8 | 9 | namespace sap::interp::ast 10 | { 11 | ErrorOr ForLoop::typecheck_impl(Typechecker* ts, const Type* infer, bool keep_lvalue) const 12 | { 13 | // everything should be in its own scope. 14 | auto tree = ts->current()->declareAnonymousNamespace(); 15 | auto _ = ts->pushTree(tree); 16 | 17 | auto ret = std::make_unique(m_location); 18 | 19 | if(this->init != nullptr) 20 | ret->init = TRY(this->init->typecheck(ts)).take_stmt(); 21 | 22 | if(this->cond != nullptr) 23 | { 24 | auto cond_res = TRY(this->cond->typecheck(ts, Type::makeBool())); 25 | if(not cond_res.type()->isBool()) 26 | return ErrMsg(ts, "cannot convert '{}' to boolean condition", cond_res.type()); 27 | 28 | ret->cond = std::move(cond_res).take_expr(); 29 | } 30 | 31 | if(this->update != nullptr) 32 | ret->update = TRY(this->update->typecheck(ts)).take_stmt(); 33 | 34 | { 35 | auto t = ts->enterLoopBody(); 36 | ret->body = TRY(this->body->typecheck(ts)).take(); 37 | } 38 | 39 | return TCResult::ofVoid(std::move(ret)); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /source/interp/ast/fstring.cpp: -------------------------------------------------------------------------------- 1 | // fstring.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/ast.h" 6 | #include "interp/cst.h" 7 | #include "interp/interp.h" 8 | 9 | namespace sap::interp::ast 10 | { 11 | ErrorOr FStringExpr::typecheck_impl(Typechecker* ts, const Type* infer, bool keep_lvalue) const 12 | { 13 | std::vector new_parts {}; 14 | 15 | for(auto& part : this->parts) 16 | { 17 | if(auto expr = std::get_if>(&part); expr != nullptr) 18 | { 19 | auto loc = expr->get()->loc(); 20 | auto call = std::make_unique(loc); 21 | 22 | auto name = std::make_unique(loc); 23 | name->name.name = "to_string"; 24 | 25 | call->callee = std::move(name); 26 | call->rewritten_ufcs = true; 27 | call->arguments.push_back(FunctionCall::Arg { 28 | .name = std::nullopt, 29 | .value = std::move(*const_cast*>(expr)), 30 | }); 31 | 32 | auto cst_call = TRY(call->typecheck(ts)).take_expr(); 33 | if(auto t = cst_call->type(); not t->isString()) 34 | return ErrMsg(loc, "`to_string` method returned non-string type '{}'", t); 35 | 36 | new_parts.push_back(Right(std::move(cst_call))); 37 | } 38 | else 39 | { 40 | new_parts.push_back(Left(std::get(part))); 41 | } 42 | } 43 | 44 | return TCResult::ofRValue(m_location, std::move(new_parts)); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /source/interp/ast/hook.cpp: -------------------------------------------------------------------------------- 1 | // hook.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/ast.h" 6 | #include "interp/cst.h" 7 | #include "interp/interp.h" 8 | 9 | namespace sap::interp::ast 10 | { 11 | ErrorOr HookBlock::typecheck_impl(Typechecker* ts, const Type* infer, bool keep_lvalue) const 12 | { 13 | if(ts->isCurrentlyInFunction()) 14 | return ErrMsg(ts, "hook blocks can only appear the top level"); 15 | 16 | auto block = TRY(this->body->typecheck(ts, infer, keep_lvalue)).take(); 17 | ts->interpreter()->addHookBlock(this->phase, block.get()); 18 | 19 | return TCResult::ofVoid(m_location, std::move(block)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /source/interp/ast/ident.cpp: -------------------------------------------------------------------------------- 1 | // ident.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "location.h" // for error 6 | 7 | #include "interp/ast.h" // for Ident, Declaration 8 | #include "interp/type.h" // for Type 9 | #include "interp/interp.h" // for Interpreter, StackFrame, DefnTree 10 | #include "interp/eval_result.h" // for EvalResult 11 | 12 | namespace sap::interp::ast 13 | { 14 | ErrorOr Ident::typecheck_impl(Typechecker* ts, const Type* infer, bool keep_lvalue) const 15 | { 16 | auto tree = ts->current(); 17 | auto decls = TRY(tree->lookup(this->name)); 18 | 19 | // TODO: use 'infer' to disambiguate references to functions, or 20 | // have a new kind of TCResult that returns a list of (unresolved) things instead of just one 21 | 22 | assert(decls.size() > 0); 23 | if(decls.size() == 1) 24 | { 25 | auto resolved_decl = decls[0]; 26 | auto dt = resolved_decl->type; 27 | 28 | if(not keep_lvalue && not dt->isCloneable()) 29 | return ErrMsg(ts, "'{}' values cannot be copied; use '*' to move", dt); 30 | 31 | auto ident = std::make_unique(m_location, dt, this->name, resolved_decl); 32 | return TCResult::ofLValue(std::move(ident), resolved_decl->is_mutable); 33 | } 34 | else 35 | { 36 | return ErrMsg(ts, "ambiguous reference to '{}'", this->name); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /source/interp/ast/if.cpp: -------------------------------------------------------------------------------- 1 | // if.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/ast.h" 6 | #include "interp/cst.h" 7 | #include "interp/interp.h" 8 | #include "interp/eval_result.h" 9 | 10 | namespace sap::interp::ast 11 | { 12 | ErrorOr IfStmt::typecheck_impl(Typechecker* ts, const Type* infer, bool keep_lvalue) const 13 | { 14 | auto cond = TRY(this->if_cond->typecheck(ts, Type::makeBool())); 15 | if(not cond.type()->isBool()) 16 | return ErrMsg(ts, "cannot convert '{}' to boolean condition", cond.type()); 17 | 18 | auto new_if = std::make_unique(m_location); 19 | new_if->if_cond = std::move(cond).take_expr(); 20 | new_if->if_body = TRY(this->true_case->typecheck(ts)).take(); 21 | 22 | if(this->else_case) 23 | new_if->else_body = TRY(this->else_case->typecheck(ts)).take(); 24 | 25 | return TCResult::ofVoid(std::move(new_if)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /source/interp/ast/import.cpp: -------------------------------------------------------------------------------- 1 | // import.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "sap/config.h" 6 | #include "sap/frontend.h" 7 | 8 | #include "tree/document.h" 9 | #include "tree/container.h" 10 | 11 | #include "interp/ast.h" 12 | #include "interp/interp.h" 13 | 14 | namespace sap::interp::ast 15 | { 16 | ErrorOr ImportStmt::typecheck_impl(Typechecker* ts, const Type* infer, bool keep_lvalue) const 17 | { 18 | auto cs = ts->interpreter(); 19 | 20 | auto resolved = TRY(sap::paths::resolveLibrary(m_location, this->file_path)); 21 | if(auto dir = stdfs::path(resolved); stdfs::is_directory(dir)) 22 | { 23 | auto name = dir.stem(); 24 | name.replace_extension(".sap"); 25 | 26 | auto lib_file = dir / name; 27 | if(not stdfs::exists(lib_file)) 28 | return ErrMsg(ts, "imported folder '{}' does not contain a '{}' file", resolved, name.string()); 29 | 30 | resolved = lib_file.string(); 31 | } 32 | 33 | if(cs->wasFileImported(resolved)) 34 | return TCResult::ofVoid(m_location); 35 | 36 | cs->addImportedFile(resolved); 37 | 38 | if(auto e = watch::addFileToWatchList(resolved); e.is_err()) 39 | return ErrMsg(ts, "{}", e.error()); 40 | 41 | auto file = cs->loadFile(resolved); 42 | auto doc = TRY(frontend::parse(cs->keepStringAlive(resolved), file.chars())); 43 | 44 | if(doc.haveDocStart()) 45 | return ErrMsg(ts, "file '{}' contains a '\\start_document', which cannot be imported"); 46 | 47 | auto imported_block = std::make_unique(this->loc()); 48 | imported_block->body = std::move(doc).takePreamble(); 49 | imported_block->target_scope = QualifiedId { .top_level = true }; 50 | 51 | auto _ = ts->pushTree(ts->top()); 52 | return TCResult::ofVoid(m_location, TRY(imported_block->typecheck(ts)).take()); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /source/interp/ast/logical_op.cpp: -------------------------------------------------------------------------------- 1 | // logical_op.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "util.h" 6 | 7 | #include "interp/ast.h" 8 | #include "interp/cst.h" 9 | #include "interp/type.h" 10 | #include "interp/value.h" 11 | #include "interp/interp.h" 12 | #include "interp/eval_result.h" 13 | 14 | namespace sap::interp::ast 15 | { 16 | const char* op_to_string(LogicalBinOp::Op op) 17 | { 18 | switch(op) 19 | { 20 | using enum LogicalBinOp::Op; 21 | case And: return "and"; 22 | case Or: return "or"; 23 | } 24 | util::unreachable(); 25 | } 26 | 27 | static cst::LogicalBinOp::Op ast_op_to_cst_op(LogicalBinOp::Op op) 28 | { 29 | switch(op) 30 | { 31 | using enum LogicalBinOp::Op; 32 | case And: return cst::LogicalBinOp::Op::And; 33 | case Or: return cst::LogicalBinOp::Op::Or; 34 | } 35 | util::unreachable(); 36 | } 37 | 38 | ErrorOr LogicalBinOp::typecheck_impl(Typechecker* ts, const Type* infer, bool keep_lvalue) const 39 | { 40 | auto lres = TRY(this->lhs->typecheck(ts, /* infer: */ Type::makeBool())); 41 | auto rres = TRY(this->rhs->typecheck(ts, /* infer: */ Type::makeBool())); 42 | 43 | if(not lres.type()->isBool()) 44 | { 45 | return ErrMsg(this->lhs->loc(), "invalid type for operand of '{}': expected bool, got '{}'", 46 | op_to_string(this->op), lres.type()); 47 | } 48 | else if(not rres.type()->isBool()) 49 | { 50 | return ErrMsg(this->rhs->loc(), "invalid type for operand of '{}': expected bool, got '{}'", 51 | op_to_string(this->op), rres.type()); 52 | } 53 | 54 | return TCResult::ofRValue(m_location, ast_op_to_cst_op(this->op), 55 | std::move(lres).take_expr(), std::move(rres).take_expr()); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /source/interp/ast/misc.cpp: -------------------------------------------------------------------------------- 1 | // misc.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/ast.h" 6 | #include "interp/interp.h" 7 | 8 | namespace sap::interp::ast 9 | { 10 | ErrorOr ArraySpreadOp::typecheck_impl(Typechecker* ts, const Type* infer, bool keep_lvalue) const 11 | { 12 | auto value = TRY(this->expr->typecheck(ts, infer)); 13 | if(not value.type()->isArray()) 14 | return ErrMsg(ts, "invalid use of '...' operator on non-array type '{}'", value.type()); 15 | 16 | auto ret_type = Type::makeArray(value.type()->arrayElement(), /* variadic: */ true); 17 | 18 | if(value.isLValue() && not ret_type->arrayElement()->isCloneable()) 19 | { 20 | return ErrMsg(ts, "arrays of type '{}' cannot be spread with `...` without first moving; use `...*`", 21 | ret_type->arrayElement()); 22 | } 23 | 24 | return TCResult::ofRValue(m_location, ret_type, std::move(value).take_expr()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /source/interp/ast/move.cpp: -------------------------------------------------------------------------------- 1 | // move.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/ast.h" 6 | #include "interp/interp.h" 7 | 8 | namespace sap::interp::ast 9 | { 10 | ErrorOr MoveExpr::typecheck_impl(Typechecker* ts, const Type* infer, bool keep_lvalue) const 11 | { 12 | auto ret = TRY(this->expr->typecheck(ts, infer, /* keep_lvalue: */ true)); 13 | if(not ret.isLValue()) 14 | return ErrMsg(ts, "invalid use of move-expression on rvalue"); 15 | 16 | return TCResult::ofRValue(m_location, std::move(ret).take_expr()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /source/interp/ast/optional.cpp: -------------------------------------------------------------------------------- 1 | // optional.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/ast.h" 6 | #include "interp/interp.h" 7 | 8 | namespace sap::interp::ast 9 | { 10 | ErrorOr NullCoalesceOp::typecheck_impl(Typechecker* ts, const Type* infer, bool keep_lvalue) const 11 | { 12 | auto lres = TRY(this->lhs->typecheck(ts)); 13 | auto rres = TRY(this->rhs->typecheck(ts)); 14 | 15 | // this is a little annoying because while we don't want "?int + &int", "?&int and &int" should still work 16 | if(not lres.type()->isOptional() && not lres.type()->isPointer()) 17 | return ErrMsg(ts, "invalid use of '??' with non-pointer, non-optional type '{}' on the left", lres.type()); 18 | 19 | // the rhs type is either the same as the lhs, or it's the element of the lhs optional/ptr 20 | auto lelm_type = lres.type()->isOptional() ? lres.type()->optionalElement() : lres.type()->pointerElement(); 21 | 22 | bool is_flatmap = (lres.type() == rres.type() || ts->canImplicitlyConvert(lres.type(), rres.type())); 23 | bool is_valueor = (lelm_type == rres.type() || ts->canImplicitlyConvert(lelm_type, rres.type())); 24 | 25 | if(not(is_flatmap || is_valueor)) 26 | return ErrMsg(ts, "invalid use of '??' with mismatching types '{}' and '{}'", lres.type(), rres.type()); 27 | 28 | // return whatever the rhs type is -- be it optional or pointer. 29 | return TCResult::ofRValue(m_location, rres.type(), 30 | is_flatmap ? cst::NullCoalesceOp::Kind::Flatmap : cst::NullCoalesceOp::Kind::ValueOr, 31 | std::move(lres).take_expr(), std::move(rres).take_expr()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /source/interp/ast/return.cpp: -------------------------------------------------------------------------------- 1 | // return.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/ast.h" 6 | #include "interp/interp.h" 7 | 8 | namespace sap::interp::ast 9 | { 10 | ErrorOr ReturnStmt::typecheck_impl(Typechecker* ts, const Type* infer, bool keep_lvalue) const 11 | { 12 | if(not ts->isCurrentlyInFunction()) 13 | return ErrMsg(ts, "invalid use of 'return' outside of a function body"); 14 | 15 | auto ret_type = ts->getCurrentFunctionReturnType(); 16 | 17 | std::unique_ptr return_value {}; 18 | if(this->expr != nullptr) 19 | return_value = TRY(this->expr->typecheck(ts, ret_type, /* move: */ true)).take_expr(); 20 | 21 | auto expr_type = return_value ? return_value->type() : Type::makeVoid(); 22 | if(not ts->canImplicitlyConvert(expr_type, ret_type)) 23 | return ErrMsg(ts, "cannot return value of type '{}' in function returning '{}'", expr_type, ret_type); 24 | 25 | return TCResult::ofVoid(m_location, ret_type, std::move(return_value)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /source/interp/ast/subscript_op.cpp: -------------------------------------------------------------------------------- 1 | // subscript_op.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/ast.h" 6 | #include "interp/interp.h" 7 | #include "interp/polymorph.h" 8 | 9 | namespace sap::interp::ast 10 | { 11 | ErrorOr SubscriptOp::typecheck_impl(Typechecker* ts, const Type* infer, bool keep_lvalue) const 12 | { 13 | // check whether this is a explicit generic-function instantiation 14 | // needs to fulfil these criteria: 15 | // 1. lhs is an identifier 16 | // 2. we can resolve the lhs identifier to a generic decl 17 | if(auto lhs_ident = dynamic_cast(this->array.get())) 18 | { 19 | auto _x = ts->current()->lookup(lhs_ident->name); 20 | if(_x.is_err()) 21 | goto not_generic_function; 22 | 23 | auto& decls = _x.unwrap(); 24 | 25 | bool found_generic = false; 26 | for(auto& decl : decls) 27 | { 28 | if(decl->generic_func != nullptr) 29 | found_generic = true; 30 | } 31 | 32 | if(not found_generic) 33 | goto not_generic_function; 34 | 35 | // ok we have at least one generic function, try to do some generic stuff. 36 | return polymorph::createGenericOverloadSet(ts, infer, lhs_ident->name, std::move(decls), this->indices); 37 | } 38 | 39 | not_generic_function: 40 | auto lhs = TRY(this->array->typecheck(ts, /* infer: */ nullptr, keep_lvalue)); 41 | 42 | // TODO: multiple subscript indices for overloaded operators 43 | auto rhs = TRY(this->indices[0].value->typecheck(ts)); 44 | 45 | auto ltype = lhs.type(); 46 | auto rtype = rhs.type(); 47 | if(not ltype->isArray()) 48 | return ErrMsg(ts, "invalid use of operator '[]' on non-array type '{}'", ltype); 49 | if(not rtype->isInteger()) 50 | return ErrMsg(ts, "subscript index must be an integer"); 51 | 52 | std::vector> new_indices {}; 53 | new_indices.push_back(std::move(rhs).take_expr()); 54 | 55 | // FIXME: lots of code dupe 56 | if(lhs.isLValue()) 57 | { 58 | if(lhs.isMutable()) 59 | { 60 | return TCResult::ofMutableLValue(m_location, ltype->arrayElement(), 61 | std::move(lhs).take_expr(), std::move(new_indices)); 62 | } 63 | else 64 | { 65 | return TCResult::ofImmutableLValue(m_location, ltype->arrayElement(), 66 | std::move(lhs).take_expr(), std::move(new_indices)); 67 | } 68 | } 69 | else 70 | { 71 | return TCResult::ofRValue(m_location, ltype->arrayElement(), std::move(lhs).take_expr(), 72 | std::move(new_indices)); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /source/interp/ast/type_expr.cpp: -------------------------------------------------------------------------------- 1 | // type_expr.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/ast.h" 6 | #include "interp/type.h" 7 | #include "interp/value.h" 8 | #include "interp/interp.h" 9 | #include "interp/eval_result.h" 10 | 11 | namespace sap::interp::ast 12 | { 13 | ErrorOr TypeExpr::typecheck_impl(Typechecker* ts, const Type* infer, bool keep_lvalue) const 14 | { 15 | auto t = TRY(ts->resolveType(this->ptype)); 16 | return TCResult::ofRValue(m_location, t); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /source/interp/ast/unary_op.cpp: -------------------------------------------------------------------------------- 1 | // unary_op.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "util.h" // for is_one_of 6 | 7 | #include "interp/ast.h" // for BinaryOp::Op, BinaryOp, Expr, Binary... 8 | #include "interp/type.h" // for Type, ArrayType 9 | #include "interp/value.h" // for Value 10 | #include "interp/interp.h" // for Interpreter 11 | #include "interp/eval_result.h" // for EvalResult, TRY_VALUE 12 | 13 | namespace sap::interp::ast 14 | { 15 | const char* op_to_string(UnaryOp::Op op) 16 | { 17 | switch(op) 18 | { 19 | using enum UnaryOp::Op; 20 | case Plus: return "+"; 21 | case Minus: return "-"; 22 | case LogicalNot: return "not"; 23 | } 24 | util::unreachable(); 25 | } 26 | 27 | static cst::UnaryOp::Op ast_op_to_cst_op(UnaryOp::Op op) 28 | { 29 | switch(op) 30 | { 31 | using enum UnaryOp::Op; 32 | case Plus: return cst::UnaryOp::Op::Plus; 33 | case Minus: return cst::UnaryOp::Op::Minus; 34 | case LogicalNot: return cst::UnaryOp::Op::LogicalNot; 35 | } 36 | util::unreachable(); 37 | } 38 | 39 | ErrorOr UnaryOp::typecheck_impl(Typechecker* ts, const Type* infer, bool keep_lvalue) const 40 | { 41 | switch(this->op) 42 | { 43 | case Op::Plus: 44 | case Op::Minus: { 45 | auto inside = TRY(this->expr->typecheck(ts)); 46 | auto ty = inside.type(); 47 | 48 | if(not(ty->isInteger() || ty->isFloating() || ty->isLength())) 49 | { 50 | return ErrMsg(this->expr->loc(), "invalid type '{}' for unary '{}' operator", ty, 51 | op_to_string(this->op)); 52 | } 53 | 54 | // same type as we got in 55 | return TCResult::ofRValue(m_location, ty, ast_op_to_cst_op(this->op), 56 | std::move(inside).take_expr()); 57 | } 58 | 59 | case Op::LogicalNot: { 60 | auto inside = TRY(this->expr->typecheck(ts, /* infer: */ Type::makeBool())); 61 | auto ty = inside.type(); 62 | 63 | if(not ty->isBool()) 64 | return ErrMsg(this->expr->loc(), "invalid type '{}' for unary logical not operator", ty); 65 | 66 | return TCResult::ofRValue(m_location, Type::makeBool(), ast_op_to_cst_op(this->op), 67 | std::move(inside).take_expr()); 68 | } 69 | } 70 | util::unreachable(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /source/interp/ast/using.cpp: -------------------------------------------------------------------------------- 1 | // using.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "sap/frontend.h" 6 | 7 | #include "tree/document.h" 8 | #include "tree/container.h" 9 | 10 | #include "interp/ast.h" 11 | #include "interp/interp.h" 12 | 13 | namespace sap::interp::ast 14 | { 15 | ErrorOr UsingStmt::typecheck_impl(Typechecker* ts, const Type* infer, bool keep_lvalue) const 16 | { 17 | auto self = ts->current(); 18 | if(auto maybe_decls = self->lookup(this->module); maybe_decls.ok()) 19 | { 20 | for(auto& decl : *maybe_decls) 21 | self->useDeclaration(m_location, decl, this->alias); 22 | 23 | return TCResult::ofVoid(m_location); 24 | } 25 | 26 | auto tree = self->lookupScope(this->module.parents, this->module.top_level); 27 | if(tree == nullptr || (tree = tree->lookupNamespace(this->module.name)) == nullptr) 28 | return ErrMsg(m_location, "no declaration named '{}'", this->module); 29 | 30 | TRY(self->useNamespace(m_location, tree, this->alias)); 31 | return TCResult::ofVoid(m_location); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /source/interp/ast/variable.cpp: -------------------------------------------------------------------------------- 1 | // variable.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/ast.h" // for VariableDefn, Expr, Declaration, Var... 6 | #include "interp/type.h" // for Type 7 | #include "interp/interp.h" // for Interpreter, DefnTree, StackFrame 8 | #include "interp/eval_result.h" // for EvalResult, TRY_VALUE 9 | 10 | namespace sap::interp::ast 11 | { 12 | ErrorOr VariableDefn::declare(Typechecker* ts) const 13 | { 14 | // this does nothing because we don't have the type 15 | return Ok(); 16 | } 17 | 18 | ErrorOr VariableDefn::typecheck_impl(Typechecker* ts, const Type* infer, bool keep_lvalue) const 19 | { 20 | // if we have neither, it's an error 21 | if(not this->explicit_type.has_value() && this->initialiser == nullptr) 22 | return ErrMsg(ts, "variable without explicit type must have an initialiser"); 23 | 24 | const Type* var_type = nullptr; 25 | std::unique_ptr init {}; 26 | 27 | if(this->explicit_type.has_value()) 28 | { 29 | var_type = TRY(ts->resolveType(*this->explicit_type)); 30 | if(var_type->isVoid()) 31 | return ErrMsg(ts, "cannot declare variable of type 'void'"); 32 | 33 | if(this->initialiser != nullptr) 34 | { 35 | init = TRY(this->initialiser->typecheck(ts, /* infer: */ var_type)).take_expr(); 36 | if(not ts->canImplicitlyConvert(init->type(), var_type)) 37 | { 38 | return ErrMsg(ts, "cannot initialise variable of type '{}' with expression of type '{}'", // 39 | var_type, init->type()); 40 | } 41 | } 42 | } 43 | else 44 | { 45 | assert(this->initialiser != nullptr); 46 | init = TRY(this->initialiser->typecheck(ts)).take_expr(); 47 | var_type = init->type(); 48 | } 49 | 50 | auto decl = cst::Declaration(m_location, ts->current(), this->name, var_type, this->is_mutable); 51 | this->declaration = TRY(ts->current()->declare(std::move(decl))); 52 | 53 | auto defn = std::make_unique(m_location, this->declaration, this->is_global, std::move(init), 54 | this->is_mutable); 55 | 56 | this->declaration->define(defn.get()); 57 | return TCResult::ofVoid(std::move(defn)); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /source/interp/ast/while.cpp: -------------------------------------------------------------------------------- 1 | // while.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/ast.h" 6 | #include "interp/interp.h" 7 | 8 | namespace sap::interp::ast 9 | { 10 | ErrorOr WhileLoop::typecheck_impl(Typechecker* ts, const Type* infer, bool keep_lvalue) const 11 | { 12 | auto cond = TRY(this->condition->typecheck(ts, Type::makeBool())); 13 | if(not cond.type()->isBool()) 14 | return ErrMsg(ts, "cannot convert '{}' to boolean condition", cond.type()); 15 | 16 | auto _ = ts->enterLoopBody(); 17 | auto new_body = TRY(this->body->typecheck(ts)).take(); 18 | 19 | return TCResult::ofVoid(m_location, std::move(cond).take_expr(), std::move(new_body)); 20 | } 21 | 22 | ErrorOr BreakStmt::typecheck_impl(Typechecker* ts, const Type* infer, bool keep_lvalue) const 23 | { 24 | if(not ts->isCurrentlyInLoopBody()) 25 | return ErrMsg(ts, "invalid use of 'break' outside of a loop body"); 26 | 27 | return TCResult::ofVoid(m_location); 28 | } 29 | 30 | ErrorOr ContinueStmt::typecheck_impl(Typechecker* ts, const Type* infer, bool keep_lvalue) const 31 | { 32 | if(not ts->isCurrentlyInLoopBody()) 33 | return ErrMsg(ts, "invalid use of 'continue' outside of a loop body"); 34 | 35 | return TCResult::ofVoid(m_location); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /source/interp/basedefs.h: -------------------------------------------------------------------------------- 1 | // basedefs.h 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | namespace sap::interp 8 | { 9 | struct QualifiedId 10 | { 11 | bool top_level = false; 12 | std::vector parents; 13 | std::string name; 14 | 15 | bool operator==(const QualifiedId&) const = default; 16 | bool operator!=(const QualifiedId&) const = default; 17 | 18 | std::string str() const; 19 | 20 | QualifiedId parentScopeFor(std::string name) const; 21 | QualifiedId withoutLastComponent() const; 22 | 23 | static QualifiedId named(std::string name); 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /source/interp/builtins/be_alignment.cpp: -------------------------------------------------------------------------------- 1 | // be_alignment.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "sap/style.h" 6 | #include "tree/base.h" 7 | #include "interp/value.h" 8 | #include "interp/builtin_types.h" 9 | 10 | namespace sap::interp::builtin 11 | { 12 | using PT = frontend::PType; 13 | 14 | const Type* builtin::BE_Alignment::type = nullptr; 15 | 16 | frontend::PType BE_Alignment::enumeratorType() 17 | { 18 | return PT::named(frontend::TYPE_INT); 19 | } 20 | 21 | std::vector BE_Alignment::enumerators() 22 | { 23 | return util::vectorOf( // 24 | make_builtin_enumerator("Left", static_cast(Alignment::Left)), // 25 | make_builtin_enumerator("Right", static_cast(Alignment::Right)), // 26 | make_builtin_enumerator("Centred", static_cast(Alignment::Centre)), // 27 | make_builtin_enumerator("Centered", static_cast(Alignment::Centre)), // 28 | make_builtin_enumerator("Justified", static_cast(Alignment::Justified))); 29 | } 30 | 31 | Value builtin::BE_Alignment::make(Alignment alignment) 32 | { 33 | return Value::enumerator(type->toEnum(), Value::integer(static_cast(alignment))); 34 | } 35 | 36 | Alignment builtin::BE_Alignment::unmake(const Value& value) 37 | { 38 | return static_cast(value.getEnumerator().getInteger()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /source/interp/builtins/bs_glyph_adjustment.cpp: -------------------------------------------------------------------------------- 1 | // bs_glyph_adjustment.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/value.h" 6 | #include "interp/interp.h" 7 | #include "interp/builtin_types.h" 8 | 9 | namespace sap::interp::builtin 10 | { 11 | using PT = frontend::PType; 12 | using Field = ast::StructDefn::Field; 13 | 14 | const Type* builtin::BS_GlyphSpacingAdjustment::type = nullptr; 15 | std::vector builtin::BS_GlyphSpacingAdjustment::fields() 16 | { 17 | auto pt_char = PT::named(frontend::TYPE_CHAR); 18 | auto pt_float = PT::named(frontend::TYPE_FLOAT); 19 | 20 | return util::vectorOf( // 21 | Field { .name = "match", .type = PT::array(PT::array(pt_char)) }, // 22 | Field { .name = "adjust", .type = PT::array(pt_float) } // 23 | ); 24 | } 25 | 26 | Value builtin::BS_GlyphSpacingAdjustment::make(Evaluator* ev, const GlyphSpacingAdjustment& adj) 27 | { 28 | auto matches = make_array(Type::makeArray(Type::makeChar()), adj.match, [](const auto& m) { 29 | return make_array(Type::makeChar(), m, [](const auto& ch) { return Value::character(ch); }); 30 | }); 31 | 32 | auto adjusts = make_array(Type::makeFloating(), adj.adjust, [](double x) { return Value::floating(x); }); 33 | 34 | return StructMaker(BS_GlyphSpacingAdjustment::type->toStruct()) 35 | .set("match", std::move(matches)) 36 | .set("adjust", std::move(adjusts)) 37 | .make(); 38 | } 39 | 40 | GlyphSpacingAdjustment builtin::BS_GlyphSpacingAdjustment::unmake(Evaluator* ev, const Value& value) 41 | { 42 | GlyphSpacingAdjustment ret {}; 43 | ret.match = unmake_array>(value.getStructField("match"), [](const auto& v) { 44 | return unmake_array(v, [](const auto& vv) { return vv.getChar(); }); 45 | }); 46 | 47 | ret.adjust = unmake_array(value.getStructField("adjust"), [](const auto& v) { 48 | return v.getFloating(); 49 | }); 50 | 51 | return ret; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /source/interp/builtins/bs_link_annotation.cpp: -------------------------------------------------------------------------------- 1 | // bs_link_annotation.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/interp.h" 6 | #include "interp/builtin_fns.h" 7 | #include "interp/builtin_types.h" 8 | 9 | namespace sap::interp::builtin 10 | { 11 | using PT = frontend::PType; 12 | using Field = ast::StructDefn::Field; 13 | 14 | const Type* builtin::BS_LinkAnnotation::type = nullptr; 15 | std::vector builtin::BS_LinkAnnotation::fields() 16 | { 17 | auto pt_len = PT::named(frontend::TYPE_LENGTH); 18 | auto pt_string = PT::named(frontend::TYPE_STRING); 19 | 20 | return util::vectorOf( // 21 | Field { .name = "position", .type = ptype_for_builtin() }, // 22 | Field { .name = "size", .type = ptype_for_builtin() }, // 23 | Field { .name = "destination", .type = ptype_for_builtin() } // 24 | ); 25 | } 26 | 27 | Value builtin::BS_LinkAnnotation::make(Evaluator* ev, LinkAnnotation annot) 28 | { 29 | return StructMaker(BS_LinkAnnotation::type->toStruct()) 30 | .set("position", BS_AbsPosition::make(ev, annot.position)) 31 | .set("size", BS_Size2d::make(ev, annot.size)) 32 | .set("destination", BS_AbsPosition::make(ev, annot.destination)) 33 | .make(); 34 | } 35 | 36 | LinkAnnotation builtin::BS_LinkAnnotation::unmake(Evaluator* ev, const Value& value) 37 | { 38 | auto position = BS_AbsPosition::unmake(ev, value.getStructField("position")); 39 | auto dest = BS_AbsPosition::unmake(ev, value.getStructField("destination")); 40 | auto size = BS_Size2d::unmake(ev, value.getStructField("size")); 41 | 42 | return LinkAnnotation { 43 | .position = std::move(position), 44 | .size = size.resolve(ev->currentStyle()), 45 | .destination = std::move(dest), 46 | }; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /source/interp/builtins/bs_outline_item.cpp: -------------------------------------------------------------------------------- 1 | // bs_outline_item.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/interp.h" 6 | #include "interp/builtin_fns.h" 7 | #include "interp/builtin_types.h" 8 | 9 | namespace sap::interp::builtin 10 | { 11 | using PT = frontend::PType; 12 | using Field = ast::StructDefn::Field; 13 | 14 | const Type* builtin::BS_OutlineItem::type = nullptr; 15 | std::vector builtin::BS_OutlineItem::fields() 16 | { 17 | auto pt_len = PT::named(frontend::TYPE_LENGTH); 18 | auto pt_string = PT::named(frontend::TYPE_STRING); 19 | 20 | return util::vectorOf( // 21 | Field { .name = "title", .type = pt_string }, // 22 | Field { .name = "position", .type = ptype_for_builtin() }, // 23 | Field { .name = "children", .type = PT::array(ptype_for_builtin()) } // 24 | ); 25 | } 26 | 27 | Value builtin::BS_OutlineItem::make(Evaluator* ev, OutlineItem pos) 28 | { 29 | return StructMaker(BS_OutlineItem::type->toStruct()) 30 | .set("title", Value::string(unicode::u32StringFromUtf8(pos.title))) 31 | .set("position", BS_AbsPosition::make(ev, pos.position)) 32 | .set("children", 33 | Value::array(BS_OutlineItem::type, 34 | util::map(pos.children, [ev](const auto& x) { return BS_OutlineItem::make(ev, x); }))) 35 | .make(); 36 | } 37 | 38 | OutlineItem builtin::BS_OutlineItem::unmake(Evaluator* ev, const Value& value) 39 | { 40 | auto title = value.getStructField("title").getUtf8String(); 41 | auto position = BS_AbsPosition::unmake(ev, value.getStructField("position")); 42 | auto children = util::map(value.getStructField("children").getArray(), [ev](const auto& child) { 43 | return BS_OutlineItem::unmake(ev, child); 44 | }); 45 | 46 | return OutlineItem { 47 | .title = std::move(title), 48 | .position = std::move(position), 49 | .children = std::move(children), 50 | }; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /source/interp/builtins/bs_pos2d.cpp: -------------------------------------------------------------------------------- 1 | // bs_pos2d.cpp 2 | // Copyright (c) 2023, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/interp.h" 6 | #include "interp/builtin_fns.h" 7 | #include "interp/builtin_types.h" 8 | 9 | namespace sap::interp::builtin 10 | { 11 | using PT = frontend::PType; 12 | using Field = ast::StructDefn::Field; 13 | 14 | const Type* builtin::BS_Pos2d::type = nullptr; 15 | std::vector builtin::BS_Pos2d::fields() 16 | { 17 | auto pt_len = PT::named(frontend::TYPE_LENGTH); 18 | return util::vectorOf( // 19 | Field { .name = "x", .type = pt_len }, // 20 | Field { .name = "y", .type = pt_len } // 21 | ); 22 | } 23 | 24 | Value BS_Pos2d::make(Evaluator* ev, Position pos) 25 | { 26 | return StructMaker(BS_Pos2d::type->toStruct()) // 27 | .set("x", Value::length(DynLength(pos.x()))) 28 | .set("y", Value::length(DynLength(pos.y()))) 29 | .make(); 30 | } 31 | 32 | Value builtin::BS_Pos2d::make(Evaluator* ev, DynLength2d pos) 33 | { 34 | return StructMaker(BS_Pos2d::type->toStruct()) // 35 | .set("x", Value::length(pos.x)) 36 | .set("y", Value::length(pos.y)) 37 | .make(); 38 | } 39 | 40 | Position builtin::BS_Pos2d::unmake(Evaluator* ev, const Value& value) 41 | { 42 | auto x = get_struct_field(value, "x", &Value::getLength); 43 | auto y = get_struct_field(value, "y", &Value::getLength); 44 | return Position { x.resolve(ev->currentStyle()), y.resolve(ev->currentStyle()) }; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /source/interp/builtins/bs_size2d.cpp: -------------------------------------------------------------------------------- 1 | // bs_size2d.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/interp.h" 6 | #include "interp/builtin_fns.h" 7 | #include "interp/builtin_types.h" 8 | 9 | namespace sap::interp::builtin 10 | { 11 | using PT = frontend::PType; 12 | using Field = ast::StructDefn::Field; 13 | 14 | const Type* builtin::BS_Size2d::type = nullptr; 15 | std::vector builtin::BS_Size2d::fields() 16 | { 17 | auto pt_len = PT::named(frontend::TYPE_LENGTH); 18 | return util::vectorOf( // 19 | Field { .name = "x", .type = pt_len }, // 20 | Field { .name = "y", .type = pt_len } // 21 | ); 22 | } 23 | 24 | Value BS_Size2d::make(Evaluator* ev, Size2d pos) 25 | { 26 | return StructMaker(BS_Size2d::type->toStruct()) // 27 | .set("x", Value::length(DynLength(pos.x()))) 28 | .set("y", Value::length(DynLength(pos.y()))) 29 | .make(); 30 | } 31 | 32 | Value builtin::BS_Size2d::make(Evaluator* ev, DynLength2d pos) 33 | { 34 | return StructMaker(BS_Size2d::type->toStruct()) // 35 | .set("x", Value::length(pos.x)) 36 | .set("y", Value::length(pos.y)) 37 | .make(); 38 | } 39 | 40 | DynLength2d builtin::BS_Size2d::unmake(Evaluator* ev, const Value& value) 41 | { 42 | auto x = get_struct_field(value, "x", &Value::getLength); 43 | auto y = get_struct_field(value, "y", &Value::getLength); 44 | return DynLength2d { x, y }; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /source/interp/builtins/bs_state.cpp: -------------------------------------------------------------------------------- 1 | // bs_state.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "sap/frontend.h" 6 | 7 | #include "interp/interp.h" 8 | #include "interp/builtin_types.h" 9 | 10 | namespace sap::interp::builtin 11 | { 12 | using PT = frontend::PType; 13 | using Field = ast::StructDefn::Field; 14 | 15 | const Type* builtin::BS_State::type = nullptr; 16 | std::vector builtin::BS_State::fields() 17 | { 18 | auto pt_int = PT::named(frontend::TYPE_INT); 19 | auto pt_size2d = ptype_for_builtin(); 20 | auto pt_font_family = ptype_for_builtin(); 21 | 22 | return util::vectorOf( // 23 | Field { .name = "layout_pass", .type = pt_int }, // 24 | Field { .name = "serif_font_family", .type = pt_font_family }, // 25 | Field { .name = "sans_font_family", .type = pt_font_family }, // 26 | Field { .name = "mono_font_family", .type = pt_font_family } // 27 | ); 28 | } 29 | 30 | Value builtin::BS_State::make(Evaluator* ev, const GlobalState& state) 31 | { 32 | return StructMaker(BS_State::type->toStruct()) // 33 | .set("layout_pass", Value::integer(checked_cast(state.layout_pass))) 34 | .set("serif_font_family", BS_FontFamily::make(ev, *state.document_settings->serif_font_family)) 35 | .set("sans_font_family", BS_FontFamily::make(ev, *state.document_settings->sans_font_family)) 36 | .set("mono_font_family", BS_FontFamily::make(ev, *state.document_settings->mono_font_family)) 37 | .make(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /source/interp/builtins/include.cpp: -------------------------------------------------------------------------------- 1 | // include.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "sap/config.h" 6 | #include "sap/frontend.h" 7 | 8 | #include "tree/base.h" 9 | #include "tree/document.h" 10 | #include "tree/container.h" 11 | 12 | #include "interp/interp.h" 13 | #include "interp/builtin_fns.h" 14 | 15 | namespace sap::interp::builtin 16 | { 17 | ErrorOr include_file(Evaluator* ev, std::vector& args) 18 | { 19 | assert(args.size() == 1); 20 | 21 | auto file_path = args[0].getUtf8String(); 22 | 23 | auto resolved = TRY(sap::paths::resolveLibrary(ev->loc(), file_path)); 24 | if(auto e = watch::addFileToWatchList(resolved); e.is_err()) 25 | return ErrMsg(ev, "{}", e.error()); 26 | 27 | auto cs = ev->interpreter(); 28 | auto file = cs->loadFile(resolved); 29 | auto doc = TRY(frontend::parse(cs->keepStringAlive(resolved), file.chars())); 30 | 31 | util::log("included file '{}'", resolved); 32 | return EvalResult::ofValue(ev->addToHeapAndGetPointer(Value::treeBlockObject(std::move(doc).takeContainer()))); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /source/interp/builtins/object_refs.cpp: -------------------------------------------------------------------------------- 1 | // object_refs.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/interp.h" 6 | #include "interp/builtin_fns.h" 7 | 8 | namespace sap::interp::builtin 9 | { 10 | ErrorOr ref_object(Evaluator* ev, std::vector& args) 11 | { 12 | assert(args.size() == 1); 13 | assert(args[0].type()->isPointer()); 14 | auto ty = args[0].type()->pointerElement(); 15 | 16 | if(args[0].getPointer() == nullptr) 17 | return ErrMsg(ev, "cannot call `ref()` with a null pointer"); 18 | 19 | auto& value = *args[0].getPointer(); 20 | 21 | if(ty->isTreeInlineObj()) 22 | { 23 | return EvalResult::ofValue(Value::treeInlineObjectRef( // 24 | const_cast(&value.getTreeInlineObj()))); 25 | } 26 | else if(ty->isTreeBlockObj()) 27 | { 28 | return EvalResult::ofValue( // 29 | Value::treeBlockObjectRef(const_cast(&value.getTreeBlockObj()))); 30 | } 31 | else if(ty->isLayoutObject()) 32 | { 33 | return EvalResult::ofValue( // 34 | Value::layoutObjectRef(const_cast(&value.getLayoutObject()))); 35 | } 36 | else 37 | { 38 | sap::internal_error("??? '{}'", ty); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /source/interp/builtins/start.cpp: -------------------------------------------------------------------------------- 1 | // start.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/interp.h" 6 | #include "interp/builtin_fns.h" 7 | #include "interp/builtin_types.h" 8 | 9 | namespace sap::interp::builtin 10 | { 11 | ErrorOr start_document(Evaluator* ev, std::vector& args) 12 | { 13 | assert(args.size() == 1); 14 | 15 | DocumentSettings settings {}; 16 | if(args[0].haveOptionalValue()) 17 | settings = TRY(BS_DocumentSettings::unmake(ev, **args[0].getOptional())); 18 | 19 | return EvalResult::ofValue(BS_DocumentSettings::make(ev, std::move(settings))); 20 | } 21 | 22 | ErrorOr request_layout(Evaluator* ev, std::vector& args) 23 | { 24 | assert(args.size() == 0); 25 | 26 | ev->requestLayout(); 27 | return EvalResult::ofVoid(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /source/interp/builtins/struct_maker.cpp: -------------------------------------------------------------------------------- 1 | // struct_maker.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "tree/base.h" 6 | #include "layout/base.h" 7 | 8 | #include "interp/ast.h" 9 | #include "interp/value.h" 10 | #include "interp/builtin_types.h" 11 | 12 | namespace sap::interp::builtin 13 | { 14 | StructMaker::StructMaker(const StructType* type) : m_type(type) 15 | { 16 | m_fields.resize(type->getFields().size()); 17 | } 18 | 19 | Value StructMaker::make() 20 | { 21 | std::vector fields {}; 22 | 23 | for(size_t i = 0; i < m_fields.size(); i++) 24 | { 25 | auto a = m_type->getFieldAtIndex(i); 26 | if(not m_fields[i].has_value()) 27 | { 28 | if(not a->isOptional()) 29 | sap::internal_error("unset field '{}'", m_type->getFields()[i].name); 30 | 31 | // it's optional -- make it null. 32 | fields.push_back(Value::optional(a->optionalElement(), std::nullopt)); 33 | } 34 | else 35 | { 36 | auto b = m_fields[i]->type(); 37 | if(a == b) 38 | fields.push_back(std::move(*m_fields[i])); 39 | else if(a->isOptional() && a->optionalElement() == b) 40 | fields.push_back(Value::optional(a->optionalElement(), std::move(*m_fields[i]))); 41 | else 42 | sap::internal_error("mismatched field {} ({}, {})", m_type->getFields()[i].name, a, b); 43 | } 44 | } 45 | 46 | return Value::structure(m_type, std::move(fields)); 47 | } 48 | 49 | StructMaker& StructMaker::set(zst::str_view name, Value value) 50 | { 51 | if(not m_type->hasFieldNamed(name)) 52 | sap::internal_error("struct '{}' has no field named '{}'", (const Type*) m_type, name); 53 | 54 | m_fields[m_type->getFieldIndex(name)] = std::move(value); 55 | return *this; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /source/interp/cst/assign_op.cpp: -------------------------------------------------------------------------------- 1 | // assign_op.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/cst.h" 6 | #include "interp/interp.h" 7 | 8 | namespace sap::interp::cst 9 | { 10 | // defined in binop.cpp 11 | extern ErrorOr evaluateBinaryOperationOnValues(Evaluator* ev, BinaryOp::Op op, Value lval, Value rval); 12 | 13 | ErrorOr AssignOp::evaluate_impl(Evaluator* ev) const 14 | { 15 | auto lval_result = TRY(this->lhs->evaluate(ev)); 16 | auto ltype = lval_result.get().type(); 17 | 18 | if(not lval_result.isLValue()) 19 | return ErrMsg(ev, "cannot assign to non-lvalue"); 20 | 21 | // auto rval = 22 | Value result_value {}; 23 | 24 | if(this->op != Op::None) 25 | { 26 | auto bin_op = [this]() { 27 | switch(this->op) 28 | { 29 | case Op::Add: return BinaryOp::Op::Add; 30 | case Op::Subtract: return BinaryOp::Op::Subtract; 31 | case Op::Multiply: return BinaryOp::Op::Multiply; 32 | case Op::Divide: return BinaryOp::Op::Divide; 33 | case Op::Modulo: return BinaryOp::Op::Modulo; 34 | case Op::None: assert(false && "unreachable!"); 35 | } 36 | util::unreachable(); 37 | }(); 38 | 39 | auto rval = TRY_VALUE(this->rhs->evaluate(ev)); 40 | result_value = TRY(evaluateBinaryOperationOnValues(ev, bin_op, std::move(lval_result.get()), 41 | std::move(rval))); 42 | } 43 | else 44 | { 45 | result_value = TRY_VALUE(this->rhs->evaluate(ev)); 46 | } 47 | 48 | // TODO: 'any' might need work here 49 | auto value = ev->castValue(std::move(result_value), ltype); 50 | if(value.type() != ltype) 51 | return ErrMsg(ev, "cannot assign to '{}' from incompatible type '{}'", ltype, value.type()); 52 | 53 | lval_result.get() = std::move(value); 54 | return EvalResult::ofVoid(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /source/interp/cst/block.cpp: -------------------------------------------------------------------------------- 1 | // block.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/cst.h" 6 | #include "interp/interp.h" 7 | #include "interp/eval_result.h" 8 | 9 | namespace sap::interp::cst 10 | { 11 | ErrorOr Block::evaluate_impl(Evaluator* ev) const 12 | { 13 | auto _ = ev->pushFrame(); 14 | 15 | for(auto& stmt : this->body) 16 | { 17 | auto result = TRY(stmt->evaluate(ev)); 18 | ev->frame().dropTemporaries(); 19 | 20 | if(not result.isNormal()) 21 | return OkMove(result); 22 | } 23 | 24 | return EvalResult::ofVoid(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /source/interp/cst/cast.cpp: -------------------------------------------------------------------------------- 1 | // cast.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/cst.h" 6 | #include "interp/type.h" 7 | #include "interp/value.h" 8 | #include "interp/interp.h" 9 | #include "interp/eval_result.h" 10 | 11 | namespace sap::interp::cst 12 | { 13 | ErrorOr CastExpr::evaluate_impl(Evaluator* ev) const 14 | { 15 | auto val = TRY_VALUE(this->expr->evaluate(ev)); 16 | 17 | Value result {}; 18 | switch(this->cast_kind) 19 | { 20 | using enum CastKind; 21 | 22 | case None: return ErrMsg(ev, "invalid cast!"); 23 | 24 | case Implicit: // 25 | result = ev->castValue(std::move(val), m_type); 26 | break; 27 | 28 | case Pointer: // 29 | if(m_type->isMutablePointer()) 30 | result = Value::mutablePointer(m_type->pointerElement(), val.getMutablePointer()); 31 | else 32 | result = Value::pointer(m_type->pointerElement(), val.getPointer()); 33 | break; 34 | 35 | case FloatToInteger: // 36 | result = Value::integer(static_cast(val.getFloating())); 37 | break; 38 | 39 | case CharToInteger: // 40 | result = Value::integer(static_cast(val.getChar())); 41 | break; 42 | 43 | case IntegerToChar: // 44 | result = Value::character(static_cast(val.getInteger())); 45 | break; 46 | } 47 | 48 | return EvalResult::ofValue(std::move(result)); 49 | } 50 | 51 | ErrorOr UnionVariantCastExpr::evaluate_impl(Evaluator* ev) const 52 | { 53 | auto val = TRY(this->expr->evaluate(ev)); 54 | if(auto uvi = val.get().getUnionVariantIndex(); uvi != this->variant_idx) 55 | { 56 | return ErrMsg(ev, "value was variant '{}', cannot convert to '{}'", this->union_type->getCases()[uvi].name, 57 | this->union_type->getCases()[this->variant_idx].name); 58 | } 59 | 60 | if(val.isLValue()) 61 | return EvalResult::ofLValue(val.lValuePointer()->getUnionUnderlyingStruct()); 62 | else 63 | return EvalResult::ofValue(val.take().takeUnionUnderlyingStruct()); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /source/interp/cst/deref.cpp: -------------------------------------------------------------------------------- 1 | // deref.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/cst.h" 6 | #include "interp/interp.h" 7 | 8 | namespace sap::interp::cst 9 | { 10 | ErrorOr DereferenceOp::evaluate_impl(Evaluator* ev) const 11 | { 12 | auto expr_res = TRY(this->expr->evaluate(ev)); 13 | assert(expr_res.hasValue()); 14 | 15 | auto& inside = expr_res.get(); 16 | auto type = inside.type(); 17 | 18 | if(type->isOptional()) 19 | { 20 | auto opt = inside.getOptional(); 21 | if(opt.has_value()) 22 | { 23 | if(expr_res.isLValue()) 24 | return EvalResult::ofValue((*opt)->clone()); 25 | else 26 | return EvalResult::ofValue(std::move(**opt)); 27 | } 28 | else 29 | { 30 | return ErrMsg(ev, "dereferencing empty optional"); 31 | } 32 | } 33 | else 34 | { 35 | auto ptr = inside.getPointer(); 36 | if(ptr == nullptr) 37 | return ErrMsg(ev, "dereferencing null pointer"); 38 | 39 | return EvalResult::ofLValue(const_cast(*ptr)); 40 | } 41 | } 42 | 43 | 44 | 45 | 46 | 47 | ErrorOr AddressOfOp::evaluate_impl(Evaluator* ev) const 48 | { 49 | auto expr_res = TRY(this->expr->evaluate(ev)); 50 | assert(expr_res.hasValue()); 51 | assert(expr_res.isLValue()); 52 | assert(m_type->isPointer()); 53 | 54 | auto& inside = expr_res.get(); 55 | // if(auto inside_ty = inside.type(); 56 | // inside_ty->isTreeBlockObj() || inside_ty->isTreeInlineObj() || inside_ty->isLayoutObject()) 57 | // { 58 | // return EvalResult::ofVoid(); 59 | // } 60 | // else 61 | { 62 | Value ret {}; 63 | if(this->is_mutable) 64 | ret = Value::mutablePointer(m_type->pointerElement(), &inside); 65 | else 66 | ret = Value::pointer(m_type->pointerElement(), &inside); 67 | 68 | return EvalResult::ofValue(std::move(ret)); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /source/interp/cst/dotop.cpp: -------------------------------------------------------------------------------- 1 | // dotop.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/cst.h" 6 | #include "interp/interp.h" 7 | 8 | namespace sap::interp::cst 9 | { 10 | ErrorOr DotOp::evaluate_impl(Evaluator* ev) const 11 | { 12 | assert(this->struct_type->hasFieldNamed(this->field_name)); 13 | auto field_idx = this->struct_type->getFieldIndex(this->field_name); 14 | auto field_type = this->struct_type->getFieldAtIndex(field_idx); 15 | 16 | auto lhs_res = TRY(this->expr->evaluate(ev)); 17 | if(not lhs_res.hasValue()) 18 | sap::internal_error("unexpected void value"); 19 | 20 | auto& lhs_value = lhs_res.get(); 21 | auto ltype = lhs_value.type(); 22 | if(this->is_optional) 23 | { 24 | bool left_has_value = (lhs_value.isOptional() && lhs_value.haveOptionalValue()) 25 | || (lhs_value.type()->isPointer() && lhs_value.getPointer() != nullptr); 26 | 27 | if(lhs_value.type()->isPointer()) 28 | { 29 | Value* result = nullptr; 30 | if(left_has_value) 31 | result = &lhs_value.getStructField(field_idx); 32 | 33 | if(ltype->isMutablePointer()) 34 | return EvalResult::ofLValue(*result); 35 | else 36 | return EvalResult::ofValue(Value::pointer(field_type, result)); 37 | } 38 | else 39 | { 40 | if(left_has_value) 41 | { 42 | auto fields = std::move(lhs_value).takeOptional().value().takeStructFields(); 43 | return EvalResult::ofValue(Value::optional(field_type, std::move(fields[field_idx]))); 44 | } 45 | else 46 | { 47 | return EvalResult::ofValue(Value::optional(field_type, std::nullopt)); 48 | } 49 | } 50 | } 51 | else 52 | { 53 | if(lhs_res.isLValue()) 54 | return EvalResult::ofLValue(lhs_value.getStructField(field_idx)); 55 | else 56 | return EvalResult::ofValue(lhs_value.getStructField(field_idx).clone()); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /source/interp/cst/enum.cpp: -------------------------------------------------------------------------------- 1 | // enum.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/cst.h" 6 | #include "interp/interp.h" 7 | 8 | namespace sap::interp::cst 9 | { 10 | ErrorOr EnumeratorDefn::evaluate_impl(Evaluator* ev) const 11 | { 12 | auto et = this->declaration->type->toEnum(); 13 | if(this->value == nullptr) 14 | { 15 | assert(et->elementType()->isInteger()); 16 | 17 | int64_t prev_value = 0; 18 | if(this->prev_sibling != nullptr) 19 | prev_value = ev->getGlobalValue(this->prev_sibling)->getEnumerator().getInteger(); 20 | 21 | ev->setGlobalValue(this, Value::enumerator(et, Value::integer(prev_value))); 22 | } 23 | else 24 | { 25 | auto tmp = TRY_VALUE(this->value->evaluate(ev)); 26 | auto val = Value::enumerator(et, ev->castValue(std::move(tmp), et->elementType())); 27 | 28 | ev->setGlobalValue(this, std::move(val)); 29 | } 30 | 31 | return EvalResult::ofVoid(); 32 | } 33 | 34 | ErrorOr EnumDefn::evaluate_impl(Evaluator* ev) const 35 | { 36 | // this does nothing (all the work is done by the enumerators) 37 | for(auto& e : this->enumerators) 38 | TRY(e->evaluate(ev)); 39 | 40 | return EvalResult::ofVoid(); 41 | } 42 | 43 | ErrorOr EnumeratorExpr::evaluate_impl(Evaluator* ev) const 44 | { 45 | auto v = ev->getGlobalValue(this->enumerator); 46 | assert(v != nullptr); 47 | 48 | return EvalResult::ofLValue(*v); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /source/interp/cst/for.cpp: -------------------------------------------------------------------------------- 1 | // for.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/cst.h" 6 | #include "interp/interp.h" 7 | 8 | namespace sap::interp::cst 9 | { 10 | ErrorOr ForLoop::evaluate_impl(Evaluator* ev) const 11 | { 12 | auto _ = ev->pushFrame(); 13 | if(this->init != nullptr) 14 | TRY(this->init->evaluate(ev)); 15 | 16 | bool loop_cond = true; 17 | while(loop_cond) 18 | { 19 | if(this->cond != nullptr) 20 | { 21 | auto cond_val = TRY_VALUE(this->cond->evaluate(ev)); 22 | loop_cond = ev->castValue(std::move(cond_val), Type::makeBool()).getBool(); 23 | if(not loop_cond) 24 | break; 25 | } 26 | 27 | auto val = TRY(this->body->evaluate(ev)); 28 | if(not val.isNormal()) 29 | { 30 | if(val.isReturn()) 31 | return OkMove(val); 32 | else if(val.isLoopBreak()) 33 | break; 34 | else if(val.isLoopContinue()) 35 | (void) false; // do nothing 36 | } 37 | 38 | if(this->update != nullptr) 39 | TRY(this->update->evaluate(ev)); 40 | } 41 | 42 | return EvalResult::ofVoid(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /source/interp/cst/fstring.cpp: -------------------------------------------------------------------------------- 1 | // fstring.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/cst.h" 6 | #include "interp/interp.h" 7 | 8 | namespace sap::interp::cst 9 | { 10 | ErrorOr FStringExpr::evaluate_impl(Evaluator* ev) const 11 | { 12 | std::u32string str {}; 13 | 14 | for(auto& part : this->parts) 15 | { 16 | if(auto expr = part.maybe_right(); expr != nullptr) 17 | str += TRY_VALUE((*expr)->evaluate(ev)).getUtf32String(); 18 | else 19 | str += part.left(); 20 | } 21 | 22 | return EvalResult::ofValue(Value::string(std::move(str))); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /source/interp/cst/func.cpp: -------------------------------------------------------------------------------- 1 | // func.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/cst.h" // for FunctionDefn, VariableDefn, Function... 6 | #include "interp/type.h" // for Type, FunctionType 7 | #include "interp/value.h" // for Value 8 | #include "interp/interp.h" // for Interpreter, DefnTree, StackFrame 9 | #include "interp/eval_result.h" // for EvalResult 10 | 11 | namespace sap::interp::cst 12 | { 13 | ErrorOr FunctionDefn::call(Evaluator* ev, std::vector& args) const 14 | { 15 | auto _ = ev->pushFrame(); 16 | auto& frame = ev->frame(); 17 | 18 | if(args.size() != this->params.size()) 19 | return ErrMsg(ev, "function call arity mismatch"); 20 | 21 | for(size_t i = 0; i < args.size(); i++) 22 | frame.setValue(this->params[i].defn.get(), std::move(args[i])); 23 | 24 | return this->body->evaluate(ev); 25 | } 26 | 27 | // evaluating these don't do anything 28 | ErrorOr FunctionDefn::evaluate_impl(Evaluator* ev) const 29 | { 30 | return EvalResult::ofVoid(); 31 | } 32 | 33 | ErrorOr BuiltinFunctionDefn::evaluate_impl(Evaluator* ev) const 34 | { 35 | return EvalResult::ofVoid(); 36 | } 37 | 38 | ErrorOr PartiallyResolvedOverloadSet::evaluate_impl(Evaluator* ev) const 39 | { 40 | if(this->items.empty()) 41 | return ErrMsg(this->loc(), "reference to overload set '{}' resulted in an empty set", this->name); 42 | 43 | auto e = ErrorMessage(this->loc(), zpr::sprint("ambiguous reference to '{}' in overload set", this->name)); 44 | for(auto& item : this->items) 45 | e.addInfo(item.decl->location, zpr::sprint("possible candidate:")); 46 | 47 | return Err(std::move(e)); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /source/interp/cst/hook.cpp: -------------------------------------------------------------------------------- 1 | // hook.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/cst.h" 6 | #include "interp/interp.h" 7 | 8 | namespace sap::interp::cst 9 | { 10 | ErrorOr HookBlock::evaluate_impl(Evaluator* ev) const 11 | { 12 | if(ev->interpreter()->currentPhase() != this->phase) 13 | return EvalResult::ofVoid(); 14 | 15 | return this->body->evaluate(ev); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /source/interp/cst/ident.cpp: -------------------------------------------------------------------------------- 1 | // ident.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/cst.h" // for Ident, Declaration 6 | #include "interp/type.h" // for Type 7 | #include "interp/interp.h" // for Interpreter, StackFrame, DefnTree 8 | #include "interp/eval_result.h" // for EvalResult 9 | 10 | namespace sap::interp::cst 11 | { 12 | ErrorOr Ident::evaluate_impl(Evaluator* ev) const 13 | { 14 | // this should have been set by typechecking! 15 | assert(this->resolved_decl != nullptr); 16 | 17 | auto* defn = this->resolved_decl->definition(); 18 | if(defn->declaration->function_decl.has_value()) 19 | { 20 | if(not defn->declaration->type->isFunction()) 21 | return ErrMsg(ev, "expected function type, found '{}'", defn->declaration->type); 22 | 23 | return EvalResult::ofValue(Value::function(defn->declaration->type->toFunction(), 24 | [defn](Interpreter* cs, std::vector& args) -> std::optional { // 25 | auto ret = cs->evaluator().call(defn, args); 26 | if(ret.is_err()) 27 | sap::internal_error("failed to evaluate function"); 28 | 29 | if(ret->hasValue()) 30 | return ret.unwrap().take(); 31 | else 32 | return std::nullopt; 33 | })); 34 | } 35 | 36 | auto* frame = &ev->frame(); 37 | while(frame != nullptr) 38 | { 39 | if(auto value = frame->valueOf(defn); value != nullptr) 40 | return EvalResult::ofLValue(*value); 41 | 42 | frame = frame->parent(); 43 | } 44 | 45 | // we didn't find any locals -- check globals. 46 | if(auto value = ev->getGlobalValue(defn); value != nullptr) 47 | return EvalResult::ofLValue(*value); 48 | 49 | return ErrMsg(ev, "use of uninitialised variable '{}'", this->name); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /source/interp/cst/if.cpp: -------------------------------------------------------------------------------- 1 | // if.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/cst.h" 6 | #include "interp/interp.h" 7 | #include "interp/eval_result.h" 8 | 9 | namespace sap::interp::cst 10 | { 11 | ErrorOr IfStmt::evaluate_impl(Evaluator* ev) const 12 | { 13 | auto cond = TRY_VALUE(this->if_cond->evaluate(ev)); 14 | cond = ev->castValue(std::move(cond), Type::makeBool()); 15 | 16 | if(cond.getBool()) 17 | { 18 | if(auto ret = TRY(this->if_body->evaluate(ev)); not ret.isNormal()) 19 | return OkMove(ret); 20 | } 21 | else if(this->else_body != nullptr) 22 | { 23 | if(auto ret = TRY(this->else_body->evaluate(ev)); not ret.isNormal()) 24 | return OkMove(ret); 25 | } 26 | 27 | return EvalResult::ofVoid(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /source/interp/cst/if_let.cpp: -------------------------------------------------------------------------------- 1 | // if_let.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/cst.h" 6 | #include "interp/interp.h" 7 | #include "interp/eval_result.h" 8 | 9 | namespace sap::interp::cst 10 | { 11 | ErrorOr IfLetOptionalStmt::evaluate_impl(Evaluator* ev) const 12 | { 13 | auto f = ev->pushFrame(); 14 | auto value = TRY_VALUE(this->expr->evaluate(ev)); 15 | assert(value.type()->isOptional()); 16 | 17 | if(value.haveOptionalValue()) 18 | { 19 | TRY(this->defn->evaluate_impl(ev)); 20 | ev->frame().setValue(this->defn.get(), std::move(value).takeOptional().value()); 21 | 22 | if(auto ret = TRY(this->true_case->evaluate(ev)); not ret.isNormal()) 23 | return OkMove(ret); 24 | } 25 | else if(this->else_case != nullptr) 26 | { 27 | if(auto ret = TRY(this->else_case->evaluate(ev)); not ret.isNormal()) 28 | return OkMove(ret); 29 | } 30 | 31 | return EvalResult::ofVoid(); 32 | } 33 | 34 | 35 | ErrorOr IfLetUnionStmt::evaluate_impl(Evaluator* ev) const 36 | { 37 | auto f = ev->pushFrame(); 38 | auto value = TRY_VALUE(this->expr->evaluate(ev)); 39 | 40 | assert(not this->rhs_defn->is_global); 41 | TRY(this->rhs_defn->evaluate(ev)); 42 | 43 | if(value.getUnionVariantIndex() == this->variant_index) 44 | { 45 | auto def = this->rhs_defn.get(); 46 | 47 | auto& variant = value.getUnionUnderlyingStruct(); 48 | if(def->is_mutable) 49 | ev->frame().setValue(def, Value::mutablePointer(variant.type(), &variant)); 50 | else 51 | ev->frame().setValue(def, Value::pointer(variant.type(), &variant)); 52 | 53 | for(auto& d : this->binding_defns) 54 | TRY(d->evaluate(ev)); 55 | 56 | if(auto ret = TRY(this->true_case->evaluate(ev)); not ret.isNormal()) 57 | return OkMove(ret); 58 | } 59 | else if(this->else_case != nullptr) 60 | { 61 | if(auto ret = TRY(this->else_case->evaluate(ev)); not ret.isNormal()) 62 | return OkMove(ret); 63 | } 64 | 65 | return EvalResult::ofVoid(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /source/interp/cst/import.cpp: -------------------------------------------------------------------------------- 1 | // import.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/cst.h" 6 | #include "interp/interp.h" 7 | 8 | namespace sap::interp::cst 9 | { 10 | ErrorOr ImportStmt::evaluate_impl(Evaluator* ev) const 11 | { 12 | if(this->block != nullptr) 13 | TRY(this->block->evaluate(ev)); 14 | 15 | return EvalResult::ofVoid(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /source/interp/cst/literal.cpp: -------------------------------------------------------------------------------- 1 | // literal.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/cst.h" 6 | #include "interp/type.h" 7 | #include "interp/value.h" 8 | #include "interp/interp.h" 9 | #include "interp/eval_result.h" 10 | 11 | namespace sap::interp::cst 12 | { 13 | ErrorOr ArrayLit::evaluate_impl(Evaluator* ev) const 14 | { 15 | std::vector values {}; 16 | for(auto& elm : this->elements) 17 | values.push_back(TRY_VALUE(elm->evaluate(ev))); 18 | 19 | return EvalResult::ofValue(Value::array(m_type->toArray()->elementType(), std::move(values))); 20 | } 21 | 22 | ErrorOr LengthExpr::evaluate_impl(Evaluator* ev) const 23 | { 24 | return EvalResult::ofValue(Value::length(this->length)); 25 | } 26 | 27 | ErrorOr BooleanLit::evaluate_impl(Evaluator* ev) const 28 | { 29 | return EvalResult::ofValue(Value::boolean(this->value)); 30 | } 31 | 32 | ErrorOr NumberLit::evaluate_impl(Evaluator* ev) const 33 | { 34 | if(this->is_floating) 35 | return EvalResult::ofValue(Value::floating(float_value)); 36 | else 37 | return EvalResult::ofValue(Value::integer(int_value)); 38 | } 39 | 40 | ErrorOr StringLit::evaluate_impl(Evaluator* ev) const 41 | { 42 | return EvalResult::ofValue(Value::string(this->string)); 43 | } 44 | 45 | ErrorOr CharLit::evaluate_impl(Evaluator* ev) const 46 | { 47 | return EvalResult::ofValue(Value::character(this->character)); 48 | } 49 | 50 | ErrorOr NullLit::evaluate_impl(Evaluator* ev) const 51 | { 52 | return EvalResult::ofValue(Value::nullPointer()); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /source/interp/cst/logical_op.cpp: -------------------------------------------------------------------------------- 1 | // logical_op.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "util.h" // for is_one_of 6 | 7 | #include "interp/cst.h" // for BinaryOp::Op, BinaryOp, Expr, Binary... 8 | #include "interp/type.h" // for Type, ArrayType 9 | #include "interp/value.h" // for Value 10 | #include "interp/interp.h" // for Interpreter 11 | #include "interp/eval_result.h" // for EvalResult, TRY_VALUE 12 | 13 | namespace sap::interp::cst 14 | { 15 | ErrorOr LogicalBinOp::evaluate_impl(Evaluator* ev) const 16 | { 17 | auto lval = TRY_VALUE(this->lhs->evaluate(ev)); 18 | if(not lval.isBool()) 19 | return ErrMsg(ev, "expected bool"); 20 | 21 | if(this->op == Op::And && lval.getBool() == false) 22 | return EvalResult::ofValue(Value::boolean(false)); 23 | 24 | else if(this->op == Op::Or && lval.getBool() == true) 25 | return EvalResult::ofValue(Value::boolean(true)); 26 | 27 | auto rval = TRY_VALUE(this->rhs->evaluate(ev)); 28 | if(not rval.isBool()) 29 | return ErrMsg(ev, "expected bool"); 30 | 31 | return EvalResult::ofValue(std::move(rval)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /source/interp/cst/misc.cpp: -------------------------------------------------------------------------------- 1 | // misc.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/cst.h" 6 | #include "interp/interp.h" 7 | 8 | namespace sap::interp::cst 9 | { 10 | ErrorOr ArraySpreadOp::evaluate_impl(Evaluator* ev) const 11 | { 12 | auto arr = TRY_VALUE(this->expr->evaluate(ev)).takeArray(); 13 | return EvalResult::ofValue(Value::array(m_type->arrayElement(), std::move(arr), 14 | /* variadic: */ true)); 15 | } 16 | 17 | ErrorOr VariadicPackExpr::evaluate_impl(Evaluator* ev) const 18 | { 19 | auto elm_type = m_type->arrayElement(); 20 | 21 | std::vector values {}; 22 | for(auto& e : this->exprs) 23 | { 24 | auto val = TRY_VALUE(e->evaluate(ev)); 25 | 26 | // need to flatten, because otherwise ...[a] will end up as [[a]]. 27 | if(val.type()->isVariadicArray()) 28 | { 29 | auto arr = std::move(val).takeArray(); 30 | for(auto& v : arr) 31 | values.push_back(ev->castValue(std::move(v), elm_type)); 32 | } 33 | else 34 | { 35 | values.push_back(ev->castValue(std::move(val), elm_type)); 36 | } 37 | } 38 | 39 | return EvalResult::ofValue(Value::array(elm_type, std::move(values), 40 | /* variadic: */ true)); 41 | } 42 | 43 | 44 | ErrorOr TypeExpr::evaluate_impl(Evaluator* ev) const 45 | { 46 | return ErrMsg(ev, "oh no"); 47 | } 48 | 49 | ErrorOr GenericPlaceholderDefn::evaluate_impl(Evaluator* ev) const 50 | { 51 | return EvalResult::ofVoid(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /source/interp/cst/move.cpp: -------------------------------------------------------------------------------- 1 | // move.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/cst.h" 6 | #include "interp/interp.h" 7 | 8 | namespace sap::interp::cst 9 | { 10 | ErrorOr MoveExpr::evaluate_impl(Evaluator* ev) const 11 | { 12 | auto inside = TRY(this->expr->evaluate(ev)); 13 | if(not inside.isLValue()) 14 | return ErrMsg(ev, "expected lvalue"); 15 | 16 | return EvalResult::ofValue(inside.move()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /source/interp/cst/optional.cpp: -------------------------------------------------------------------------------- 1 | // optional.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/cst.h" 6 | #include "interp/interp.h" 7 | 8 | namespace sap::interp::cst 9 | { 10 | ErrorOr NullCoalesceOp::evaluate_impl(Evaluator* ev) const 11 | { 12 | // short circuit -- don't evaluate rhs eagerly 13 | auto lval = TRY_VALUE(this->lhs->evaluate(ev)); 14 | auto ltype = lval.type(); 15 | 16 | assert(lval.isOptional() || lval.isNull() || lval.type()->isPointer()); 17 | bool left_has_value = 18 | not lval.isNull() 19 | && ((lval.isOptional() && lval.haveOptionalValue()) // 20 | || (lval.isPointer() && lval.getPointer() != nullptr)); 21 | 22 | if(left_has_value) 23 | { 24 | // if it's the same, then just return it. this means that both the left and right 25 | // sides were either pointers or optionals. 26 | if(this->kind == Kind::Flatmap) 27 | return EvalResult::ofValue(ev->castValue(std::move(lval), m_type)); 28 | 29 | // otherwise, the right type is the "element" of the left type 30 | if(ltype->isOptional()) 31 | { 32 | auto ret = ev->castValue(std::move(lval).takeOptional().value(), m_type); 33 | return EvalResult::ofValue(std::move(ret)); 34 | } 35 | else 36 | { 37 | auto ret = ev->castValue(lval.getPointer()->clone(), m_type); 38 | return EvalResult::ofValue(std::move(ret)); 39 | } 40 | } 41 | else 42 | { 43 | // just evaluate the right-hand side. 44 | return EvalResult::ofValue(TRY_VALUE(this->rhs->evaluate(ev))); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /source/interp/cst/return.cpp: -------------------------------------------------------------------------------- 1 | // return.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/cst.h" 6 | #include "interp/interp.h" 7 | 8 | namespace sap::interp::cst 9 | { 10 | ErrorOr ReturnStmt::evaluate_impl(Evaluator* ev) const 11 | { 12 | if(this->expr == nullptr) 13 | return EvalResult::ofReturnVoid(); 14 | 15 | auto value = TRY(this->expr->evaluate(ev)); 16 | Value ret {}; 17 | 18 | if(value.isLValue()) 19 | { 20 | // check whether the thing exists in the current call frame 21 | auto cur_call_depth = ev->frame().callDepth(); 22 | auto frame = &ev->frame(); 23 | 24 | bool found = false; 25 | while(frame != nullptr && frame->callDepth() == cur_call_depth) 26 | { 27 | if(frame->containsValue(*value.lValuePointer())) 28 | { 29 | ret = ev->castValue(value.move(), this->type); 30 | found = true; 31 | break; 32 | } 33 | 34 | frame = frame->parent(); 35 | } 36 | 37 | // if we didn't find it, then it's a global of some kind; 38 | // don't try to move it out, just return it. 39 | if(not found) 40 | ret = value.take(); 41 | } 42 | else 43 | { 44 | ret = ev->castValue(value.take(), this->type); 45 | } 46 | 47 | return EvalResult::ofReturnValue(std::move(ret)); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /source/interp/cst/struct.cpp: -------------------------------------------------------------------------------- 1 | // struct.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/cst.h" 6 | #include "interp/interp.h" 7 | #include "interp/overload_resolution.h" 8 | 9 | namespace sap::interp::cst 10 | { 11 | ErrorOr StructDefn::evaluate_impl(Evaluator* ev) const 12 | { 13 | // do nothing 14 | return EvalResult::ofVoid(); 15 | } 16 | 17 | ErrorOr StructLit::evaluate_impl(Evaluator* ev) const 18 | { 19 | assert(m_type->isStruct()); 20 | auto struct_type = m_type->toStruct(); 21 | 22 | std::vector field_values {}; 23 | for(size_t i = 0; i < this->field_inits.size(); i++) 24 | { 25 | auto& f = this->field_inits[i]; 26 | auto ft = struct_type->getFieldTypes()[i]; 27 | 28 | auto v = TRY_VALUE(f->evaluate(ev)); 29 | field_values.push_back(ev->castValue(std::move(v), ft)); 30 | } 31 | 32 | return EvalResult::ofValue(Value::structure(struct_type, std::move(field_values))); 33 | } 34 | 35 | ErrorOr StructUpdateOp::evaluate_impl(Evaluator* ev) const 36 | { 37 | Value* struct_ptr = nullptr; 38 | Value _struct_val; 39 | if(this->is_assignment) 40 | { 41 | struct_ptr = TRY(this->structure->evaluate(ev)).lValuePointer(); 42 | } 43 | else 44 | { 45 | _struct_val = TRY_VALUE(this->structure->evaluate(ev)); 46 | struct_ptr = &_struct_val; 47 | } 48 | 49 | auto struct_type = struct_ptr->type()->toStruct(); 50 | auto _ctx = ev->pushStructFieldContext(struct_ptr); 51 | 52 | util::hashmap new_values {}; 53 | for(auto& [name, expr] : this->updates) 54 | { 55 | auto field_idx = struct_type->getFieldIndex(name); 56 | auto field_type = struct_type->getFieldAtIndex(field_idx); 57 | 58 | if(this->is_optional && field_type->isOptional()) 59 | { 60 | if(struct_ptr->getStructField(field_idx).haveOptionalValue()) 61 | continue; 62 | } 63 | 64 | auto value = TRY_VALUE(expr->evaluate(ev)); 65 | value = ev->castValue(std::move(value), field_type); 66 | 67 | new_values.emplace(field_idx, std::move(value)); 68 | } 69 | 70 | // update them all at once. 71 | for(auto& [idx, val] : new_values) 72 | struct_ptr->getStructField(idx) = std::move(val); 73 | 74 | if(this->is_assignment) 75 | return EvalResult::ofVoid(); 76 | else 77 | return EvalResult::ofValue(std::move(_struct_val)); 78 | } 79 | 80 | 81 | ErrorOr StructContextSelf::evaluate_impl(Evaluator* ev) const 82 | { 83 | auto& ctx = ev->getStructFieldContext(); 84 | return EvalResult::ofLValue(ctx); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /source/interp/cst/subscript_op.cpp: -------------------------------------------------------------------------------- 1 | // subscript_op.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/cst.h" 6 | #include "interp/interp.h" 7 | 8 | namespace sap::interp::cst 9 | { 10 | ErrorOr SubscriptOp::evaluate_impl(Evaluator* ev) const 11 | { 12 | auto lhs_res = TRY(this->array->evaluate(ev)); 13 | auto rhs = TRY_VALUE(this->indices[0]->evaluate(ev)); 14 | 15 | auto idx = checked_cast(rhs.getInteger()); 16 | 17 | if(lhs_res.isLValue()) 18 | { 19 | auto& arr = lhs_res.lValuePointer()->getArray(); 20 | if(idx >= arr.size()) 21 | return ErrMsg(ev, "array index out of bounds (idx={}, size={})", idx, arr.size()); 22 | 23 | auto& val = arr[idx]; 24 | return EvalResult::ofLValue(*const_cast(&val)); 25 | } 26 | else 27 | { 28 | auto arr = lhs_res.take().takeArray(); 29 | if(idx >= arr.size()) 30 | return ErrMsg(ev, "array index out of bounds (idx={}, size={})", idx, arr.size()); 31 | 32 | return EvalResult::ofValue(std::move(arr[idx])); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /source/interp/cst/unary_op.cpp: -------------------------------------------------------------------------------- 1 | // unary_op.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "util.h" // for is_one_of 6 | 7 | #include "interp/cst.h" // for BinaryOp::Op, BinaryOp, Expr, Binary... 8 | #include "interp/value.h" // for Value 9 | #include "interp/interp.h" // for Interpreter 10 | #include "interp/eval_result.h" // for EvalResult, TRY_VALUE 11 | 12 | namespace sap::interp::cst 13 | { 14 | ErrorOr UnaryOp::evaluate_impl(Evaluator* ev) const 15 | { 16 | auto val = TRY_VALUE(this->expr->evaluate(ev)); 17 | switch(this->op) 18 | { 19 | case Op::Plus: { 20 | return EvalResult::ofValue(std::move(val)); 21 | } 22 | 23 | case Op::Minus: { 24 | if(val.type()->isInteger()) 25 | return EvalResult::ofValue(Value::integer(-val.getInteger())); 26 | else if(val.type()->isFloating()) 27 | return EvalResult::ofValue(Value::floating(-val.getFloating())); 28 | else if(val.type()->isLength()) 29 | return EvalResult::ofValue(Value::length(val.getLength().negate())); 30 | else 31 | assert(false); 32 | } 33 | 34 | case Op::LogicalNot: { 35 | return EvalResult::ofValue(Value::boolean(not val.getBool())); 36 | } 37 | } 38 | util::unreachable(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /source/interp/cst/union.cpp: -------------------------------------------------------------------------------- 1 | // union.cpp 2 | // Copyright (c) 2023, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/cst.h" 6 | #include "interp/interp.h" 7 | #include "interp/overload_resolution.h" 8 | 9 | namespace sap::interp::cst 10 | { 11 | ErrorOr UnionDefn::evaluate_impl(Evaluator* ev) const 12 | { 13 | // do nothing 14 | return EvalResult::ofVoid(); 15 | } 16 | 17 | ErrorOr UnionExpr::evaluate_impl(Evaluator* ev) const 18 | { 19 | auto union_type = this->type()->toUnion(); 20 | 21 | // make the case value as a struct. 22 | auto case_type = union_type->getCaseAtIndex(this->case_index); 23 | std::vector field_values {}; 24 | for(size_t i = 0; i < this->values.size(); i++) 25 | { 26 | auto& f = this->values[i]; 27 | auto ft = case_type->getFieldTypes()[i]; 28 | 29 | auto v = TRY_VALUE(f->evaluate(ev)); 30 | field_values.push_back(ev->castValue(std::move(v), ft)); 31 | } 32 | 33 | auto case_value = Value::structure(case_type, std::move(field_values)); 34 | return EvalResult::ofValue(Value::unionVariant(union_type, this->case_index, std::move(case_value))); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /source/interp/cst/variable.cpp: -------------------------------------------------------------------------------- 1 | // variable.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/cst.h" // for VariableDefn, Expr, Declaration, Var... 6 | #include "interp/type.h" // for Type 7 | #include "interp/interp.h" // for Interpreter, DefnTree, StackFrame 8 | #include "interp/eval_result.h" // for EvalResult, TRY_VALUE 9 | 10 | namespace sap::interp::cst 11 | { 12 | ErrorOr VariableDefn::evaluate_impl(Evaluator* ev) const 13 | { 14 | if(not this->initialiser.is_null()) 15 | { 16 | // if this is a global variable that was already initialised, don't re-initialise it 17 | // the implication is that for layout passes > 1, we don't reset values (we need to persist some state!) 18 | if(this->is_global && ev->getGlobalValue(this) != nullptr) 19 | return EvalResult::ofVoid(); 20 | 21 | auto value = ev->castValue(TRY_VALUE(this->initialiser->evaluate(ev)), this->declaration->type); 22 | if(this->is_global) 23 | ev->setGlobalValue(this, std::move(value)); 24 | else 25 | ev->frame().setValue(this, std::move(value)); 26 | } 27 | 28 | return EvalResult::ofVoid(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /source/interp/cst/while.cpp: -------------------------------------------------------------------------------- 1 | // while.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/cst.h" 6 | #include "interp/interp.h" 7 | 8 | namespace sap::interp::cst 9 | { 10 | ErrorOr WhileLoop::evaluate_impl(Evaluator* ev) const 11 | { 12 | bool loop_cond = false; 13 | do 14 | { 15 | auto cond = TRY_VALUE(this->condition->evaluate(ev)); 16 | loop_cond = ev->castValue(std::move(cond), Type::makeBool()).getBool(); 17 | 18 | if(not loop_cond) 19 | break; 20 | 21 | auto val = TRY(this->body->evaluate(ev)); 22 | if(not val.isNormal()) 23 | { 24 | if(val.isReturn()) 25 | return OkMove(val); 26 | else if(val.isLoopBreak()) 27 | break; 28 | else if(val.isLoopContinue()) 29 | continue; 30 | } 31 | } while(loop_cond); 32 | 33 | return EvalResult::ofVoid(); 34 | } 35 | 36 | ErrorOr BreakStmt::evaluate_impl(Evaluator* ev) const 37 | { 38 | return EvalResult::ofLoopBreak(); 39 | } 40 | 41 | ErrorOr ContinueStmt::evaluate_impl(Evaluator* ev) const 42 | { 43 | return EvalResult::ofLoopContinue(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /source/interp/destructors.cpp: -------------------------------------------------------------------------------- 1 | // destructors.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "tree/base.h" 6 | #include "interp/ast.h" // for Stmt 7 | 8 | namespace sap::interp 9 | { 10 | ast::Stmt::~Stmt() 11 | { 12 | } 13 | 14 | cst::Stmt::~Stmt() 15 | { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /source/interp/interp.h: -------------------------------------------------------------------------------- 1 | // interp.h 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #include "ast.h" // for Definition, Declaration, QualifiedId 10 | #include "util.h" // for Defer, hashmap 11 | #include "value.h" // for Value 12 | 13 | #include "sap/config.h" 14 | 15 | #include "interp/type.h" // for Type 16 | #include "interp/basedefs.h" // for InlineObject 17 | #include "interp/eval_result.h" // for EvalResult 18 | 19 | #include "interp/evaluator.h" 20 | #include "interp/typechecker.h" 21 | 22 | namespace sap::tree 23 | { 24 | struct BlockObject; 25 | struct InlineObject; 26 | } 27 | 28 | namespace sap::layout 29 | { 30 | struct Document; 31 | } 32 | 33 | namespace sap::interp 34 | { 35 | struct Interpreter 36 | { 37 | Interpreter(); 38 | 39 | zst::byte_span loadFile(zst::str_view filename); 40 | 41 | Evaluator& evaluator() { return *m_evaluator; } 42 | Typechecker& typechecker() { return *m_typechecker; } 43 | 44 | pdf::PdfFont& addLoadedFont(std::unique_ptr font); 45 | ErrorOr getLoadedFontById(int64_t font_id); 46 | 47 | // this is necessary to keep the strings around... 48 | std::u32string& keepStringAlive(zst::wstr_view str); 49 | std::string& keepStringAlive(zst::str_view str); 50 | 51 | ProcessingPhase currentPhase() const { return m_current_phase; } 52 | void setCurrentPhase(ProcessingPhase phase) { m_current_phase = phase; } 53 | 54 | void addHookBlock(const ast::HookBlock* block); 55 | void addHookBlock(ProcessingPhase phase, const cst::Block* block); 56 | 57 | ErrorOr runHooks(); 58 | 59 | void addImportedFile(std::string filename); 60 | bool wasFileImported(const std::string& filename) const; 61 | 62 | tree::BlockObject* retainBlockObject(zst::SharedPtr tbo); 63 | 64 | void addMicrotypeConfig(config::MicrotypeConfig config); 65 | std::optional getMicrotypeProtrusionFor(char32_t ch, const Style& style) const; 66 | 67 | private: 68 | std::unique_ptr m_typechecker; 69 | std::unique_ptr m_evaluator; 70 | 71 | ProcessingPhase m_current_phase; 72 | util::hashmap> m_hook_blocks; 73 | 74 | std::vector> m_file_contents; 75 | 76 | util::hashmap> m_loaded_fonts; 77 | 78 | std::vector> m_retained_tbos; 79 | 80 | util::hashset m_imported_files; 81 | 82 | std::vector m_leaked_strings; 83 | std::vector m_leaked_strings32; 84 | 85 | std::vector m_microtype_configs; 86 | }; 87 | } 88 | -------------------------------------------------------------------------------- /source/interp/overload_resolution.h: -------------------------------------------------------------------------------- 1 | // overload_resolution.h 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #include "interp/ast.h" 10 | #include "interp/type.h" 11 | #include "interp/interp.h" 12 | 13 | namespace sap::interp::ast 14 | { 15 | struct InputArg 16 | { 17 | std::optional type; 18 | std::optional name; 19 | }; 20 | 21 | struct ExpectedParam 22 | { 23 | std::string name; 24 | const Type* type; 25 | const cst::Expr* default_value; 26 | }; 27 | 28 | using ExpectedParams = std::vector; 29 | 30 | struct FinalArg 31 | { 32 | const Type* param_type; 33 | 34 | // the vector implies a variadic 35 | using ArgIdxOrDefault = Either; 36 | Either> value; 37 | }; 38 | 39 | struct ArrangedArguments 40 | { 41 | using ArgList = std::vector; 42 | 43 | ArgList arguments; 44 | int coercion_cost; 45 | }; 46 | 47 | ErrorOr arrangeCallArguments( // 48 | const Typechecker* ts, // 49 | const ExpectedParams& expected_params, // 50 | const std::vector& arguments, // 51 | const char* fn_or_struct, // 52 | const char* thing_name, // 53 | const char* thing_name2); 54 | 55 | } 56 | -------------------------------------------------------------------------------- /source/interp/polymorph.h: -------------------------------------------------------------------------------- 1 | // polymorph.h 2 | // Copyright (c) 2023, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include "interp/ast.h" 8 | #include "interp/type.h" 9 | #include "interp/interp.h" 10 | 11 | namespace sap::interp::polymorph 12 | { 13 | ErrorOr createGenericOverloadSet(Typechecker* ts, 14 | const Type* infer, 15 | const QualifiedId& name, 16 | std::vector declarations, 17 | const std::vector& explicit_args); 18 | } 19 | -------------------------------------------------------------------------------- /source/interp/types/array.cpp: -------------------------------------------------------------------------------- 1 | // arrayType.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/type.h" // for ArrayType, Type, Type::KIND_ARRAY 6 | 7 | namespace sap::interp 8 | { 9 | std::string ArrayType::str() const 10 | { 11 | if(m_is_variadic) 12 | return zpr::sprint("[{}...]", m_element_type); 13 | else 14 | return zpr::sprint("[{}]", m_element_type); 15 | } 16 | 17 | ArrayType::ArrayType(const Type* element_type, bool is_variadic) 18 | : Type(Type::KIND_ARRAY) 19 | , m_element_type(element_type) 20 | , m_is_variadic(is_variadic) 21 | { 22 | } 23 | 24 | bool ArrayType::sameAs(const Type* other) const 25 | { 26 | if(not other->isArray()) 27 | return false; 28 | 29 | auto of = other->toArray(); 30 | return m_element_type == of->m_element_type && m_is_variadic == of->m_is_variadic; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /source/interp/types/enum.cpp: -------------------------------------------------------------------------------- 1 | // enum.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/type.h" 6 | 7 | namespace sap::interp 8 | { 9 | std::string EnumType::str() const 10 | { 11 | return zpr::sprint("enum({}: {})", m_name.name, m_element_type); 12 | } 13 | 14 | bool EnumType::sameAs(const Type* other) const 15 | { 16 | if(not other->isEnum()) 17 | return false; 18 | 19 | auto of = other->toEnum(); 20 | return m_element_type == of->m_element_type && m_name == of->m_name; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /source/interp/types/function.cpp: -------------------------------------------------------------------------------- 1 | // function.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/type.h" // for FunctionType, Type, Type::KIND_FUNCTION 6 | 7 | namespace sap::interp 8 | { 9 | std::string FunctionType::str() const 10 | { 11 | std::string args {}; 12 | for(size_t i = 0; i < m_params.size(); i++) 13 | { 14 | if(i != 0) 15 | args += ", "; 16 | args += m_params[i]->str(); 17 | } 18 | return zpr::sprint("fn({}) -> {}", args, m_return_type); 19 | } 20 | 21 | FunctionType::FunctionType(std::vector params, const Type* return_type) 22 | : Type(Type::KIND_FUNCTION) 23 | , m_params(std::move(params)) 24 | , m_return_type(return_type) 25 | { 26 | } 27 | 28 | bool FunctionType::sameAs(const Type* other) const 29 | { 30 | if(not other->isFunction()) 31 | return false; 32 | 33 | auto of = other->toFunction(); 34 | return m_params == of->m_params && m_return_type == of->m_return_type; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /source/interp/types/optional.cpp: -------------------------------------------------------------------------------- 1 | // optional.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/type.h" 6 | 7 | namespace sap::interp 8 | { 9 | std::string OptionalType::str() const 10 | { 11 | return zpr::sprint("?{}", m_element_type); 12 | } 13 | 14 | bool OptionalType::sameAs(const Type* other) const 15 | { 16 | if(not other->isOptional()) 17 | return false; 18 | 19 | auto of = other->toOptional(); 20 | return m_element_type == of->m_element_type; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /source/interp/types/pointer.cpp: -------------------------------------------------------------------------------- 1 | // pointer.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/type.h" 6 | 7 | namespace sap::interp 8 | { 9 | std::string PointerType::str() const 10 | { 11 | if(m_is_mutable) 12 | return zpr::sprint("&mut {}", m_element_type); 13 | else 14 | return zpr::sprint("&{}", m_element_type); 15 | } 16 | 17 | bool PointerType::sameAs(const Type* other) const 18 | { 19 | if(not other->isPointer()) 20 | return false; 21 | 22 | auto of = other->toPointer(); 23 | return m_element_type == of->m_element_type && m_is_mutable == of->m_is_mutable; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /source/interp/types/struct.cpp: -------------------------------------------------------------------------------- 1 | // struct.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/type.h" 6 | 7 | namespace sap::interp 8 | { 9 | StructType::StructType(QualifiedId name, std::vector fields) 10 | : Type(KIND_STRUCT), m_name(std::move(name)), m_fields(std::move(fields)) 11 | { 12 | size_t i = 0; 13 | for(auto& [n, t] : m_fields) 14 | m_field_map[n] = { i++, t }; 15 | } 16 | 17 | void StructType::setFields(std::vector fields) 18 | { 19 | m_fields = std::move(fields); 20 | 21 | size_t i = 0; 22 | for(auto& [n, t] : m_fields) 23 | m_field_map[n] = { i++, t }; 24 | } 25 | 26 | std::string StructType::str() const 27 | { 28 | return zpr::sprint("struct({})", m_name.name); 29 | } 30 | 31 | bool StructType::sameAs(const Type* other) const 32 | { 33 | return other->isStruct() && other->toStruct()->name() == m_name; 34 | } 35 | 36 | bool StructType::hasFieldNamed(zst::str_view name) const 37 | { 38 | return m_field_map.contains(name); 39 | } 40 | 41 | size_t StructType::getFieldIndex(zst::str_view name) const 42 | { 43 | auto it = m_field_map.find(name); 44 | assert(it != m_field_map.end()); 45 | 46 | return it->second.first; 47 | } 48 | 49 | const Type* StructType::getFieldNamed(zst::str_view name) const 50 | { 51 | auto it = m_field_map.find(name); 52 | assert(it != m_field_map.end()); 53 | 54 | return it->second.second; 55 | } 56 | 57 | const Type* StructType::getFieldAtIndex(size_t idx) const 58 | { 59 | assert(idx < m_fields.size()); 60 | return m_fields[idx].type; 61 | } 62 | 63 | 64 | auto StructType::getFields() const -> const std::vector& 65 | { 66 | return m_fields; 67 | } 68 | 69 | std::vector StructType::getFieldTypes() const 70 | { 71 | std::vector types {}; 72 | for(auto& [n, t] : m_fields) 73 | types.push_back(t); 74 | 75 | return types; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /source/interp/types/union.cpp: -------------------------------------------------------------------------------- 1 | // union.cpp 2 | // Copyright (c) 2023, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "interp/type.h" 6 | 7 | namespace sap::interp 8 | { 9 | UnionType::UnionType(QualifiedId name, std::vector cases) 10 | : Type(KIND_UNION), m_name(std::move(name)), m_cases(std::move(cases)) 11 | { 12 | size_t i = 0; 13 | for(auto& [n, t] : m_cases) 14 | m_case_map[n] = { i++, t }; 15 | } 16 | 17 | void UnionType::setCases(std::vector cases) 18 | { 19 | m_cases = std::move(cases); 20 | 21 | size_t i = 0; 22 | for(auto& [n, t] : m_cases) 23 | m_case_map[n] = { i++, t }; 24 | } 25 | 26 | std::string UnionType::str() const 27 | { 28 | return zpr::sprint("union({})", m_name.name); 29 | } 30 | 31 | bool UnionType::sameAs(const Type* other) const 32 | { 33 | return other->isUnion() && other->toUnion()->name() == m_name; 34 | } 35 | 36 | bool UnionType::hasCaseNamed(zst::str_view name) const 37 | { 38 | return m_case_map.contains(name); 39 | } 40 | 41 | size_t UnionType::getCaseIndex(zst::str_view name) const 42 | { 43 | auto it = m_case_map.find(name); 44 | assert(it != m_case_map.end()); 45 | 46 | return it->second.first; 47 | } 48 | 49 | const StructType* UnionType::getCaseNamed(zst::str_view name) const 50 | { 51 | auto it = m_case_map.find(name); 52 | assert(it != m_case_map.end()); 53 | 54 | return it->second.second; 55 | } 56 | 57 | const StructType* UnionType::getCaseAtIndex(size_t idx) const 58 | { 59 | assert(idx < m_cases.size()); 60 | return m_cases[idx].type; 61 | } 62 | 63 | 64 | auto UnionType::getCases() const -> const std::vector& 65 | { 66 | return m_cases; 67 | } 68 | 69 | std::vector UnionType::getCaseTypes() const 70 | { 71 | std::vector types {}; 72 | for(auto& [n, t] : m_cases) 73 | types.push_back(t); 74 | 75 | return types; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /source/layout/container.h: -------------------------------------------------------------------------------- 1 | // container.h 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include "sap/path.h" 8 | 9 | #include "layout/path.h" 10 | #include "layout/layout_object.h" 11 | 12 | namespace sap::layout 13 | { 14 | struct LayoutBase; 15 | 16 | struct Container : LayoutObject 17 | { 18 | enum class Direction 19 | { 20 | None, 21 | Vertical, 22 | Horizontal, 23 | }; 24 | 25 | explicit Container(const Style& style, 26 | LayoutSize size, 27 | Direction direction, 28 | bool glue_objects, 29 | BorderStyle border_style, 30 | std::vector> objs, 31 | std::optional override_obj_spacing = std::nullopt); 32 | 33 | void addObject(std::unique_ptr obj); 34 | std::vector>& objects(); 35 | const std::vector>& objects() const; 36 | 37 | virtual bool requires_space_reservation() const override; 38 | virtual layout::PageCursor compute_position_impl(layout::PageCursor cursor) override; 39 | virtual void render_impl(const LayoutBase* layout, std::vector& pages) const override; 40 | 41 | private: 42 | bool m_glued; 43 | Direction m_direction; 44 | BorderStyle m_border_style; 45 | std::vector> m_objects; 46 | std::optional m_override_obj_spacing {}; 47 | 48 | std::vector m_border_objects {}; 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /source/layout/cursor.h: -------------------------------------------------------------------------------- 1 | // cursor.h 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include "sap/units.h" 8 | 9 | namespace sap::layout 10 | { 11 | struct LayoutBase; 12 | 13 | struct RelativePos 14 | { 15 | struct TAG_RELATIVE; 16 | using Pos = dim::Vector2; 17 | 18 | Pos pos; 19 | size_t page_num; 20 | }; 21 | 22 | struct AbsolutePagePos 23 | { 24 | Position pos; 25 | size_t page_num; 26 | }; 27 | 28 | 29 | struct CursorPayload 30 | { 31 | uint8_t payload[48]; 32 | }; 33 | 34 | struct PageCursor 35 | { 36 | PageCursor(LayoutBase* layout, CursorPayload payload); 37 | ~PageCursor(); 38 | 39 | PageCursor(const PageCursor& other); 40 | PageCursor& operator=(const PageCursor& other); 41 | 42 | PageCursor(PageCursor&& other); 43 | PageCursor& operator=(PageCursor&& other); 44 | 45 | Length widthAtCursor() const; 46 | Length verticalSpaceAtCursor() const; 47 | [[nodiscard]] PageCursor moveRight(Length shift) const; 48 | [[nodiscard]] PageCursor moveDown(Length shift) const; 49 | [[nodiscard]] PageCursor newPage() const; 50 | [[nodiscard]] PageCursor newLine(Length line_height, bool* made_new_page = nullptr) const; 51 | [[nodiscard]] PageCursor ensureVerticalSpace(Length line_height, bool* made_new_page = nullptr) const; 52 | [[nodiscard]] PageCursor carriageReturn() const; 53 | [[nodiscard]] PageCursor limitWidth(Length width) const; 54 | 55 | RelativePos position() const; 56 | LayoutBase* layout() const { return m_layout; } 57 | 58 | PageCursor moveToPosition(RelativePos pos) const; 59 | 60 | private: 61 | LayoutBase* m_layout; 62 | CursorPayload m_payload; 63 | }; 64 | } 65 | -------------------------------------------------------------------------------- /source/layout/image.cpp: -------------------------------------------------------------------------------- 1 | // image.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "pdf/page.h" 6 | #include "pdf/xobject.h" 7 | 8 | #include "tree/image.h" 9 | 10 | #include "layout/image.h" 11 | 12 | namespace sap::layout 13 | { 14 | Image::Image(const Style& style, LayoutSize size, ImageBitmap image) 15 | : LayoutObject(style, size), m_image(std::move(image)) 16 | { 17 | } 18 | 19 | bool Image::requires_space_reservation() const 20 | { 21 | return true; 22 | } 23 | 24 | layout::PageCursor Image::compute_position_impl(layout::PageCursor cursor) 25 | { 26 | this->positionRelatively(cursor.position()); 27 | return cursor.moveRight(m_layout_size.width).newLine(m_layout_size.descent); 28 | } 29 | 30 | void Image::render_impl(const LayoutBase* layout, std::vector& pages) const 31 | { 32 | if(not sap::isDraftMode()) 33 | { 34 | auto pos = this->resolveAbsPosition(layout); 35 | auto page = pages[pos.page_num]; 36 | 37 | auto pdf_size = pdf::Size2d(m_layout_size.width.into(), m_layout_size.total_height().into()); 38 | 39 | auto page_obj = util::make( // 40 | m_image, // 41 | pdf_size, // 42 | page->convertVector2(pos.pos.into())); 43 | 44 | page->addObject(page_obj); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /source/layout/image.h: -------------------------------------------------------------------------------- 1 | // image.h 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include "layout/base.h" 8 | 9 | namespace sap 10 | { 11 | struct Style; 12 | namespace tree 13 | { 14 | struct Image; 15 | struct DocumentObject; 16 | } 17 | } 18 | 19 | namespace sap::layout 20 | { 21 | struct Image : LayoutObject 22 | { 23 | using LayoutObject::LayoutObject; 24 | 25 | virtual bool requires_space_reservation() const override; 26 | virtual layout::PageCursor compute_position_impl(layout::PageCursor cursor) override; 27 | virtual void render_impl(const LayoutBase* layout, std::vector& pages) const override; 28 | 29 | private: 30 | friend struct tree::Image; 31 | 32 | explicit Image(const Style& style, LayoutSize size, ImageBitmap m_image); 33 | ImageBitmap m_image; 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /source/layout/line.h: -------------------------------------------------------------------------------- 1 | // line.h 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #include "layout/base.h" 10 | 11 | namespace sap::tree 12 | { 13 | struct Separator; 14 | struct BlockObject; 15 | struct InlineObject; 16 | } 17 | 18 | namespace sap::layout 19 | { 20 | namespace linebreak 21 | { 22 | struct BrokenLine; 23 | } 24 | 25 | struct LineAdjustment 26 | { 27 | Length left_protrusion; 28 | Length right_protrusion; 29 | util::hashmap piece_adjustments; 30 | }; 31 | 32 | struct Line : LayoutObject 33 | { 34 | virtual layout::PageCursor compute_position_impl(layout::PageCursor cursor) override; 35 | virtual void render_impl(const LayoutBase* layout, std::vector& pages) const override; 36 | 37 | static std::unique_ptr fromInlineObjects(interp::Interpreter* cs, 38 | const Style& style, 39 | std::span> objs, 40 | Size2d available_space, 41 | bool is_first_line, 42 | bool is_last_line, 43 | std::optional adjustment = std::nullopt); 44 | 45 | sap::Length lineSpacing() const { return m_line_spacing; } 46 | const std::vector>& objects() const { return m_objects; } 47 | 48 | private: 49 | explicit Line(const Style& style, 50 | LayoutSize size, 51 | sap::Length line_spacing, 52 | std::vector> objs); 53 | 54 | private: 55 | sap::Length m_line_spacing; 56 | std::vector> m_objects; 57 | }; 58 | 59 | Length calculatePreferredSeparatorWidth(const tree::Separator* sep, 60 | bool is_end_of_line, 61 | const Style& left_style, 62 | const Style& right_style); 63 | } 64 | -------------------------------------------------------------------------------- /source/layout/paragraph.h: -------------------------------------------------------------------------------- 1 | // paragraph.h 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include "pdf/font.h" // for Font 8 | #include "pdf/units.h" // for PdfScalar 9 | 10 | #include "layout/base.h" // for Cursor, LayoutObject, RectPageLayout, Inter... 11 | #include "layout/word.h" // for Word 12 | #include "layout/line.h" 13 | 14 | namespace sap 15 | { 16 | struct Style; 17 | namespace tree 18 | { 19 | struct Paragraph; 20 | struct DocumentObject; 21 | } 22 | } 23 | 24 | namespace sap::layout 25 | { 26 | struct Paragraph : LayoutObject 27 | { 28 | using LayoutObject::LayoutObject; 29 | 30 | virtual layout::PageCursor compute_position_impl(layout::PageCursor cursor) override; 31 | virtual void render_impl(const LayoutBase* layout, std::vector& pages) const override; 32 | 33 | private: 34 | friend struct tree::Paragraph; 35 | Paragraph(const Style& style, 36 | LayoutSize size, 37 | std::vector> lines, 38 | std::vector> para_inline_objs); 39 | 40 | private: 41 | std::vector> m_lines {}; 42 | std::vector> m_para_inline_objs; 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /source/layout/path.h: -------------------------------------------------------------------------------- 1 | // path.h 2 | // Copyright (c) 2023, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include "sap/path.h" 8 | #include "sap/style.h" 9 | #include "sap/units.h" 10 | 11 | #include "layout/base.h" 12 | 13 | namespace pdf 14 | { 15 | struct Text; 16 | } 17 | 18 | namespace sap::layout 19 | { 20 | struct Path : LayoutObject 21 | { 22 | Path(const Style& style, 23 | LayoutSize size, 24 | PathStyle path_style, 25 | std::shared_ptr segments, 26 | Position origin); 27 | 28 | virtual bool requires_space_reservation() const override; 29 | virtual layout::PageCursor compute_position_impl(layout::PageCursor cursor) override; 30 | virtual void render_impl(const LayoutBase* layout, std::vector& pages) const override; 31 | 32 | const PathSegments& segments() const { return *m_segments; } 33 | 34 | private: 35 | PathStyle m_path_style; 36 | std::shared_ptr m_segments; 37 | Position m_origin; 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /source/layout/spacer.cpp: -------------------------------------------------------------------------------- 1 | // spacer.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "layout/spacer.h" 6 | 7 | namespace sap::layout 8 | { 9 | Spacer::Spacer(const Style& style, LayoutSize size) : LayoutObject(style, std::move(size)) 10 | { 11 | } 12 | 13 | bool Spacer::is_phantom() const 14 | { 15 | return true; 16 | } 17 | 18 | layout::PageCursor Spacer::compute_position_impl(layout::PageCursor cursor) 19 | { 20 | this->positionRelatively(cursor.position()); 21 | 22 | if(m_layout_size.descent != 0) 23 | cursor = cursor.newLine(m_layout_size.descent); 24 | 25 | if(m_layout_size.width != 0) 26 | cursor = cursor.moveRight(m_layout_size.width); 27 | 28 | return cursor; 29 | } 30 | 31 | void Spacer::render_impl(const LayoutBase* layout, std::vector& pages) const 32 | { 33 | (void) layout; 34 | (void) pages; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /source/layout/spacer.h: -------------------------------------------------------------------------------- 1 | // spacer.h 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include "layout/layout_object.h" 8 | 9 | namespace sap::layout 10 | { 11 | struct LayoutBase; 12 | 13 | struct Spacer : LayoutObject 14 | { 15 | explicit Spacer(const Style& cur_style, LayoutSize size); 16 | 17 | virtual bool is_phantom() const override; 18 | virtual layout::PageCursor compute_position_impl(layout::PageCursor cursor) override; 19 | virtual void render_impl(const LayoutBase* layout, std::vector& pages) const override; 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /source/layout/style.cpp: -------------------------------------------------------------------------------- 1 | // style.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "sap/style.h" 6 | 7 | namespace sap 8 | { 9 | const Style& Style::empty() 10 | { 11 | static Style ret {}; 12 | return ret; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /source/layout/word.h: -------------------------------------------------------------------------------- 1 | // word.h 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include "sap/style.h" // for Stylable, Style 8 | #include "sap/units.h" // for Length 9 | 10 | #include "tree/paragraph.h" 11 | 12 | #include "layout/base.h" 13 | 14 | namespace pdf 15 | { 16 | struct Text; 17 | } 18 | 19 | namespace sap::layout 20 | { 21 | struct Word : LayoutObject 22 | { 23 | Word(std::u32string text, const Style& style, Length relative_offset, Length raise_height, LayoutSize size); 24 | 25 | virtual layout::PageCursor compute_position_impl(layout::PageCursor cursor) override; 26 | virtual void render_impl(const LayoutBase* layout, std::vector& pages) const override; 27 | 28 | zst::wstr_view text() const { return m_text; } 29 | 30 | Length relativeOffset() const { return m_relative_offset; } 31 | 32 | private: 33 | void render(AbsolutePagePos line_pos, 34 | std::vector& pages, 35 | pdf::Text* text, 36 | bool is_first_in_text, 37 | Length offset_from_prev) const; 38 | 39 | void render_to_text(pdf::Text* text) const; 40 | 41 | friend struct Line; 42 | 43 | private: 44 | Length m_relative_offset {}; 45 | Length m_raise_height {}; 46 | std::u32string m_text {}; 47 | }; 48 | } 49 | -------------------------------------------------------------------------------- /source/location.h: -------------------------------------------------------------------------------- 1 | // location.h 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | namespace sap 8 | { 9 | /* 10 | Internal location members are 0-based; when printing, convert to 1-based yourself. 11 | */ 12 | struct Location 13 | { 14 | uint32_t line; 15 | uint32_t column; 16 | uint32_t length; 17 | size_t byte_offset; 18 | 19 | zst::str_view filename; 20 | zst::str_view file_contents; 21 | 22 | bool is_builtin = false; 23 | 24 | static inline Location builtin() 25 | { 26 | return Location { 27 | .line = 0, 28 | .column = 0, 29 | .length = 1, 30 | .filename = "builtin", 31 | .is_builtin = true, 32 | }; 33 | } 34 | 35 | bool operator==(const Location&) const = default; 36 | bool operator!=(const Location&) const = default; 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /source/misc/dijkstra.h: -------------------------------------------------------------------------------- 1 | // dijkstra.h 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include // for priority_queue 8 | #include 9 | #include 10 | 11 | #include "util.h" // for hasher 12 | 13 | namespace util 14 | { 15 | // clang-format off 16 | template 17 | concept DijkstraNode = requires(Node node) 18 | { 19 | typename Node::Distance; 20 | 21 | // .neighbours should return an iterable of pairs 22 | { node.neighbours() } -> std::ranges::range; 23 | { *node.neighbours().begin() } -> std::convertible_to&>; 24 | 25 | // Nodes should be hashable and equality comparable 26 | { util::hasher{}(node) } -> std::convertible_to; 27 | { node == node } -> std::same_as; 28 | 29 | requires requires(typename Node::Distance d) 30 | { 31 | // distances should be comparable 32 | d <=> d; 33 | // distances should be default constructible to get a 0 34 | decltype(d) {}; 35 | }; 36 | }; 37 | // clang-format on 38 | 39 | template 40 | std::vector dijkstra_shortest_path(Node start, Node end) 41 | { 42 | std::vector path {}; 43 | using Distance = typename Node::Distance; 44 | // From, To, Distance from *start* to To, via From 45 | using Edge = std::tuple; 46 | using EdgeComp = decltype([](const Edge& lhs, const Edge& rhs) { 47 | return std::get(lhs) > std::get(rhs); 48 | }); 49 | 50 | std::priority_queue, EdgeComp> queue; 51 | 52 | // Maps Tos to Froms 53 | util::hashmap parents; 54 | 55 | queue.emplace(start, start, Distance {}); 56 | while(!queue.empty()) 57 | { 58 | auto [from, to, distance] = std::move(queue.top()); 59 | queue.pop(); 60 | 61 | if(!parents.try_emplace(to, from).second) 62 | continue; 63 | 64 | if(to == end) 65 | { 66 | path.push_back(to); 67 | auto node = to; 68 | while(true) 69 | { 70 | node = parents.find(node)->second; 71 | if(node == start) 72 | break; 73 | 74 | path.push_back(node); 75 | } 76 | 77 | // Reverse path 78 | std::reverse(path.begin(), path.end()); 79 | return path; 80 | } 81 | 82 | // Not at end yet 83 | for(auto& [neighbour, distance_to_neighbour] : to.neighbours()) 84 | queue.emplace(to, neighbour, distance + distance_to_neighbour); 85 | } 86 | 87 | // Unreachable if dijkstra is correctly implemented 88 | sap::internal_error("Fuck, dijkstra exploded"); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /source/misc/hyphenator.h: -------------------------------------------------------------------------------- 1 | // hyphenator.h 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include // for array 8 | 9 | #include "util.h" // for hashmap 10 | #include "misc/short_string.h" 11 | 12 | namespace sap::hyph 13 | { 14 | struct Hyphenator 15 | { 16 | static Hyphenator parseFromFile(const std::string& path); 17 | 18 | const std::vector& computeHyphenationPoints(zst::wstr_view word); 19 | 20 | private: 21 | using HyphenationPoints = std::array; 22 | struct Pats 23 | { 24 | util::hashmap, HyphenationPoints> m_front_pats; 25 | util::hashmap, HyphenationPoints> m_mid_pats; 26 | util::hashmap, HyphenationPoints> m_back_pats; 27 | 28 | static Pats parse(zst::wstr_view contents); 29 | }; 30 | 31 | Hyphenator(Pats pats) : m_pats(std::move(pats)) { } 32 | void parseAndAddExceptions(zst::wstr_view contents); 33 | 34 | Pats m_pats; 35 | util::hashmap> m_hyphenation_cache; 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /source/misc/path_segment.cpp: -------------------------------------------------------------------------------- 1 | // path_segment.cpp 2 | // Copyright (c) 2023, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "sap/path.h" 6 | 7 | namespace sap 8 | { 9 | PathSegment::PathSegment(Kind kind) : m_kind(kind) 10 | { 11 | } 12 | 13 | PathSegment::PathSegment(Kind kind, Position a) : m_kind(kind) 14 | { 15 | m_points[0] = a; 16 | } 17 | 18 | PathSegment::PathSegment(Kind kind, Position a, Position b) : m_kind(kind) 19 | { 20 | m_points[0] = a; 21 | m_points[1] = b; 22 | } 23 | 24 | PathSegment::PathSegment(Kind kind, Position a, Position b, Position c) : m_kind(kind) 25 | { 26 | m_points[0] = a; 27 | m_points[1] = b; 28 | m_points[2] = c; 29 | } 30 | 31 | std::span PathSegment::points() const 32 | { 33 | const size_t num_points = [this]() -> size_t { 34 | switch(m_kind) 35 | { 36 | case Move: return 1; 37 | case Line: return 1; 38 | case CubicBezier: return 3; 39 | case CubicBezierIC1: return 2; 40 | case CubicBezierIC2: return 2; 41 | case Rectangle: return 2; 42 | case Close: return 0; 43 | } 44 | util::unreachable(); 45 | }(); 46 | 47 | return std::span(m_points.begin(), num_points); 48 | } 49 | 50 | PathSegment PathSegment::move(Position to) 51 | { 52 | return PathSegment(Move, std::move(to)); 53 | } 54 | 55 | PathSegment PathSegment::line(Position to) 56 | { 57 | return PathSegment(Line, std::move(to)); 58 | } 59 | 60 | PathSegment PathSegment::cubicBezier(Position cp1, Position cp2, Position final) 61 | { 62 | return PathSegment(CubicBezier, std::move(cp1), std::move(cp2), std::move(final)); 63 | } 64 | 65 | PathSegment PathSegment::cubicBezierIC1(Position cp2, Position final) 66 | { 67 | return PathSegment(CubicBezierIC1, std::move(cp2), std::move(final)); 68 | } 69 | 70 | PathSegment PathSegment::cubicBezierIC2(Position cp1, Position final) 71 | { 72 | return PathSegment(CubicBezierIC2, std::move(cp1), std::move(final)); 73 | } 74 | 75 | PathSegment PathSegment::rectangle(Position pos, Vector2 size) 76 | { 77 | return PathSegment(Rectangle, std::move(pos), Position(size.x(), size.y())); 78 | } 79 | 80 | PathSegment PathSegment::close() 81 | { 82 | return PathSegment(Close); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /source/misc/paths.cpp: -------------------------------------------------------------------------------- 1 | // paths.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include 6 | 7 | #include "sap/config.h" 8 | 9 | namespace sap::paths 10 | { 11 | static std::vector g_include_paths; 12 | static std::vector g_library_paths; 13 | 14 | const std::vector& librarySearchPaths() 15 | { 16 | return g_library_paths; 17 | } 18 | 19 | void addIncludeSearchPath(std::string path) 20 | { 21 | g_include_paths.push_back(std::move(path)); 22 | } 23 | 24 | void addLibrarySearchPath(std::string path) 25 | { 26 | g_library_paths.push_back(std::move(path)); 27 | } 28 | 29 | static ErrorOr 30 | resolve_path(const Location& loc, zst::str_view _path, const std::vector& search) 31 | { 32 | // first, try to resolve it relative to the current directory 33 | auto cwd = stdfs::current_path(); 34 | auto path = stdfs::path(_path.sv()); 35 | 36 | if(auto p = cwd / path; stdfs::exists(p)) 37 | return Ok(p.string()); 38 | 39 | for(auto it = search.rbegin(); it != search.rend(); ++it) 40 | { 41 | if(auto p = stdfs::path(*it) / path; stdfs::exists(p)) 42 | return Ok(p.string()); 43 | } 44 | 45 | return ErrMsg(loc, "file '{}' could not be found", _path); 46 | } 47 | 48 | ErrorOr resolveInclude(const Location& loc, zst::str_view path) 49 | { 50 | return resolve_path(loc, path, g_include_paths); 51 | } 52 | 53 | ErrorOr resolveLibrary(const Location& loc, zst::str_view path) 54 | { 55 | return resolve_path(loc, path, g_library_paths); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /source/misc/util.cpp: -------------------------------------------------------------------------------- 1 | // util.cpp 2 | // Copyright (c) 2021, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #if !defined(_WIN32) 10 | #include 11 | #include 12 | #endif 13 | 14 | #include "util.h" 15 | 16 | namespace util 17 | { 18 | #if defined(_WIN32) 19 | zst::unique_span readEntireFile(const std::string& path) 20 | { 21 | FILE* fd = fopen(path.c_str(), "rb"); 22 | if(fd == nullptr) 23 | sap::internal_error("failed to open '{}'; fopen(): {}", path, strerror(errno)); 24 | 25 | const size_t size = stdfs::file_size(path); 26 | auto ptr = new uint8_t[size]; 27 | 28 | if(fread(&ptr[0], 1, size, fd) != size) 29 | sap::internal_error("failed to read '{}': fread(): {}", path, strerror(errno)); 30 | 31 | fclose(fd); 32 | return zst::unique_span((uint8_t*) ptr, size, [](const void* p, size_t n) { 33 | delete[] reinterpret_cast(const_cast(p)); 34 | }); 35 | } 36 | 37 | #else 38 | zst::unique_span readEntireFile(const std::string& path) 39 | { 40 | auto fd = open(path.c_str(), O_RDONLY); 41 | if(fd < 0) 42 | sap::internal_error("failed to open '{}'; open(): {}", path, strerror(errno)); 43 | 44 | struct stat st; 45 | if(fstat(fd, &st) < 0) 46 | sap::internal_error("failed to stat '{}'; fstat(): {}", path, strerror(errno)); 47 | 48 | auto size = static_cast(st.st_size); 49 | auto ptr = mmap(0, size, PROT_READ, MAP_PRIVATE, fd, /* offset: */ 0); 50 | if(ptr == reinterpret_cast(-1)) 51 | sap::internal_error("failed to read '{}': mmap(): {}", path, strerror(errno)); 52 | 53 | close(fd); 54 | return zst::unique_span((uint8_t*) ptr, size, [](const void* p, size_t n) { 55 | munmap(const_cast(p), n); 56 | }); 57 | } 58 | #endif 59 | } 60 | 61 | namespace sap 62 | { 63 | OwnedImageBitmap ImageBitmap::clone() const 64 | { 65 | auto num_pixels = this->pixel_width * this->pixel_height; 66 | auto new_rgb = zst::unique_span(new uint8_t[num_pixels * 3], num_pixels * 3); 67 | memcpy(new_rgb.get(), this->rgb.data(), num_pixels * 3); 68 | 69 | zst::unique_span new_alpha {}; 70 | if(this->haveAlpha()) 71 | { 72 | new_alpha = zst::unique_span(new uint8_t[num_pixels], num_pixels); 73 | memcpy(new_alpha.get(), this->alpha.data(), num_pixels); 74 | } 75 | 76 | return OwnedImageBitmap { 77 | .pixel_width = pixel_width, 78 | .pixel_height = pixel_height, 79 | .bits_per_pixel = bits_per_pixel, 80 | .rgb = std::move(new_rgb), 81 | .alpha = std::move(new_alpha), 82 | }; 83 | } 84 | 85 | OwnedImageBitmap OwnedImageBitmap::clone() const 86 | { 87 | return this->span().clone(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /source/pdf/annotation.cpp: -------------------------------------------------------------------------------- 1 | // annotation.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "pdf/file.h" 6 | #include "pdf/page.h" 7 | #include "pdf/object.h" 8 | #include "pdf/annotation.h" 9 | 10 | namespace pdf 11 | { 12 | Annotation::Annotation(pdf::Position2d pos, pdf::Size2d size) : m_position(pos), m_size(size) 13 | { 14 | } 15 | 16 | LinkAnnotation::LinkAnnotation(pdf::Position2d pos, pdf::Size2d size, Destination dest) 17 | : Annotation(pos, size), m_destination(std::move(dest)) 18 | { 19 | } 20 | 21 | Dictionary* LinkAnnotation::toDictionary(File* file) const 22 | { 23 | auto dest = Array::create(IndirectRef::create(file->getPage(m_destination.page)->dictionary()), 24 | names::XYZ.ptr(), 25 | Decimal::create(m_destination.position.x().value()), // 26 | Decimal::create(m_destination.position.y().value()), // 27 | Decimal::create(m_destination.zoom)); 28 | 29 | auto dict = Dictionary::createIndirect(names::Annot, 30 | { 31 | { names::Subtype, names::Link.ptr() }, 32 | { 33 | // our m_position is the upper left, but pdf expects the Rect to be lower-left / upper-right 34 | // so we need to adjust this a bit. this is further complicated by the fact that here, we 35 | // are working in PDF space -- Y-up. 36 | names::Rect, 37 | Array::create( // 38 | Decimal::create(m_position.x().value()), // 39 | Decimal::create(m_position.y().value()), // 40 | Decimal::create((m_position.x() + m_size.x()).value()), // 41 | Decimal::create((m_position.y() + m_size.y()).value())), // 42 | }, 43 | // { names::BS, Dictionary::create(names::Border, { { names::S, names::S.ptr() } }) }, 44 | { names::F, Integer::create(1 << 2) }, // set the Print flag 45 | { names::C, Array::create(Decimal::create(1.0), Decimal::create(0), Decimal::create(0)) }, 46 | { 47 | names::A, 48 | Dictionary::create(names::Action, { { names::S, names::GoTo.ptr() }, { names::D, dest } }), 49 | }, 50 | }); 51 | 52 | return dict; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /source/pdf/annotation.h: -------------------------------------------------------------------------------- 1 | // annotation.h 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include "pdf/destination.h" 8 | 9 | namespace pdf 10 | { 11 | struct File; 12 | struct Dictionary; 13 | 14 | struct OutlineItem 15 | { 16 | enum class State 17 | { 18 | Open, 19 | Closed 20 | }; 21 | 22 | OutlineItem(std::string name, Destination dest, State state = State::Closed); 23 | 24 | void addChild(OutlineItem child); 25 | 26 | const std::string& name() const { return m_name; } 27 | State state() const { return m_default_state; } 28 | const std::vector& children() const { return m_children; } 29 | 30 | Dictionary* toDictionary(Dictionary* parent, File* file) const; 31 | static std::pair linkChildren(const std::vector& children, 32 | Dictionary* parent, 33 | File* file); 34 | 35 | private: 36 | std::string m_name; 37 | Destination m_destination; 38 | State m_default_state; 39 | 40 | std::vector m_children; 41 | }; 42 | 43 | struct Annotation 44 | { 45 | virtual Dictionary* toDictionary(File* file) const = 0; 46 | virtual ~Annotation() = default; 47 | 48 | protected: 49 | Annotation(pdf::Position2d position, pdf::Size2d size); 50 | 51 | pdf::Position2d m_position; 52 | pdf::Size2d m_size; 53 | }; 54 | 55 | struct LinkAnnotation : Annotation 56 | { 57 | LinkAnnotation(pdf::Position2d position, pdf::Size2d size, Destination dest); 58 | 59 | virtual Dictionary* toDictionary(File* file) const override; 60 | 61 | private: 62 | Destination m_destination; 63 | }; 64 | } 65 | -------------------------------------------------------------------------------- /source/pdf/builtin_font.h: -------------------------------------------------------------------------------- 1 | // builtin_font.h 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include "font/font_source.h" 8 | 9 | template <> 10 | struct std::hash> 11 | { 12 | size_t operator()(std::pair p) const 13 | { 14 | return std::hash()((static_cast(p.first) << 32) | (static_cast(p.second))); 15 | } 16 | }; 17 | 18 | namespace pdf 19 | { 20 | struct BuiltinFont : font::FontSource 21 | { 22 | enum Core14 23 | { 24 | TimesRoman, 25 | TimesBold, 26 | TimesItalic, 27 | TimesBoldItalic, 28 | 29 | Courier, 30 | CourierBold, 31 | CourierOblique, 32 | CourierBoldOblique, 33 | 34 | Helvetica, 35 | HelveticaBold, 36 | HelveticaOblique, 37 | HelveticaBoldOblique, 38 | 39 | Symbol, 40 | ZapfDingbats, 41 | }; 42 | 43 | virtual std::string name() const override { return m_name; } 44 | Core14 kind() const { return m_kind; } 45 | 46 | GlyphId getFirstGlyphId() const { return m_first_glyph_id; } 47 | GlyphId getLastGlyphId() const { return m_last_glyph_id; } 48 | 49 | virtual bool isBuiltin() const override { return true; } 50 | 51 | virtual util::hashmap 52 | getPositioningAdjustmentsForGlyphSequence(zst::span glyphs, 53 | const font::FeatureSet& features) const override; 54 | virtual std::optional 55 | performSubstitutionsForGlyphSequence(zst::span glyphs, 56 | const font::FeatureSet& features) const override; 57 | 58 | virtual font::GlyphMetrics get_glyph_metrics_impl(GlyphId glyphid) const override; 59 | 60 | static std::unique_ptr get(Core14 font); 61 | 62 | private: 63 | BuiltinFont(Core14 which); 64 | 65 | Core14 m_kind; 66 | 67 | std::string m_name; 68 | GlyphId m_first_glyph_id; 69 | GlyphId m_last_glyph_id; 70 | 71 | util::hashmap, font::GlyphAdjustment> m_kerning_pairs; 72 | 73 | // note: we know that the ligatures are only in pairs... and they're only ever 'fi' and 'fl' 74 | util::hashmap, GlyphId> m_ligature_pairs; 75 | }; 76 | } 77 | -------------------------------------------------------------------------------- /source/pdf/destination.h: -------------------------------------------------------------------------------- 1 | // destination.h 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include "pdf/units.h" 8 | 9 | namespace pdf 10 | { 11 | struct File; 12 | struct Dictionary; 13 | 14 | struct Destination 15 | { 16 | size_t page; 17 | double zoom; 18 | pdf::Position2d position; 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /source/pdf/file.h: -------------------------------------------------------------------------------- 1 | // file.h 2 | // Copyright (c) 2021, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #include "pdf/object.h" 10 | #include "pdf/annotation.h" 11 | 12 | namespace pdf 13 | { 14 | struct Page; 15 | struct Writer; 16 | struct Object; 17 | 18 | struct File 19 | { 20 | File(); 21 | 22 | File(const File&) = delete; 23 | File& operator=(const File&) = delete; 24 | 25 | File(File&&) = default; 26 | File& operator=(File&&) = default; 27 | 28 | void write(Writer* stream); 29 | void addObject(Object* obj); 30 | 31 | size_t getNewObjectId(); 32 | 33 | void addPage(Page* page); 34 | Page* getPage(size_t page_num) const; 35 | 36 | void addOutlineItem(OutlineItem outline_item); 37 | 38 | size_t getNextFontResourceNumber(); 39 | 40 | private: 41 | Dictionary* create_page_tree(); 42 | 43 | private: 44 | size_t m_current_id = 0; 45 | util::hashmap m_objects; 46 | 47 | std::vector m_pages; 48 | std::vector m_outline_items; 49 | 50 | size_t m_current_font_number = 0; 51 | }; 52 | 53 | } 54 | -------------------------------------------------------------------------------- /source/pdf/font/.builtin_font.cpp.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhiayang/sap/012a780bcd4ba901253146b67596528875cfa039/source/pdf/font/.builtin_font.cpp.swp -------------------------------------------------------------------------------- /source/pdf/font/cidset.cpp: -------------------------------------------------------------------------------- 1 | // cidset.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "types.h" // for GlyphId 6 | 7 | #include "pdf/font.h" // for Font, File 8 | #include "pdf/object.h" // for Stream 9 | 10 | #include "font/font_file.h" 11 | 12 | namespace pdf 13 | { 14 | void PdfFont::writeCIDSet() const 15 | { 16 | auto font_file = dynamic_cast(m_source.get()); 17 | if(not font_file) 18 | return; 19 | 20 | auto stream = m_cidset; 21 | stream->clear(); 22 | 23 | /* 24 | NOTE: PDF/A Specification: ISO 19005-2:2011, Clause: 6.2.11.4.2, Test number: 2 25 | 26 | If the FontDescriptor dictionary of an embedded CID font contains a CIDSet stream, then it shall 27 | identify all CIDs which are present in the font program, regardless of whether a CID in the font is 28 | referenced or used by the PDF or not. 29 | 30 | 31 | Our current subsetting strategy for both TTF and CFF fonts is simple to delete the glyph 32 | info for unused glyphs, and *NOT* to re-number the glyphs in any way. This means that the number 33 | of glyphs "present" in the font does not change --- ie. we must mark every glyph as present. 34 | */ 35 | 36 | uint8_t ff[1] = { 0xFF }; 37 | for(size_t i = 0, n = (font_file->numGlyphs() + 7) / 8; i < n; i++) 38 | stream->append(ff, 1); 39 | 40 | #if 0 41 | int num_bits = 0; 42 | uint8_t current = 0; 43 | for(uint32_t gid = 0; gid < font_file->numGlyphs(); gid++) 44 | { 45 | bool bit = true; // font_file->isGlyphUsed(GlyphId { gid }); 46 | current <<= 1; 47 | current |= (bit ? 1 : 0); 48 | num_bits++; 49 | 50 | if(num_bits == 8) 51 | { 52 | stream->append(¤t, 1); 53 | num_bits = 0; 54 | current = 0; 55 | } 56 | } 57 | 58 | if(num_bits > 0) 59 | { 60 | current <<= (8 - num_bits); 61 | stream->append(¤t, 1); 62 | } 63 | #endif 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /source/pdf/font/misc.cpp: -------------------------------------------------------------------------------- 1 | // misc.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include 6 | 7 | #include "pdf/builtin_font.h" 8 | 9 | #include "./builtin_afms/times_roman.h" 10 | #include "./builtin_afms/times_bold.h" 11 | #include "./builtin_afms/times_italic.h" 12 | #include "./builtin_afms/times_bold_italic.h" 13 | 14 | #include "./builtin_afms/courier.h" 15 | #include "./builtin_afms/courier_bold.h" 16 | #include "./builtin_afms/courier_oblique.h" 17 | #include "./builtin_afms/courier_bold_oblique.h" 18 | 19 | #include "./builtin_afms/helvetica.h" 20 | #include "./builtin_afms/helvetica_bold.h" 21 | #include "./builtin_afms/helvetica_oblique.h" 22 | #include "./builtin_afms/helvetica_bold_oblique.h" 23 | 24 | #include "./builtin_afms/symbol.h" 25 | #include "./builtin_afms/zapf_dingbats.h" 26 | 27 | namespace pdf 28 | { 29 | template 30 | constexpr static std::pair split_array(const uint8_t (&arr)[N]) 31 | { 32 | return { &arr[0], sizeof(arr) }; 33 | } 34 | 35 | std::pair get_compressed_afm(BuiltinFont::Core14 font) 36 | { 37 | using C14 = BuiltinFont::Core14; 38 | switch(font) 39 | { 40 | case C14::TimesRoman: return split_array(AFM_TIMES_ROMAN); 41 | case C14::TimesBold: return split_array(AFM_TIMES_BOLD); 42 | case C14::TimesItalic: return split_array(AFM_TIMES_ITALIC); 43 | case C14::TimesBoldItalic: return split_array(AFM_TIMES_BOLD_ITALIC); 44 | case C14::Courier: return split_array(AFM_COURIER); 45 | case C14::CourierBold: return split_array(AFM_COURIER_BOLD); 46 | case C14::CourierOblique: return split_array(AFM_COURIER_OBLIQUE); 47 | case C14::CourierBoldOblique: return split_array(AFM_COURIER_BOLD_OBLIQUE); 48 | case C14::Helvetica: return split_array(AFM_HELVETICA); 49 | case C14::HelveticaBold: return split_array(AFM_HELVETICA_BOLD); 50 | case C14::HelveticaOblique: return split_array(AFM_HELVETICA_OBLIQUE); 51 | case C14::HelveticaBoldOblique: return split_array(AFM_HELVETICA_BOLD_OBLIQUE); 52 | case C14::Symbol: return split_array(AFM_SYMBOL); 53 | case C14::ZapfDingbats: return split_array(AFM_ZAPF_DINGBATS); 54 | } 55 | util::unreachable(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /source/pdf/graphics.cpp: -------------------------------------------------------------------------------- 1 | // graphics.cpp 2 | // Copyright (c) 2023, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "pdf/object.h" 6 | #include "pdf/resource.h" 7 | 8 | namespace pdf 9 | { 10 | ExtGraphicsState::ExtGraphicsState() : Resource(KIND_EXTGSTATE), m_dict(Dictionary::create({})) 11 | { 12 | m_dict->add(names::CA, Integer::create(1)); 13 | m_dict->add(names::ca, Integer::create(1)); 14 | } 15 | 16 | void ExtGraphicsState::serialise() const 17 | { 18 | // ????? 19 | } 20 | 21 | Object* ExtGraphicsState::resourceObject() const 22 | { 23 | return m_dict; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /source/pdf/misc.h: -------------------------------------------------------------------------------- 1 | // misc.h 2 | // Copyright (c) 2021, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include // for stderr 8 | 9 | namespace pdf 10 | { 11 | struct Writer; 12 | struct Object; 13 | 14 | template 15 | [[noreturn]] inline void error(zst::str_view fmt, Args&&... args) 16 | { 17 | zpr::fprintln(stderr, "internal error (pdf): {}", zpr::fwd(fmt, static_cast(args)...)); 18 | abort(); 19 | // exit(1); 20 | } 21 | 22 | std::string encodeStringLiteral(zst::str_view sv); 23 | 24 | 25 | struct IndirHelper 26 | { 27 | IndirHelper(Writer* w, const Object* obj); 28 | ~IndirHelper(); 29 | 30 | IndirHelper(IndirHelper&&) = delete; 31 | IndirHelper(const IndirHelper&) = delete; 32 | 33 | Writer* w = 0; 34 | bool indirect = false; 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /source/pdf/outlines.cpp: -------------------------------------------------------------------------------- 1 | // outlines.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "pdf/pdf.h" 6 | #include "pdf/annotation.h" 7 | 8 | namespace pdf 9 | { 10 | OutlineItem::OutlineItem(std::string name, Destination dest, State state) 11 | : m_name(std::move(name)), m_destination(std::move(dest)), m_default_state(state) 12 | { 13 | } 14 | 15 | void OutlineItem::addChild(OutlineItem child) 16 | { 17 | m_children.push_back(std::move(child)); 18 | } 19 | 20 | std::pair OutlineItem::linkChildren( // 21 | const std::vector& children, 22 | Dictionary* parent, 23 | File* file) 24 | { 25 | assert(not children.empty()); 26 | auto first_child = children.front().toDictionary(parent, file); 27 | auto last_child = children.size() == 1 ? first_child : children.back().toDictionary(parent, file); 28 | 29 | // now, string together the other children. 30 | Dictionary* current_child = first_child; 31 | for(size_t i = 1; i < children.size(); i++) 32 | { 33 | auto right_sibling = i + 1 == children.size() ? last_child : children[i].toDictionary(parent, file); 34 | current_child->add(names::Next, right_sibling); 35 | right_sibling->add(names::Prev, current_child); 36 | 37 | current_child = right_sibling; 38 | } 39 | 40 | return { first_child, last_child }; 41 | } 42 | 43 | Dictionary* OutlineItem::toDictionary(Dictionary* parent, File* file) const 44 | { 45 | auto dict = Dictionary::createIndirect({}); 46 | dict->add(names::Title, String::create(m_name)); 47 | dict->add(names::Parent, parent); 48 | 49 | auto dest = Array::create(IndirectRef::create(file->getPage(m_destination.page)->dictionary()), 50 | names::XYZ.ptr(), 51 | Decimal::create(m_destination.position.x().value()), // 52 | Decimal::create(m_destination.position.y().value()), // 53 | Decimal::create(m_destination.zoom)); 54 | 55 | dict->add(names::A, 56 | Dictionary::create(names::Action, 57 | { 58 | { names::S, names::GoTo.ptr() }, 59 | { names::D, dest }, 60 | })); 61 | 62 | if(m_children.size() > 0) 63 | { 64 | auto [first_child, last_child] = linkChildren(m_children, dict, file); 65 | dict->add(names::Count, Integer::create(-checked_cast(m_children.size()))); 66 | dict->add(names::First, first_child); 67 | dict->add(names::Last, last_child); 68 | } 69 | 70 | return dict; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /source/pdf/page.h: -------------------------------------------------------------------------------- 1 | // page.h 2 | // Copyright (c) 2021, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include "pdf/units.h" // for Size2d, Vector2_YDown, Vector2_YUp 8 | #include "pdf/object.h" // for Dictionary, File 9 | #include "pdf/page_object.h" // for PageObject 10 | 11 | namespace pdf 12 | { 13 | struct File; 14 | struct Resource; 15 | struct Annotation; 16 | 17 | struct Page 18 | { 19 | Page(); 20 | 21 | void serialise(File* file) const; 22 | void serialiseResources() const; 23 | 24 | void addObject(PageObject* obj); 25 | 26 | Size2d size() const; 27 | 28 | Vector2_YUp convertVector2(Vector2_YDown v2) const; 29 | Vector2_YDown convertVector2(Vector2_YUp v2) const; 30 | 31 | void addResource(const Resource* resource) const; 32 | void addAnnotation(const Annotation* annotation); 33 | 34 | Dictionary* dictionary() const { return m_dictionary; } 35 | 36 | private: 37 | Dictionary* m_dictionary; 38 | std::vector m_objects; 39 | std::vector m_annotations; 40 | mutable util::hashset m_resources; 41 | 42 | Size2d m_page_size {}; 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /source/pdf/page_object.h: -------------------------------------------------------------------------------- 1 | // page_object.h 2 | // Copyright (c) 2021, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | namespace pdf 8 | { 9 | struct Page; 10 | struct Stream; 11 | 12 | struct PageObject 13 | { 14 | virtual ~PageObject(); 15 | 16 | virtual void writePdfCommands(Stream* stream) const = 0; 17 | virtual void addResources(const Page* page) const = 0; 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /source/pdf/path.h: -------------------------------------------------------------------------------- 1 | // path.h 2 | // Copyright (c) 2023, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #include "sap/path.h" 10 | #include "sap/colour.h" 11 | 12 | #include "pdf/units.h" 13 | #include "pdf/page_object.h" 14 | 15 | namespace pdf 16 | { 17 | struct GraphicsState 18 | { 19 | }; 20 | 21 | struct Path : PageObject 22 | { 23 | struct MoveTo 24 | { 25 | Position2d pos; 26 | }; 27 | 28 | struct LineTo 29 | { 30 | Position2d pos; 31 | }; 32 | 33 | struct CubicBezier 34 | { 35 | Position2d cp1; 36 | Position2d cp2; 37 | Position2d end; 38 | }; 39 | 40 | struct CubicBezierIC1 41 | { 42 | Position2d cp2; 43 | Position2d end; 44 | }; 45 | 46 | struct CubicBezierIC2 47 | { 48 | Position2d cp1; 49 | Position2d end; 50 | }; 51 | 52 | struct Rectangle 53 | { 54 | Position2d start; 55 | Size2d size; 56 | }; 57 | 58 | struct ClosePath 59 | { 60 | }; 61 | 62 | struct PaintStyle 63 | { 64 | using CapStyle = sap::PathStyle::CapStyle; 65 | using JoinStyle = sap::PathStyle::JoinStyle; 66 | 67 | PdfScalar line_width; 68 | CapStyle cap_style; 69 | JoinStyle join_style; 70 | double miter_limit; 71 | sap::Colour stroke_colour; 72 | sap::Colour fill_colour; 73 | }; 74 | 75 | using Segment = std:: 76 | variant; 77 | 78 | Path(Position2d display_position, Size2d display_size); 79 | void addSegment(Segment segment); 80 | 81 | virtual void addResources(const Page* page) const override; 82 | virtual void writePdfCommands(Stream* stream) const override; 83 | 84 | private: 85 | Position2d m_display_position; 86 | Size2d m_display_size; 87 | std::vector m_segments; 88 | }; 89 | } 90 | -------------------------------------------------------------------------------- /source/pdf/pdf.h: -------------------------------------------------------------------------------- 1 | // pdf.h 2 | // Copyright (c) 2021, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include "pdf/file.h" 8 | #include "pdf/misc.h" 9 | #include "pdf/page.h" 10 | #include "pdf/units.h" 11 | #include "pdf/object.h" 12 | #include "pdf/writer.h" 13 | -------------------------------------------------------------------------------- /source/pdf/resource.cpp: -------------------------------------------------------------------------------- 1 | // resource.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "pdf/object.h" 6 | #include "pdf/resource.h" 7 | 8 | namespace pdf 9 | { 10 | Resource::~Resource() 11 | { 12 | } 13 | 14 | Resource::Resource(Kind kind) : m_kind(kind) 15 | { 16 | auto ks = this->resourceKindString(); 17 | const char* prefix = "U"; 18 | switch(m_kind) 19 | { 20 | case KIND_FONT: prefix = "F"; break; 21 | case KIND_XOBJECT: prefix = "x"; break; 22 | case KIND_EXTGSTATE: prefix = "g"; break; 23 | } 24 | 25 | m_name = zpr::sprint("{}{}", prefix, pdf::getNewResourceId(/* key: */ ks)); 26 | } 27 | 28 | zst::str_view Resource::resourceName() const 29 | { 30 | return m_name; 31 | } 32 | 33 | zst::str_view Resource::resourceKindString() const 34 | { 35 | switch(m_kind) 36 | { 37 | case KIND_FONT: return "Font"; 38 | case KIND_XOBJECT: return "XObject"; 39 | case KIND_EXTGSTATE: return "ExtGState"; 40 | } 41 | util::unreachable(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /source/pdf/resource.h: -------------------------------------------------------------------------------- 1 | // resource.h 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | namespace pdf 11 | { 12 | struct Name; 13 | struct Object; 14 | struct Dictionary; 15 | 16 | struct Resource 17 | { 18 | virtual ~Resource(); 19 | zst::str_view resourceName() const; 20 | zst::str_view resourceKindString() const; 21 | 22 | virtual void serialise() const = 0; 23 | virtual Object* resourceObject() const = 0; 24 | 25 | protected: 26 | enum Kind 27 | { 28 | KIND_FONT = 1, 29 | KIND_XOBJECT = 2, 30 | KIND_EXTGSTATE = 3, 31 | }; 32 | 33 | Resource(Kind kind); 34 | 35 | private: 36 | Kind m_kind; 37 | std::string m_name; 38 | }; 39 | 40 | struct ExtGraphicsState : Resource 41 | { 42 | ExtGraphicsState(); 43 | virtual void serialise() const override; 44 | virtual Object* resourceObject() const override; 45 | 46 | private: 47 | Dictionary* m_dict; 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /source/pdf/util/misc.cpp: -------------------------------------------------------------------------------- 1 | // misc.cpp 2 | // Copyright (c) 2021, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "pdf/misc.h" 6 | 7 | namespace pdf 8 | { 9 | std::string encodeStringLiteral(zst::str_view sv) 10 | { 11 | std::string ret; 12 | ret.reserve(sv.size() * 2 + 2); 13 | 14 | ret += "<"; 15 | for(int8_t c : sv) 16 | ret += zpr::sprint("{02x}", static_cast(c)); 17 | 18 | ret += ">"; 19 | return ret; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /source/pdf/util/writer.cpp: -------------------------------------------------------------------------------- 1 | // writer.cpp 2 | // Copyright (c) 2021, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #if defined(_WIN32) 6 | #include 7 | #else 8 | #include 9 | #endif 10 | 11 | #include // for open, O_CREAT, O_TRUNC, O_WRONLY 12 | 13 | #include "pdf/misc.h" // for error 14 | #include "pdf/object.h" // for Object 15 | #include "pdf/writer.h" // for Writer 16 | 17 | namespace pdf 18 | { 19 | Writer::Writer(zst::str_view path_) 20 | { 21 | this->path = std::move(path_); 22 | this->bytes_written = 0; 23 | this->nesting = 0; 24 | 25 | #if defined(_WIN32) 26 | if(this->fd = _open(path.str().c_str(), _O_WRONLY | _O_CREAT | _O_TRUNC, _S_IREAD | _S_IWRITE); this->fd < 0) 27 | #else 28 | if(this->fd = open(path.str().c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0664); this->fd < 0) 29 | #endif 30 | pdf::error("failed to open file for writing; open(): {}", strerror(errno)); 31 | } 32 | 33 | Writer::~Writer() 34 | { 35 | this->close(); 36 | } 37 | 38 | size_t Writer::position() const 39 | { 40 | return this->bytes_written; 41 | } 42 | 43 | void Writer::close() 44 | { 45 | if(this->fd != -1) 46 | { 47 | #if defined(_WIN32) 48 | _close(this->fd); 49 | #else 50 | ::close(this->fd); 51 | #endif 52 | } 53 | 54 | this->fd = -1; 55 | } 56 | 57 | void Writer::write(const Object* obj) 58 | { 59 | obj->write(this); 60 | } 61 | 62 | void Writer::writeFull(const Object* obj) 63 | { 64 | obj->writeFull(this); 65 | } 66 | 67 | size_t Writer::write(zst::str_view sv) 68 | { 69 | return this->writeBytes(reinterpret_cast(sv.data()), sv.size()); 70 | } 71 | 72 | size_t Writer::writeBytes(const uint8_t* bytes, size_t len) 73 | { 74 | #if defined(_WIN32) 75 | auto n = _write(this->fd, bytes, static_cast(len)); 76 | #else 77 | auto n = ::write(this->fd, bytes, len); 78 | #endif 79 | if(n < 0 || static_cast(n) != len) 80 | pdf::error("file write failed; write(): {}", strerror(errno)); 81 | 82 | this->bytes_written += len; 83 | return len; 84 | } 85 | 86 | size_t Writer::writeln(zst::str_view sv) 87 | { 88 | return this->write(sv) + this->write("\n"); 89 | } 90 | 91 | size_t Writer::writeln() 92 | { 93 | return this->writeln(""); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /source/pdf/win_ansi_encoding.h: -------------------------------------------------------------------------------- 1 | // win_ansi_encoding.h 2 | // Copyright (c) 2021, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include "types.h" 8 | 9 | namespace pdf::encoding 10 | { 11 | static inline uint8_t WIN_ANSI(char32_t cp) 12 | { 13 | static std::map cmap; 14 | if(cmap.empty()) 15 | { 16 | for(char32_t i = 0; i < 128; i++) 17 | cmap[i] = static_cast(i); 18 | 19 | cmap[U'\u20ac'] = 128; 20 | cmap[U'\u201a'] = 130; 21 | cmap[U'\u0192'] = 131; 22 | cmap[U'\u201e'] = 132; 23 | cmap[U'\u2026'] = 133; 24 | cmap[U'\u2020'] = 134; 25 | cmap[U'\u2021'] = 135; 26 | cmap[U'\u02c6'] = 136; 27 | cmap[U'\u2030'] = 137; 28 | cmap[U'\u0160'] = 138; 29 | cmap[U'\u2039'] = 139; 30 | cmap[U'\u0152'] = 140; 31 | cmap[U'\u017d'] = 142; 32 | cmap[U'\u2018'] = 145; 33 | cmap[U'\u2019'] = 146; 34 | cmap[U'\u201c'] = 147; 35 | cmap[U'\u201d'] = 148; 36 | cmap[U'\u2022'] = 149; 37 | cmap[U'\u2013'] = 150; 38 | cmap[U'\u2014'] = 151; 39 | cmap[U'\u02dc'] = 152; 40 | cmap[U'\u2122'] = 153; 41 | cmap[U'\u0161'] = 154; 42 | cmap[U'\u203a'] = 155; 43 | cmap[U'\u0153'] = 156; 44 | cmap[U'\u017e'] = 158; 45 | cmap[U'\u0178'] = 159; 46 | 47 | for(char32_t i = 0xa0; i <= 0xff; i++) 48 | cmap[i] = static_cast(i); 49 | } 50 | 51 | return cmap[cp]; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /source/pdf/writer.h: -------------------------------------------------------------------------------- 1 | // writer.h 2 | // Copyright (c) 2021, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | namespace pdf 8 | { 9 | struct Object; 10 | 11 | struct Writer 12 | { 13 | Writer(zst::str_view path); 14 | ~Writer(); 15 | 16 | int fd; 17 | int nesting; 18 | zst::str_view path; 19 | size_t bytes_written; 20 | 21 | void close(); 22 | 23 | size_t position() const; 24 | 25 | size_t write(zst::str_view sv); 26 | size_t writeln(zst::str_view sv); 27 | size_t writeln(); 28 | 29 | void write(const Object* obj); 30 | void writeFull(const Object* obj); 31 | 32 | size_t writeBytes(const uint8_t* bytes, size_t len); 33 | 34 | template 35 | size_t write(zst::str_view fmt, Args&&... args) 36 | { 37 | return zpr::cprint( 38 | [this](const char* s, size_t l) { 39 | this->write(zst::str_view(s, l)); 40 | }, 41 | fmt, static_cast(args)...); 42 | } 43 | 44 | template 45 | size_t writeln(zst::str_view fmt, Args&&... args) 46 | { 47 | return zpr::cprintln( 48 | [this](const char* s, size_t l) { 49 | this->write(zst::str_view(s, l)); 50 | }, 51 | fmt, static_cast(args)...); 52 | } 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /source/pdf/xobject.cpp: -------------------------------------------------------------------------------- 1 | // xobject.cpp 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "pdf/page.h" 6 | #include "pdf/object.h" 7 | #include "pdf/xobject.h" 8 | 9 | namespace pdf 10 | { 11 | XObject::XObject(const Name& subtype) : Resource(KIND_XOBJECT), m_stream(Stream::create()) 12 | { 13 | m_stream->dictionary()->add(names::Type, names::XObject.ptr()); 14 | m_stream->setCompressed(true); 15 | } 16 | 17 | void XObject::serialise() const 18 | { 19 | if(m_did_serialise) 20 | return; 21 | 22 | m_did_serialise = true; 23 | } 24 | 25 | Object* XObject::resourceObject() const 26 | { 27 | return m_stream; 28 | } 29 | 30 | void XObject::addResources(const Page* page) const 31 | { 32 | page->addResource(this); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /source/pdf/xobject.h: -------------------------------------------------------------------------------- 1 | // xobject.h 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include "pdf/units.h" 8 | #include "pdf/resource.h" 9 | #include "pdf/page_object.h" 10 | 11 | namespace pdf 12 | { 13 | struct File; 14 | struct Name; 15 | struct Stream; 16 | 17 | struct XObject : PageObject, Resource 18 | { 19 | XObject(const Name& subtype); 20 | 21 | virtual void addResources(const Page* page) const override; 22 | virtual Object* resourceObject() const override; 23 | virtual void serialise() const override; 24 | 25 | const std::string& getResourceName() const; 26 | Stream* stream() const { return m_stream; } 27 | 28 | protected: 29 | Stream* m_stream; 30 | mutable bool m_did_serialise = false; 31 | }; 32 | 33 | 34 | struct Image : XObject 35 | { 36 | Image(sap::ImageBitmap image_data, Size2d display_size, Position2d display_position); 37 | virtual void writePdfCommands(Stream* stream) const override; 38 | 39 | private: 40 | sap::ImageBitmap m_image; 41 | Size2d m_display_size; 42 | Position2d m_display_position; 43 | 44 | Stream* m_alpha_channel = nullptr; 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /source/precompile.h: -------------------------------------------------------------------------------- 1 | // precompile.h 2 | // Copyright (c) 2021, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | // make ssize_t on windows 29 | #ifdef _WIN32 30 | using ssize_t = std::make_signed_t; 31 | #endif 32 | 33 | #include "defs.h" 34 | #include "pool.h" 35 | #include "util.h" 36 | #include "units.h" 37 | #include "types.h" 38 | 39 | namespace stdfs = std::filesystem; 40 | -------------------------------------------------------------------------------- /source/sap/annotation.h: -------------------------------------------------------------------------------- 1 | // annotation.h 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include "layout/cursor.h" 8 | 9 | namespace sap 10 | { 11 | struct OutlineItem 12 | { 13 | std::string title; 14 | layout::AbsolutePagePos position; 15 | std::vector children; 16 | }; 17 | 18 | struct LinkAnnotation 19 | { 20 | layout::AbsolutePagePos position; 21 | Size2d size; 22 | 23 | layout::AbsolutePagePos destination; 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /source/sap/config.h: -------------------------------------------------------------------------------- 1 | // config.h 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | namespace sap::config 11 | { 12 | struct MicrotypeConfig 13 | { 14 | util::hashset matched_fonts; 15 | 16 | bool enable_if_italic = false; 17 | std::vector> required_features; 18 | 19 | util::hashmap protrusions; 20 | }; 21 | 22 | StrErrorOr parseMicrotypeConfig(zst::str_view& contents); 23 | } 24 | 25 | namespace sap::paths 26 | { 27 | void addIncludeSearchPath(std::string path); 28 | void addLibrarySearchPath(std::string path); 29 | 30 | const std::vector& librarySearchPaths(); 31 | 32 | ErrorOr resolveInclude(const Location& loc, zst::str_view path); 33 | ErrorOr resolveLibrary(const Location& loc, zst::str_view path); 34 | } 35 | -------------------------------------------------------------------------------- /source/sap/document_settings.h: -------------------------------------------------------------------------------- 1 | // document_settings.h 2 | // Copyright (c) 2022, yuki / zhiayang 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include "sap/units.h" 8 | #include "sap/style.h" 9 | #include "sap/font_family.h" 10 | 11 | namespace sap 12 | { 13 | struct DocumentSettings 14 | { 15 | static Length DEFAULT_FONT_SIZE; 16 | 17 | struct Margins 18 | { 19 | std::optional top; 20 | std::optional bottom; 21 | std::optional left; 22 | std::optional right; 23 | }; 24 | 25 | std::optional serif_font_family; 26 | std::optional sans_font_family; 27 | std::optional mono_font_family; 28 | 29 | std::optional paper_size; 30 | std::optional margins; 31 | std::optional