• R/O
  • SSH

YSLib: Commit

The YSLib project - main repository


Commit MetaInfo

Revision92d04ed3856a19e6adf140ba7f641994f7b38a27 (tree)
Time2021-03-13 15:46:42
AuthorFrankHB <frankhb1989@gmai...>
CommiterFrankHB

Log Message

更新主分支版本: build 914 rev 12 。

Change Summary

Incremental Difference

diff -r 3db7f6204127 -r 92d04ed3856a YFramework/include/NPL/NPLA.h
--- a/YFramework/include/NPL/NPLA.h Fri Mar 05 12:30:06 2021 +0800
+++ b/YFramework/include/NPL/NPLA.h Sat Mar 13 14:46:42 2021 +0800
@@ -11,13 +11,13 @@
1111 /*! \file NPLA.h
1212 \ingroup NPL
1313 \brief NPLA 公共接口。
14-\version r8055
14+\version r8285
1515 \author FrankHB <frankhb1989@gmail.com>
1616 \since build 663
1717 \par 创建时间:
1818 2016-01-07 10:32:34 +0800
1919 \par 修改时间:
20- 2021-03-01 02:53 +0800
20+ 2021-03-12 18:01 +0800
2121 \par 文本编码:
2222 UTF-8
2323 \par 模块名称:
@@ -685,6 +685,13 @@
685685 //@}
686686
687687
688+/*! \defgroup LexicalCategory Lexical Category Support
689+\brief 词法类别支持。
690+\since build 914
691+*/
692+
693+//! \ingroup LexicalCategory
694+//@{
688695 /*!
689696 \brief 字面量类别。
690697 \since build 734
@@ -760,6 +767,7 @@
760767 ImplRet(CategorizeLexeme(id) == LexemeCategory::Symbol)
761768 //@}
762769 //@}
770+//@}
763771
764772
765773 /*!
@@ -796,6 +804,13 @@
796804 //@}
797805
798806
807+/*! \defgroup TermAccessAuxiliary Term Access Auxiliary API
808+\brief 辅助项访问接口。
809+\since build 914
810+*/
811+
812+//! \ingroup TermAccessAuxiliary
813+//@{
799814 /*!
800815 \brief 访问项的值作为记号。
801816 \return 通过访问项的值取得的记号的指针,或空指针表示无法取得名称。
@@ -932,6 +947,7 @@
932947 YB_ATTR_nodiscard YB_PURE inline
933948 PDefH(observer_ptr<const TokenValue>, TermToNamePtr, const TermNode& term)
934949 ImplRet(NPL::TryAccessTerm<TokenValue>(term))
950+//@}
935951
936952
937953 /*!
@@ -1220,6 +1236,20 @@
12201236 DefGetter(const ynothrow, const EnvironmentReference&, EnvironmentReference,
12211237 r_env)
12221238
1239+ /*
1240+ \brief 添加标签。
1241+ \since build 914
1242+ */
1243+ PDefH(void, AddTags, TermTags t) ynothrow
1244+ ImplExpr(tags |= t)
1245+
1246+ /*
1247+ \brief 移除标签。
1248+ \since build 914
1249+ */
1250+ PDefH(void, RemoveTags, TermTags t) ynothrow
1251+ ImplExpr(tags &= ~t)
1252+
12231253 /*!
12241254 \brief 取被引用的值。
12251255 \throw NPLException 配置 NPL_NPLA_CheckTermReferenceIndirection 时,环境无效。
@@ -1283,6 +1313,40 @@
12831313 ImplRet(ystdex::invoke_value_or(&TermReference::get,
12841314 NPL::TryAccessLeaf<const TermReference>(term), term))
12851315
1316+//! \since build 801
1317+//@{
1318+/*!
1319+\brief 项引用函数对象操作。
1320+\note 这是 NPL::ReferenceTerm 的函数对象形式。
1321+\sa ReferenceTerm
1322+*/
1323+struct ReferenceTermOp
1324+{
1325+ //! \since build 876
1326+ template<typename _type>
1327+ auto
1328+ operator()(_type&& term) const
1329+ ynoexcept_spec(NPL::ReferenceTerm(yforward(term)))
1330+ -> decltype(NPL::ReferenceTerm(yforward(term)))
1331+ {
1332+ return NPL::ReferenceTerm(yforward(term));
1333+ }
1334+};
1335+
1336+/*!
1337+\brief 包装一个非项引用的操作为 NPL::ReferenceTermOp 以支持项引用。
1338+\relates ReferenceTermOp
1339+\since build 802
1340+*/
1341+template<typename _func>
1342+YB_STATELESS auto
1343+ComposeReferencedTermOp(_func f)
1344+ -> yimpl(decltype(ystdex::compose_n(f, ReferenceTermOp())))
1345+{
1346+ return ystdex::compose_n(f, ReferenceTermOp());
1347+}
1348+//@}
1349+
12861350
12871351 //! \since build 855
12881352 //@{
@@ -1325,19 +1389,26 @@
13251389 //! \note 空指针指定解析结果中不存在引用值,被解析的项总是视为可转移的右值项。
13261390 template<typename _tPointer>
13271391 YB_ATTR_nodiscard YB_PURE inline auto
1328-IsMovable(_tPointer p) ynothrow
1329- -> decltype(!bool(p) || NPL::IsMovable(*p))
1392+IsMovable(_tPointer p) ynothrow -> decltype(!bool(p) || NPL::IsMovable(*p))
13301393 {
13311394 return !bool(p) || NPL::IsMovable(*p);
13321395 }
13331396 //@}
13341397
13351398
1399+/*! \defgroup TermReferenceAccess Term Reference Access API
1400+\brief 项引用访问接口。
1401+\since build 914
1402+*/
1403+
1404+//! \ingroup TermReferenceAccess
1405+//@{
13361406 //! \exception 异常中立:由项的值数据成员的持有者抛出。
13371407 //@{
13381408 //! \brief 尝试访问解析 TermReference 后的项的指定类型对象指针。
13391409 //@{
13401410 /*!
1411+\sa NPL::ReferenceTerm
13411412 \sa NPL::TryAccessLeaf
13421413 \since build 854
13431414 */
@@ -1520,120 +1591,10 @@
15201591 //@}
15211592 //@}
15221593 //@}
1523-
1524-
1525-//! \since build 801
1526-//@{
1527-/*!
1528-\brief 项引用函数对象操作。
1529-\note 这是 NPL::ReferenceTerm 的函数对象形式。
1530-\sa ReferenceTerm
1531-*/
1532-struct ReferenceTermOp
1533-{
1534- //! \since build 876
1535- template<typename _type>
1536- auto
1537- operator()(_type&& term) const
1538- ynoexcept_spec(NPL::ReferenceTerm(yforward(term)))
1539- -> decltype(NPL::ReferenceTerm(yforward(term)))
1540- {
1541- return NPL::ReferenceTerm(yforward(term));
1542- }
1543-};
1544-
1545-/*!
1546-\brief 包装一个非项引用的操作为 NPL::ReferenceTermOp 以支持项引用。
1547-\relates ReferenceTermOp
1548-\since build 802
1549-*/
1550-template<typename _func>
1551-YB_STATELESS auto
1552-ComposeReferencedTermOp(_func f)
1553- -> yimpl(decltype(ystdex::compose_n(f, ReferenceTermOp())))
1554-{
1555- return ystdex::compose_n(f, ReferenceTermOp());
1556-}
15571594 //@}
15581595
15591596
15601597 /*!
1561-\brief 规约状态:某个项上的一遍规约可能的中间结果。
1562-\note 实现的编码未指定,编码和规约时的判断性能相关。
1563-\since build 730
1564-*/
1565-enum class ReductionStatus : yimpl(size_t)
1566-{
1567- /*!
1568- \brief 部分规约:需要继续进行规约。
1569- \note 一般仅用于异步规约。
1570- \since build 841
1571- */
1572- Partial = yimpl(0x00),
1573- /*!
1574- \brief 中立规约:规约成功终止,且未指定是否需要保留子项。
1575- \note 一般用于不改变被规约项是否为空项的规约遍。
1576- \since build 869
1577- */
1578- Neutral = yimpl(0x01),
1579- /*!
1580- \brief 纯值规约:规约成功终止,且要求最终清理子项。
1581- \since build 841
1582- */
1583- Clean = yimpl(0x02),
1584- /*!
1585- \brief 非纯值规约:规约成功终止,且需要保留子项。
1586- \since build 757
1587- */
1588- Retained = yimpl(0x03),
1589- /*!
1590- \brief 已取得正规表示的规约:规约成功且已保证正规化,不指定是否需要保留子项。
1591- \since build 865
1592- */
1593- Regular = yimpl(Retained),
1594- /*!
1595- \brief 重规约。
1596- \since build 757
1597- */
1598- Retrying = yimpl(0x10)
1599-};
1600-
1601-/*!
1602-\brief 根据规约状态检查是否可继续规约。
1603-\relates ReductionStatus
1604-\sa YTraceDe
1605-\since build 734
1606-
1607-只根据参数确定结果:当且仅当参数规约成功时不视为可继续规约。
1608-在调试配置下,若发现不支持的状态视为不成功,输出警告。
1609-派生实现可使用类似的接口指定多个不同的状态。
1610-*/
1611-YB_ATTR_nodiscard YF_API YB_STATELESS bool
1612-CheckReducible(ReductionStatus);
1613-
1614-
1615-/*!
1616-\sa CheckReducible
1617-\since build 841
1618-*/
1619-template<typename _func, typename... _tParams>
1620-auto
1621-CheckedReduceWith(_func f, _tParams&&... args)
1622- -> decltype(ystdex::retry_on_cond(CheckReducible, f, yforward(args)...))
1623-{
1624- return ystdex::retry_on_cond(CheckReducible, f, yforward(args)...);
1625-}
1626-
1627-/*!
1628-\brief 按规约结果正规化项。
1629-\return 第二参数。
1630-\since build 841
1631-*/
1632-YF_API ReductionStatus
1633-RegularizeTerm(TermNode&, ReductionStatus) ynothrow;
1634-
1635-
1636-/*!
16371598 \brief 提升项:设置项的内容为参数指定的项或值。
16381599 \since build 805
16391600
@@ -1877,6 +1838,122 @@
18771838
18781839
18791840 /*!
1841+\brief 规约状态:某个项上的一遍规约可能的中间结果。
1842+\note 实现的编码未指定,编码和规约时的判断性能相关。
1843+\since build 730
1844+*/
1845+enum class ReductionStatus : yimpl(size_t)
1846+{
1847+ /*!
1848+ \brief 部分规约:需要继续进行规约。
1849+ \note 一般仅用于异步规约。
1850+ \since build 841
1851+ */
1852+ Partial = yimpl(0x00),
1853+ /*!
1854+ \brief 中立规约:规约成功终止,且未指定是否需要保留子项。
1855+ \note 一般用于不改变被规约项是否为空项的规约遍。
1856+ \since build 869
1857+ */
1858+ Neutral = yimpl(0x01),
1859+ /*!
1860+ \brief 纯值规约:规约成功终止,且要求最终清理子项。
1861+ \since build 841
1862+ */
1863+ Clean = yimpl(0x02),
1864+ /*!
1865+ \brief 非纯值规约:规约成功终止,且需要保留子项。
1866+ \since build 757
1867+ */
1868+ Retained = yimpl(0x03),
1869+ /*!
1870+ \brief 已取得正规表示的规约:规约成功且已保证正规化,不指定是否需要保留子项。
1871+ \since build 865
1872+ */
1873+ Regular = yimpl(Retained),
1874+ /*!
1875+ \brief 重规约。
1876+ \since build 757
1877+ */
1878+ Retrying = yimpl(0x10)
1879+};
1880+
1881+/*!
1882+\brief 根据规约状态检查是否可继续规约。
1883+\relates ReductionStatus
1884+\sa YTraceDe
1885+\since build 734
1886+
1887+只根据参数确定结果:当且仅当参数规约成功时不视为可继续规约。
1888+在调试配置下,若发现不支持的状态视为不成功,输出警告。
1889+派生实现可使用类似的接口指定多个不同的状态。
1890+*/
1891+YB_ATTR_nodiscard YF_API YB_STATELESS bool
1892+CheckReducible(ReductionStatus);
1893+
1894+/*!
1895+\sa CheckReducible
1896+\since build 841
1897+*/
1898+template<typename _func, typename... _tParams>
1899+auto
1900+CheckedReduceWith(_func f, _tParams&&... args)
1901+ -> decltype(ystdex::retry_on_cond(CheckReducible, f, yforward(args)...))
1902+{
1903+ return ystdex::retry_on_cond(CheckReducible, f, yforward(args)...);
1904+}
1905+
1906+/*!
1907+\brief 判断参数指定的规约结果在合并中是否可被覆盖。
1908+\warning 具体支持的值未指定,结果的稳定性不应被依赖。
1909+\since build 869
1910+*/
1911+YB_ATTR_nodiscard yconstfn YB_STATELESS
1912+ PDefH(bool, IsOverridableReductionResult, ReductionStatus r) ynothrow
1913+ ImplRet(
1914+ yimpl(r == ReductionStatus::Clean || r == ReductionStatus::Retained))
1915+
1916+/*!
1917+\since build 807
1918+\note 一般第一参数用于指定被合并的之前的规约结果,第二参数指定用于合并的结果。
1919+\return 合并后的规约结果。
1920+*/
1921+//@{
1922+/*!
1923+\brief 合并规约结果。
1924+
1925+若第二参数显式指定是否保留或不保留子项,则合并后的规约结果为第二参数;
1926+否则为第一参数。
1927+*/
1928+YB_ATTR_nodiscard yconstfn YB_STATELESS PDefH(ReductionStatus,
1929+ CombineReductionResult, ReductionStatus res, ReductionStatus r) ynothrow
1930+ ImplRet(IsOverridableReductionResult(r) ? r : res)
1931+
1932+/*!
1933+\brief 合并序列规约结果。
1934+\sa CheckReducible
1935+
1936+若第二参数指定可继续规约的项,则合并后的规约结果为第二参数;
1937+ 否则同 CombineReductionResult 。
1938+若可继续规约,则指定当前规约的项的规约已终止,不合并第一参数指定的结果。
1939+下一轮序列的规约可能使用或忽略合并后的结果。
1940+*/
1941+YB_ATTR_nodiscard YB_PURE inline
1942+ PDefH(ReductionStatus, CombineSequenceReductionResult, ReductionStatus res,
1943+ ReductionStatus r) ynothrow
1944+ ImplRet(CheckReducible(r) ? r : CombineReductionResult(res, r))
1945+//@}
1946+
1947+/*!
1948+\brief 按规约结果正规化项。
1949+\return 第二参数。
1950+\since build 841
1951+*/
1952+YF_API ReductionStatus
1953+RegularizeTerm(TermNode&, ReductionStatus) ynothrow;
1954+
1955+
1956+/*!
18801957 \pre 间接断言:参数为枝节点。
18811958 \note 不访问项的值数据成员。若需返回值正确地反映规约状态,需确保为空。
18821959 \return ReductionStatus::Retained 。
@@ -1989,48 +2066,6 @@
19892066 //@}
19902067
19912068
1992-/*!
1993-\brief 判断参数指定的规约结果在合并中是否可被覆盖。
1994-\warning 具体支持的值未指定,结果的稳定性不应被依赖。
1995-\since build 869
1996-*/
1997-YB_ATTR_nodiscard yconstfn YB_STATELESS PDefH(bool,
1998- IsOverridableReductionResult, ReductionStatus r) ynothrow
1999- ImplRet(yimpl(r == ReductionStatus::Clean
2000- || r == ReductionStatus::Retained))
2001-
2002-/*!
2003-\since build 807
2004-\note 一般第一参数用于指定被合并的之前的规约结果,第二参数指定用于合并的结果。
2005-\return 合并后的规约结果。
2006-*/
2007-//@{
2008-/*!
2009-\brief 合并规约结果。
2010-
2011-若第二参数显式指定是否保留或不保留子项,则合并后的规约结果为第二参数;
2012-否则为第一参数。
2013-*/
2014-YB_ATTR_nodiscard yconstfn YB_STATELESS PDefH(ReductionStatus,
2015- CombineReductionResult, ReductionStatus res, ReductionStatus r) ynothrow
2016- ImplRet(IsOverridableReductionResult(r) ? r : res)
2017-
2018-/*!
2019-\brief 合并序列规约结果。
2020-\sa CheckReducible
2021-
2022-若第二参数指定可继续规约的项,则合并后的规约结果为第二参数;
2023- 否则同 CombineReductionResult 。
2024-若可继续规约,则指定当前规约的项的规约已终止,不合并第一参数指定的结果。
2025-下一轮序列的规约可能使用或忽略合并后的结果。
2026-*/
2027-YB_ATTR_nodiscard YB_PURE inline
2028- PDefH(ReductionStatus, CombineSequenceReductionResult, ReductionStatus res,
2029- ReductionStatus r) ynothrow
2030- ImplRet(CheckReducible(r) ? r : CombineReductionResult(res, r))
2031-//@}
2032-
2033-
20342069 //! \warning 非虚析构。
20352070 //@{
20362071 /*!
@@ -2365,7 +2400,12 @@
23652400 YB_NORETURN static void
23662401 ThrowForInvalidValue(bool = {});
23672402
2368- //! \since build 746
2403+ /*!
2404+ \brief 交换。
2405+ \since build 746
2406+
2407+ 交换环境对象的绑定、算法和锚对象指针。
2408+ */
23692409 friend PDefH(void, swap, Environment& x, Environment& y) ynothrow
23702410 ImplExpr(swap(x.Bindings, y.Bindings), swap(x.Parent, y.Parent),
23712411 swap(x.p_anchor, y.p_anchor))
@@ -2995,6 +3035,12 @@
29953035 }
29963036
29973037
3038+/*! \defgroup BindingAccess Binding Access API
3039+\brief 绑定访问接口。
3040+\since build 914
3041+*/
3042+
3043+//! \ingroup BindingAccess
29983044 /*!
29993045 \brief 构造并向绑定目标添加叶节点值。
30003046 \return 对应的值在构造前不存在。
@@ -3118,6 +3164,7 @@
31183164 ResolveEnvironment(TermNode& term);
31193165 //@}
31203166 //@}
3167+//@}
31213168
31223169
31233170 /*!
@@ -3170,7 +3217,13 @@
31703217 }
31713218
31723219
3220+/*! \defgroup NPLDiagnostics NPL Diagnostics API
3221+\brief NPL 诊断接口。
3222+\since build 914
3223+*/
3224+
31733225 /*!
3226+\ingroup NPLDiagnostics
31743227 \brief 追踪记录 NPL 异常。
31753228 \since build 903
31763229 */
diff -r 3db7f6204127 -r 92d04ed3856a YFramework/include/NPL/NPLA1.h
--- a/YFramework/include/NPL/NPLA1.h Fri Mar 05 12:30:06 2021 +0800
+++ b/YFramework/include/NPL/NPLA1.h Sat Mar 13 14:46:42 2021 +0800
@@ -11,13 +11,13 @@
1111 /*! \file NPLA1.h
1212 \ingroup NPL
1313 \brief NPLA1 公共接口。
14-\version r8516
14+\version r8529
1515 \author FrankHB <frankhb1989@gmail.com>
1616 \since build 472
1717 \par 创建时间:
1818 2014-02-02 17:58:24 +0800
1919 \par 修改时间:
20- 2021-03-01 05:22 +0800
20+ 2021-03-12 18:01 +0800
2121 \par 文本编码:
2222 UTF-8
2323 \par 模块名称:
@@ -42,8 +42,8 @@
4242 // ystdex::type_info, SourceInformation, std::integral_constant, SourceName,
4343 // NPL::tuple, NPL::get, NPL::forward_as_tuple, ReaderState,
4444 // YSLib::allocate_shared;
45-#include YFM_YSLib_Core_YEvent // for YSLib::GHEvent, YSLib::GEvent,
46-// YSLib::GCombinerInvoker, YSLib::GDefaultLastValueInvoker;
45+#include YFM_YSLib_Core_YEvent // for YSLib::GHEvent, YSLib::GCombinerInvoker,
46+// YSLib::GDefaultLastValueInvoker;
4747 #include <ystdex/algorithm.hpp> // for ystdex::fast_any_of, ystdex::split;
4848 #include <ystdex/cast.hpp> // for ystdex::polymorphic_downcast;
4949 #include <ystdex/scope_guard.hpp> // for ystdex::guard;
@@ -610,16 +610,6 @@
610610 //@}
611611 //@}
612612
613-
614-/*!
615-\brief 规约至确定的未指定值。
616-\sa ValueToken::Unspecified
617-\since build 896
618-*/
619-inline PDefH(ReductionStatus, ReduceReturnUnspecified, TermNode& term) ynothrow
620- ImplRet((term.Value = ValueObject(std::allocator_arg, term.get_allocator(),
621- ValueToken::Unspecified)), ReductionStatus::Clean)
622-
623613 /*!
624614 \brief 规约到引用列表。
625615 \note 不访问项的值数据成员。若需返回值正确地反映规约状态,需确保为空。
@@ -638,6 +628,16 @@
638628
639629
640630 /*!
631+\brief 规约至确定的未指定值。
632+\sa ValueToken::Unspecified
633+\since build 896
634+*/
635+inline PDefH(ReductionStatus, ReduceReturnUnspecified, TermNode& term) ynothrow
636+ ImplRet((term.Value = ValueObject(std::allocator_arg, term.get_allocator(),
637+ ValueToken::Unspecified)), ReductionStatus::Clean)
638+
639+
640+/*!
641641 \brief 设置跟踪深度节点:调用规约时显示深度和上下文等信息。
642642 \note 主要用于调试。
643643 \sa ContextState::Guard
@@ -1417,6 +1417,8 @@
14171417 using SourcedTokenizer = GTokenizer<SourcedByteParser>;
14181418
14191419
1420+//! \ingroup NPLDiagnostics
1421+//@{
14201422 /*!
14211423 \pre 断言:参数的数据指针非空。
14221424 \note 不对名称参数指向的数据具有所有权。一般需要使用字符串字面量。
@@ -1569,6 +1571,7 @@
15691571 */
15701572 YF_API void
15711573 TraceBacktrace(ContextNode::ReducerSequence&, YSLib::Logger&);
1574+//@}
15721575
15731576
15741577 /*
diff -r 3db7f6204127 -r 92d04ed3856a YFramework/include/NPL/NPLA1Forms.h
--- a/YFramework/include/NPL/NPLA1Forms.h Fri Mar 05 12:30:06 2021 +0800
+++ b/YFramework/include/NPL/NPLA1Forms.h Sat Mar 13 14:46:42 2021 +0800
@@ -11,13 +11,13 @@
1111 /*! \file NPLA1Forms.h
1212 \ingroup NPL
1313 \brief NPLA1 语法形式。
14-\version r7844
14+\version r7923
1515 \author FrankHB <frankhb1989@gmail.com>
1616 \since build 882
1717 \par 创建时间:
1818 2020-02-15 11:19:21 +0800
1919 \par 修改时间:
20- 2021-03-01 18:47 +0800
20+ 2021-03-12 18:01 +0800
2121 \par 文本编码:
2222 UTF-8
2323 \par 模块名称:
@@ -50,7 +50,8 @@
5050 \pre 除非另行指定支持保存当前动作,若存在子项,关联的上下文中的尾动作为空。
5151 \pre 设置为处理器调用的操作在进入调用前应确保设置尾上下文等内部状态。
5252 \pre 作为操作数的项的子项不包含引用或非正规表示引入的对操作数内的子项的循环引用。
53-\pre 作为 NPLA1 规约函数的函数的参数符合规约函数约定。
53+\pre 作为 NPLA1 规约函数的函数的参数符合规约函数实现约定。
54+\pre 间接断言:作为规约函数第一参数指定的项是枝节点,以符合语法形式的实现要求。
5455 \sa ContextState
5556 \see %Documentation::NPL.
5657 \since build 732
@@ -75,13 +76,12 @@
7576 YB_ATTR_nodiscard YF_API YB_PURE bool
7677 IsSymbol(const string&) ynothrow;
7778
78-//! \since build 786
79-//@{
8079 /*!
8180 \brief 创建等于指定字符串值的记号值。
8281 \note 不检查值是否符合符号要求。若能构成符号,则名称为指定字符串。
8382 */
8483 //@{
84+//! \since build 786
8585 YB_ATTR_nodiscard YF_API YB_PURE TokenValue
8686 StringToSymbol(const string&);
8787 //! \since build 863
@@ -89,16 +89,17 @@
8989 StringToSymbol(string&&);
9090 //@}
9191
92-//! \brief 取符号对应的名称字符串。
92+/*!
93+\brief 取符号对应的名称字符串。
94+\since build 786
95+*/
9396 YB_ATTR_nodiscard YF_API YB_STATELESS const string&
9497 SymbolToString(const TokenValue&) ynothrow;
95-//@}
9698
9799
98100 /*!
99101 \pre 断言:项或容器对应枝节点。
100102 \sa AssertBranch
101-\since build 733
102103 */
103104 //@{
104105 /*!
@@ -389,9 +390,10 @@
389390 //@}
390391
391392
392-//! \since build 760
393-//@{
394-//! \sa Forms::CallBinary
393+/*!
394+\sa Forms::CallBinary
395+\since build 760
396+*/
395397 template<typename _func>
396398 struct BinaryExpansion
397399 : private ystdex::equality_comparable<BinaryExpansion<_func>>
@@ -453,7 +455,6 @@
453455 }
454456 };
455457 //@}
456-//@}
457458
458459
459460 /*!
@@ -946,8 +947,6 @@
946947 //@}
947948
948949
949-//! \pre 间接断言:第一参数指定的项是枝节点。
950-//@{
951950 /*!
952951 \brief 创建空环境。
953952 \exception NPLException 异常中立:由 Environment 的构造函数抛出。
@@ -1490,6 +1489,84 @@
14901489 YF_API ReductionStatus
14911490 Append(TermNode&, ContextNode&);
14921491
1492+//! \since build 914
1493+//@{
1494+/*!
1495+\brief 以指定应用子在指定列表中选取并合并内容为新的列表。
1496+
1497+对参数列表 <pre>(&l &extr)</pre> ,结果同求值:
1498+<pre>accr l null? () ($lambda% (&l) forward-first% extr (expire l))
1499+ rest% cons% </pre>
1500+
1501+参考调用文法:
1502+<pre>list-extract% \<list> \<applicative></pre>
1503+*/
1504+YF_API ReductionStatus
1505+ListExtract(TermNode&, ContextNode&);
1506+
1507+/*!
1508+\brief 以 First 在指定列表中选取并合并内容为新的列表。
1509+\sa First
1510+
1511+参考调用文法:
1512+<pre>list-extract-first \<list></pre>
1513+*/
1514+YF_API ReductionStatus
1515+ListExtractFirst(TermNode&, ContextNode&);
1516+
1517+/*!
1518+\brief 以 RestFwd 在指定列表中选取并合并内容为新的列表。
1519+\sa RestFwd
1520+
1521+参考调用文法:
1522+<pre>list-extract-rest% \<list></pre>
1523+*/
1524+YF_API ReductionStatus
1525+ListExtractRestFwd(TermNode&, ContextNode&);
1526+
1527+//! \brief 局部绑定求值。
1528+//@{
1529+/*
1530+不保留结果中的引用值。
1531+
1532+参考调用文法:
1533+<pre>$let \<bindings> \<body></pre>
1534+*/
1535+YF_API ReductionStatus
1536+Let(TermNode&, ContextNode&);
1537+
1538+/*
1539+保留结果中的引用值。
1540+
1541+参考调用文法:
1542+<pre>$let% \<bindings> \<body></pre>
1543+*/
1544+YF_API ReductionStatus
1545+LetRef(TermNode&, ContextNode&);
1546+//@}
1547+
1548+//! \brief 指定静态环境并局部绑定求值。
1549+//@{
1550+/*
1551+不保留结果中的引用值。
1552+
1553+参考调用文法:
1554+<pre>$let/e \<parent> \<bindings> \<body></pre>
1555+*/
1556+YF_API ReductionStatus
1557+LetWithEnvironment(TermNode&, ContextNode&);
1558+
1559+/*
1560+保留结果中的引用值。
1561+
1562+参考调用文法:
1563+<pre>$let/e% \<parent> \<bindings> \<body></pre>
1564+*/
1565+YF_API ReductionStatus
1566+LetWithEnvironmentRef(TermNode&, ContextNode&);
1567+//@}
1568+//@}
1569+
14931570
14941571 /*!
14951572 \brief 调用 UTF-8 字符串的系统命令,并保存 int 类型的结果到项的值中。
@@ -1501,7 +1578,6 @@
15011578 */
15021579 YF_API void
15031580 CallSystem(TermNode&);
1504-//@}
15051581
15061582 } // namespace Forms;
15071583
diff -r 3db7f6204127 -r 92d04ed3856a YFramework/include/NPL/SContext.h
--- a/YFramework/include/NPL/SContext.h Fri Mar 05 12:30:06 2021 +0800
+++ b/YFramework/include/NPL/SContext.h Sat Mar 13 14:46:42 2021 +0800
@@ -11,13 +11,13 @@
1111 /*! \file SContext.h
1212 \ingroup NPL
1313 \brief S 表达式上下文。
14-\version r3853
14+\version r3866
1515 \author FrankHB <frankhb1989@gmail.com>
1616 \since build 304
1717 \par 创建时间:
1818 2012-08-03 19:55:41 +0800
1919 \par 修改时间:
20- 2021-03-01 22:45 +0800
20+ 2021-03-12 18:01 +0800
2121 \par 文本编码:
2222 UTF-8
2323 \par 模块名称:
@@ -340,7 +340,8 @@
340340 PDefH(void, SetContent, const TermNode& term)
341341 ImplExpr(SetContent(term.container, term.Value))
342342 PDefH(void, SetContent, TermNode&& term)
343- ImplExpr(SetContent(std::move(term.container), std::move(term.Value)))
343+ ImplExpr(SetContent(std::move(term.container), std::move(term.Value)),
344+ Tags = term.Tags)
344345 //@}
345346
346347 //! \since build 853
@@ -365,6 +366,7 @@
365366 con.emplace(position, NoContainer, yforward(args)...);
366367 }
367368
369+ //! \note 不访问 Tags 。
368370 PDefH(void, Clear, ) ynothrow
369371 ImplExpr(Value.Clear(), ClearContainer())
370372
@@ -715,6 +717,17 @@
715717 dst.SetContent(std::move(con), std::move(vo));
716718 }
717719 //@}
720+
721+/*!
722+\brief 检查项节点是否具有指定的值。
723+\since build 753
724+*/
725+template<typename _type>
726+YB_ATTR_nodiscard YB_PURE inline bool
727+HasValue(const TermNode& term, const _type& x)
728+{
729+ return term.Value == x;
730+}
718731 //@}
719732
720733 /*!
@@ -739,18 +752,6 @@
739752 }
740753
741754
742-/*!
743-\brief 检查项节点是否具有指定的值。
744-\since build 753
745-*/
746-template<typename _type>
747-YB_ATTR_nodiscard YB_PURE inline bool
748-HasValue(const TermNode& term, const _type& x)
749-{
750- return term.Value == x;
751-}
752-
753-
754755 //! \since build 891
755756 //@{
756757 /*!
diff -r 3db7f6204127 -r 92d04ed3856a YFramework/source/NPL/Dependency.cpp
--- a/YFramework/source/NPL/Dependency.cpp Fri Mar 05 12:30:06 2021 +0800
+++ b/YFramework/source/NPL/Dependency.cpp Sat Mar 13 14:46:42 2021 +0800
@@ -11,13 +11,13 @@
1111 /*! \file Dependency.cpp
1212 \ingroup NPL
1313 \brief 依赖管理。
14-\version r4355
14+\version r4480
1515 \author FrankHB <frankhb1989@gmail.com>
1616 \since build 623
1717 \par 创建时间:
1818 2015-08-09 22:14:45 +0800
1919 \par 修改时间:
20- 2021-03-05 01:14 +0800
20+ 2021-03-13 01:42 +0800
2121 \par 文本编码:
2222 UTF-8
2323 \par 模块名称:
@@ -37,7 +37,7 @@
3737 // IsReferenceTerm, IsBoundLValueTerm, IsUncollapsedTerm, IsUniqueTerm,
3838 // IsModifiableTerm, IsTemporaryTerm, NPL::TryAccessLeaf, LiftTermRef,
3939 // NPL::SetContentWith, Forms::CallRawUnary, LiftTerm, LiftOtherOrCopy,
40-// std::placeholders, LiftTermValueOrCopy, ystdex::bind1, MoveResolved,
40+// ystdex::bind1, std::placeholders, LiftTermValueOrCopy, MoveResolved,
4141 // ResolveIdentifier, ReduceToReferenceList, NPL::IsMovable, LiftTermOrCopy,
4242 // IsBranchedList, AccessFirstSubterm, ReferenceTerm,
4343 // ThrowInsufficientTermsError, ystdex::exchange, ystdex::plus,
@@ -299,6 +299,18 @@
299299 }, term);
300300 }
301301
302+//! \since build 914
303+YB_ATTR_nodiscard ReductionStatus
304+Qualify(TermNode& term, TermTags tag_add)
305+{
306+ return Forms::CallRawUnary([&](TermNode& tm){
307+ if(const auto p = NPL::TryAccessLeaf<TermReference>(tm))
308+ p->AddTags(tag_add);
309+ LiftTerm(term, tm);
310+ return ReductionStatus::Retained;
311+ }, term);
312+}
313+
302314 //! \since build 794
303315 //@{
304316 void
@@ -465,14 +477,10 @@
465477 return ReductionStatus::Retained;
466478 }, term);
467479 });
468- RegisterStrict(ctx, "expire", [](TermNode& term){
469- return CallRawUnary([&](TermNode& tm){
470- if(const auto p = NPL::TryAccessLeaf<TermReference>(tm))
471- p->SetTags(p->GetTags() | TermTags::Unique);
472- LiftTerm(term, tm);
473- return ReductionStatus::Retained;
474- }, term);
475- });
480+ RegisterStrict(ctx, "as-const",
481+ ystdex::bind1(Qualify, TermTags::Nonmodifying));
482+ RegisterStrict(ctx, "expire",
483+ ystdex::bind1(Qualify, TermTags::Unique));
476484 RegisterStrict(ctx, "move!", std::bind(DoMoveOrTransfer,
477485 std::ref(LiftOtherOrCopy), std::placeholders::_1));
478486 RegisterStrict(ctx, "transfer!", std::bind(DoMoveOrTransfer,
@@ -747,6 +755,13 @@
747755 });
748756 RegisterStrict(renv, "list-concat", ListConcat);
749757 RegisterStrict(renv, "append", Append);
758+ RegisterStrict(renv, "list-extract%", ListExtract);
759+ RegisterStrict(renv, "list-extract-first", ListExtractFirst);
760+ RegisterStrict(renv, "list-extract-rest%", ListExtractRestFwd);
761+ RegisterForm(renv, "$let", Let);
762+ RegisterForm(renv, "$let%", LetRef);
763+ RegisterForm(renv, "$let/e", LetWithEnvironment);
764+ RegisterForm(renv, "$let/e%", LetWithEnvironmentRef);
750765 RegisterStrict(renv, "make-standard-environment", ystdex::bind1(
751766 // NOTE: The weak reference of the ground environment is saved and it
752767 // shall not be moved after being called.
@@ -960,6 +975,11 @@
960975 $defl! first-null? (&l) null? (first l);
961976 $defl! list-concat (&x &y) foldr1 cons% (forward! y) (forward! x);
962977 $defl! append (.&ls) foldr1 list-concat () (move! ls);
978+ $defw%! list-extract% (&l &extr) d
979+ accr l null? () ($lambda% (&l)
980+ apply forward-first% (list% extr (expire l)) d) rest% cons%;
981+ $defl%! list-extract-first (&l) list-extract% l first;
982+ $defl%! list-extract-rest% (&l) list-extract% l rest%;
963983 $defv! $defw! (&f &formals &ef .&body) d
964984 eval (list $set! d f wrap (list* $vau formals ef (move! body))) d;
965985 $defw! derive-current-environment (.&envs) d
@@ -1091,32 +1111,94 @@
10911111 $defl%! assv (&x &alist) $cond ((null? alist) ())
10921112 ((eqv? x (first& (first& alist))) first alist)
10931113 (#t assv (forward! x) (rest% alist));
1094- $defv%! $let (&bindings .&body) d
1095- eval% (list* () (list* $lambda (map1 firstv bindings)
1096- (list (move! body))) (map1 rest% bindings)) d;
1097- $defv%! $let% (&bindings .&body) d
1098- eval% (list* () (list* $lambda% (map1 firstv bindings)
1099- (list (move! body))) (map1 rest% bindings)) d;
1100- $defv%! $let/e (&e &bindings .&body) d
1101- eval% (list* () (list* $lambda/e e (map1 firstv bindings)
1102- (list (move! body))) (map1 rest% bindings)) d;
1103- $defv%! $let/e% (&e &bindings .&body) d
1104- eval% (list* () (list* $lambda/e% e (map1 firstv bindings)
1105- (list (move! body))) (map1 rest% bindings)) d;
1106- $defv%! $let* (&bindings .&body) d
1107- eval% ($if (null? bindings) (list* $let () (move! body))
1108- (list $let (list (firstv bindings))
1109- (list* $let* (rest% bindings) (move! body)))) d;
1110- $defv%! $let*% (&bindings .&body) d
1111- eval% ($if (null? bindings) (list* $let* () (move! body))
1112- (list $let% (list (first bindings))
1113- (list* $let*% (rest% bindings) (move! body)))) d;
1114- $defv%! $letrec (&bindings .&body) d
1115- eval% (list $let () $sequence (list $def! (map1 firstv bindings)
1116- (list* () list (map1 rest% bindings))) (move! body)) d;
1117- $defv%! $letrec% (&bindings .&body) d
1118- eval% (list $let% () $sequence (list $def! (map1 firstv bindings)
1119- (list* () list (map1 rest% bindings))) (move! body)) d;
1114+ )NPL");
1115+#if NPL_Impl_NPLA1_Native_Forms
1116+ context.Perform(R"NPL(
1117+ $def! ($let* $let*% $letrec $letrec%)
1118+ ($lambda (&ce)
1119+ (
1120+ $def! mods () ($lambda/e ce ()
1121+ (
1122+ $defv%! $lqual (&ls) d
1123+ $if (eval (list $lvalue-identifier? ls) d)
1124+ (eval% (list as-const ls) d)
1125+ (($lambda% (&l) map1 expire (rlist l)) (eval% ls d));
1126+ $defv%! $lqual* (&x) d
1127+ ($if (eval (list $lvalue-identifier? x) d) as-const expire)
1128+ (eval% x d);
1129+ $defl%! mk-let* ($let $let* &bindings &body)
1130+ $if (null? bindings) (list* $let () (move! body))
1131+ (list $let (list (first% ($lqual* bindings)))
1132+ (list* $let* (rest% ($lqual* bindings)) (move! body)));
1133+ $defl%! mk-letrec ($let &bindings &body)
1134+ list $let% () $sequence (list $def! (list-extract-first
1135+ bindings) (list* () list (list-extract-rest% bindings)))
1136+ (move! body);
1137+ () lock-current-environment
1138+ ));
1139+ $defv/e%! $let* mods (&bindings .&body) d
1140+ eval% (mk-let* $let $let* ($lqual* bindings) (move! body)) d;
1141+ $defv/e%! $let*% mods (&bindings .&body) d
1142+ eval% (mk-let* $let% $let*% ($lqual* bindings) (move! body)) d;
1143+ $defv/e%! $letrec mods (&bindings .&body) d
1144+ eval% (mk-letrec $let ($lqual bindings) (move! body)) d;
1145+ $defv/e%! $letrec% mods (&bindings .&body) d
1146+ eval% (mk-letrec $let% ($lqual bindings) (move! body)) d;
1147+ map1 move! (list% $let* $let*% $letrec $letrec%)
1148+ )) (() get-current-environment);
1149+ )NPL");
1150+#else
1151+ context.Perform(R"NPL(
1152+ $def! ($let $let% $let/e $let/e% $let* $let*% $letrec $letrec%)
1153+ ($lambda (&ce)
1154+ (
1155+ $def! mods () ($lambda/e ce ()
1156+ (
1157+ $defv%! $lqual (&ls) d
1158+ $if (eval (list $lvalue-identifier? ls) d)
1159+ (eval% (list as-const ls) d)
1160+ (($lambda% (&l) map1 expire (rlist l)) (eval% ls d));
1161+ $defv%! $lqual* (&x) d
1162+ ($if (eval (list $lvalue-identifier? x) d) as-const expire)
1163+ (eval% x d);
1164+ $defl%! mk-let ($ctor &bindings &body)
1165+ list* () (list* $ctor (list-extract-first bindings)
1166+ (list (move! body))) (list-extract-rest% bindings);
1167+ $defl%! mk-let/e ($ctor &e &bindings &body)
1168+ list* () (list* $ctor e (list-extract-first bindings)
1169+ (list (move! body))) (list-extract-rest% bindings);
1170+ $defl%! mk-let* ($let $let* &bindings &body)
1171+ $if (null? bindings) (list* $let () (move! body))
1172+ (list $let (list (first% ($lqual* bindings)))
1173+ (list* $let* (rest% ($lqual* bindings)) (move! body)));
1174+ $defl%! mk-letrec ($let &bindings &body)
1175+ list $let% () $sequence (list $def! (list-extract-first
1176+ bindings) (list* () list (list-extract-rest% bindings)))
1177+ (move! body);
1178+ () lock-current-environment
1179+ ));
1180+ $defv/e%! $let mods (&bindings .&body) d
1181+ eval% (mk-let $lambda ($lqual bindings) (move! body)) d;
1182+ $defv/e%! $let% mods (&bindings .&body) d
1183+ eval% (mk-let $lambda% ($lqual bindings) (move! body)) d;
1184+ $defv/e%! $let/e mods (&e &bindings .&body) d
1185+ eval% (mk-let/e $lambda/e e ($lqual bindings) (move! body)) d;
1186+ $defv/e%! $let/e% mods (&e &bindings .&body) d
1187+ eval% (mk-let/e $lambda/e% e ($lqual bindings) (move! body)) d;
1188+ $defv/e%! $let* mods (&bindings .&body) d
1189+ eval% (mk-let* $let $let* ($lqual* bindings) (move! body)) d;
1190+ $defv/e%! $let*% mods (&bindings .&body) d
1191+ eval% (mk-let* $let% $let*% ($lqual* bindings) (move! body)) d;
1192+ $defv/e%! $letrec mods (&bindings .&body) d
1193+ eval% (mk-letrec $let ($lqual bindings) (move! body)) d;
1194+ $defv/e%! $letrec% mods (&bindings .&body) d
1195+ eval% (mk-letrec $let% ($lqual bindings) (move! body)) d;
1196+ map1 move!
1197+ (list% $let $let% $let/e $let/e% $let* $let*% $letrec $letrec%)
1198+ )) (() get-current-environment);
1199+ )NPL");
1200+#endif
1201+ context.Perform(R"NPL(
11201202 $defv! $as-environment (.&body) d
11211203 eval (list $let () (list $sequence (move! body)
11221204 (list () lock-current-environment))) d;
@@ -1145,11 +1227,6 @@
11451227 (symbols->imports symbols)) (eval e d);
11461228 $defl! nonfoldable? (&l)
11471229 $if (null? l) #f ($if (first-null? l) #t (nonfoldable? (rest& l)));
1148- $defl%! list-extract (&l &extr)
1149- accr l null? ()
1150- ($lambda% (&l) forward-first% extr (expire l)) rest% cons%;
1151- $defl%! list-extract-first (&l) list-extract l first;
1152- $defl%! list-extract-rest% (&l) list-extract l rest%;
11531230 $defl! list-push-front! (&l &x)
11541231 assign! l (cons% (forward! x) (move! l));
11551232 $defw%! map-reverse (&appv .&ls) d
diff -r 3db7f6204127 -r 92d04ed3856a YFramework/source/NPL/NPLA.cpp
--- a/YFramework/source/NPL/NPLA.cpp Fri Mar 05 12:30:06 2021 +0800
+++ b/YFramework/source/NPL/NPLA.cpp Sat Mar 13 14:46:42 2021 +0800
@@ -11,13 +11,13 @@
1111 /*! \file NPLA.cpp
1212 \ingroup NPL
1313 \brief NPLA 公共接口。
14-\version r3490
14+\version r3520
1515 \author FrankHB <frankhb1989@gmail.com>
1616 \since build 663
1717 \par 创建时间:
1818 2016-01-07 10:32:45 +0800
1919 \par 修改时间:
20- 2021-03-01 22:47 +0800
20+ 2021-03-06 23:23 +0800
2121 \par 文本编码:
2222 UTF-8
2323 \par 模块名称:
@@ -699,37 +699,6 @@
699699 }
700700
701701
702-bool
703-CheckReducible(ReductionStatus status)
704-{
705- switch(status)
706- {
707- case ReductionStatus::Partial:
708- case ReductionStatus::Retrying:
709- return true;
710- default:
711-#if NPL_Impl_NPLA_CheckStatus
712- // NOTE: Keep away assertions so client code can be diagnosed.
713- if(YB_UNLIKELY(status != ReductionStatus::Neutral
714- && status != ReductionStatus::Clean
715- && status != ReductionStatus::Retained))
716- YTraceDe(Warning, "Unexpected status found.");
717-#endif
718- break;
719- }
720- return {};
721-}
722-
723-ReductionStatus
724-RegularizeTerm(TermNode& term, ReductionStatus status) ynothrow
725-{
726- // NOTE: Cleanup if and only if necessary.
727- if(status == ReductionStatus::Clean)
728- term.ClearContainer();
729- return status;
730-}
731-
732-
733702 void
734703 LiftOtherOrCopy(TermNode& term, TermNode& tm, bool move)
735704 {
@@ -857,6 +826,37 @@
857826 }
858827
859828
829+bool
830+CheckReducible(ReductionStatus status)
831+{
832+ switch(status)
833+ {
834+ case ReductionStatus::Partial:
835+ case ReductionStatus::Retrying:
836+ return true;
837+ default:
838+#if NPL_Impl_NPLA_CheckStatus
839+ // NOTE: Keep away assertions so client code can be diagnosed.
840+ if(YB_UNLIKELY(status != ReductionStatus::Neutral
841+ && status != ReductionStatus::Clean
842+ && status != ReductionStatus::Retained))
843+ YTraceDe(Warning, "Unexpected status found.");
844+#endif
845+ break;
846+ }
847+ return {};
848+}
849+
850+ReductionStatus
851+RegularizeTerm(TermNode& term, ReductionStatus status) ynothrow
852+{
853+ // NOTE: Cleanup if and only if necessary.
854+ if(status == ReductionStatus::Clean)
855+ term.ClearContainer();
856+ return status;
857+}
858+
859+
860860 ReductionStatus
861861 ReduceBranchToList(TermNode& term) ynothrowv
862862 {
diff -r 3db7f6204127 -r 92d04ed3856a YFramework/source/NPL/NPLA1Forms.cpp
--- a/YFramework/source/NPL/NPLA1Forms.cpp Fri Mar 05 12:30:06 2021 +0800
+++ b/YFramework/source/NPL/NPLA1Forms.cpp Sat Mar 13 14:46:42 2021 +0800
@@ -11,13 +11,13 @@
1111 /*! \file NPLA1Forms.cpp
1212 \ingroup NPL
1313 \brief NPLA1 语法形式。
14-\version r21015
14+\version r21603
1515 \author FrankHB <frankhb1989@gmail.com>
1616 \since build 882
1717 \par 创建时间:
1818 2014-02-15 11:19:51 +0800
1919 \par 修改时间:
20- 2021-03-03 03:15 +0800
20+ 2021-03-13 14:15 +0800
2121 \par 文本编码:
2222 UTF-8
2323 \par 模块名称:
@@ -103,9 +103,9 @@
103103 YB_ATTR_nodiscard TNIter
104104 CondClauseCheck(TermNode& clause)
105105 {
106- if(YB_UNLIKELY(clause.empty()))
107- ThrowInsufficientTermsError(clause, {});
108- return clause.begin();
106+ if(IsBranch(clause))
107+ return clause.begin();
108+ ThrowInsufficientTermsError(clause, {});
109109 }
110110
111111 YB_ATTR_nodiscard bool
@@ -654,9 +654,9 @@
654654 // NOTE: On %NPL_Impl_NPLA1_Enable_TCO, this assumes %term is same to the
655655 // current term in %TCOAction, which is initialized by %CombinerReturnThunk
656656 // in NPLA1.cpp.
657- return TailCall::RelayNextGuardedProbe(ctx, term, EnvironmentGuard(ctx,
658- ctx.SwitchEnvironment(std::move(p_env))), !no_lift,
659- std::ref(ContextState::Access(ctx).ReduceOnce));
657+ return TailCall::RelayNextGuardedProbe(ctx, term,
658+ EnvironmentGuard(ctx, ctx.SwitchEnvironment(std::move(p_env))),
659+ !no_lift, std::ref(ContextState::Access(ctx).ReduceOnce));
660660 }
661661
662662 //! \since build 822
@@ -996,6 +996,14 @@
996996 };
997997
998998
999+//! \since build 914
1000+YB_FLATTEN void
1001+MoveValueListSplice(TermNode& term, TermNode& nd)
1002+{
1003+ // XXX: No cyclic reference check.
1004+ term.GetContainerRef().splice(term.end(), std::move(nd.GetContainerRef()));
1005+}
1006+
9991007 //! \since build 874
10001008 YB_FLATTEN void
10011009 MakeValueListOrMove(TermNode& term, TermNode& nd,
@@ -1004,11 +1012,7 @@
10041012 MakeValueOrMove(p_ref, [&]{
10051013 for(const auto& sub : nd)
10061014 term.Add(sub);
1007- }, [&]{
1008- // XXX: No cyclic reference check.
1009- term.GetContainerRef().splice(term.end(),
1010- std::move(nd.GetContainerRef()));
1011- });
1015+ }, std::bind(MoveValueListSplice, std::ref(term), std::ref(nd)));
10121016 }
10131017
10141018 //! \since build 859
@@ -1057,6 +1061,36 @@
10571061 }
10581062 //@}
10591063
1064+//! \since build 914
1065+//@{
1066+// NOTE: The tag is used for different call sites with the call to
1067+// %A1::NameTypedReducerHandler.
1068+enum ConsTag
1069+{
1070+ Map1Appv,
1071+ ListConcatCons,
1072+ ListExtractCons
1073+};
1074+
1075+template<ConsTag>
1076+struct ConsMoveSubNextRV final
1077+{
1078+ TermNode& Term;
1079+
1080+ YB_ATTR_nodiscard YB_FLATTEN ReductionStatus
1081+ operator()() const
1082+ {
1083+ const auto i(std::next(Term.begin()));
1084+ auto& nd_y(NPL::Deref(i));
1085+
1086+ YAssert(IsList(nd_y), "Expected a list prvalue for cons.");
1087+ MoveValueListSplice(Term, nd_y);
1088+ Term.erase(i);
1089+ return ReductionStatus::Retained;
1090+ }
1091+};
1092+//@}
1093+
10601094
10611095 //! \since build 859
10621096 //@{
@@ -1310,6 +1344,56 @@
13101344 }, term);
13111345 }
13121346
1347+//! \since build 914
1348+ReductionStatus
1349+ReduceToFirst(TermNode& term, TermNode& tm, ResolvedTermReferencePtr p_ref)
1350+{
1351+#if true
1352+ // XXX: This is verbose but likely more efficient with %YB_FLATTEN.
1353+ const bool list_not_move(p_ref && p_ref->IsReferencedLValue());
1354+
1355+ if(const auto p = NPL::TryAccessLeaf<const TermReference>(tm))
1356+ {
1357+ if(list_not_move)
1358+ {
1359+ // NOTE: As %LiftOtherOrCopy.
1360+ term.CopyContent(tm);
1361+ return ReductionStatus::Retained;
1362+ }
1363+ if(!p->IsReferencedLValue())
1364+ {
1365+ LiftMovedOther(term, *p, p->IsMovable());
1366+ return ReductionStatus::Retained;
1367+ }
1368+ }
1369+ else if(list_not_move)
1370+ return ReduceToReferenceAt(term, tm, p_ref);
1371+ // XXX: Term tags are currently not respected in prvalues.
1372+ LiftOtherOrCopy(term, tm, !p_ref || p_ref->IsModifiable());
1373+ return ReductionStatus::Retained;
1374+#else
1375+ // NOTE: For exposition only. The optimized implemenation shall be
1376+ // equivalent to this.
1377+ // XXX: This should be safe, since the parent list is guaranteed an
1378+ // lvalue by the false result of the call to %NPL::IsMovable.
1379+ if(!(p_ref && p_ref->IsReferencedLValue()))
1380+ {
1381+ if(const auto p = NPL::TryAccessLeaf<const TermReference>(tm))
1382+ {
1383+ if(!p->IsReferencedLValue())
1384+ {
1385+ LiftMovedOther(term, *p, p->IsMovable());
1386+ return ReductionStatus::Retained;
1387+ }
1388+ }
1389+ // XXX: Term tags are currently not respected in prvalues.
1390+ LiftOtherOrCopy(term, tm, !p_ref || p_ref->IsModifiable());
1391+ return ReductionStatus::Retained;
1392+ }
1393+ return ReduceToReference(term, tm, p_ref);
1394+#endif
1395+}
1396+
13131397 //! \since build 859
13141398 void
13151399 CheckResolvedListReference(TermNode& nd, bool has_ref)
@@ -1351,32 +1435,41 @@
13511435 LiftOtherOrCopy(term, tm, !has_ref);
13521436 }
13531437
1438+//! \since build 914
1439+template<typename _fLift, typename _fInsert>
1440+YB_FLATTEN ReductionStatus
1441+ReduceToRestOrVal(TermNode& term, TermNode& nd, ResolvedTermReferencePtr p_ref,
1442+ _fLift lift, _fInsert insert)
1443+{
1444+ if(IsBranchedList(nd))
1445+ {
1446+ TermNode::Container con(term.get_allocator());
1447+
1448+ auto first(nd.begin());
1449+ const auto last(nd.end());
1450+
1451+ ++first;
1452+ if(!p_ref || p_ref->IsMovable() || p_ref->IsTemporary())
1453+ {
1454+ lift(nd);
1455+ con.splice(con.end(), nd.GetContainerRef(), first, last);
1456+ }
1457+ else
1458+ for(; first != last; ++first)
1459+ insert(con.emplace_back(), *first, p_ref);
1460+ con.swap(term.GetContainerRef());
1461+ return ReductionStatus::Retained;
1462+ }
1463+ ThrowInsufficientTermsError(nd, p_ref);
1464+}
1465+
13541466 //! \since build 910
13551467 template<typename _fLift, typename _fInsert>
13561468 YB_FLATTEN ReductionStatus
13571469 RestOrVal(TermNode& term, _fLift lift, _fInsert insert)
13581470 {
13591471 return CallResolvedUnary([&](TermNode& nd, ResolvedTermReferencePtr p_ref){
1360- if(IsBranchedList(nd))
1361- {
1362- TermNode::Container con(term.get_allocator());
1363-
1364- auto first(nd.begin());
1365- const auto last(nd.end());
1366-
1367- ++first;
1368- if(!p_ref || p_ref->IsMovable() || p_ref->IsTemporary())
1369- {
1370- lift(nd);
1371- con.splice(con.end(), nd.GetContainerRef(), first, last);
1372- }
1373- else
1374- for(; first != last; ++first)
1375- insert(con.emplace_back(), *first, p_ref);
1376- con.swap(term.GetContainerRef());
1377- return ReductionStatus::Retained;
1378- }
1379- ThrowInsufficientTermsError(nd, p_ref);
1472+ return ReduceToRestOrVal(term, nd, p_ref, lift, insert);
13801473 }, term);
13811474 }
13821475
@@ -1426,8 +1519,9 @@
14261519 if(IsList(nd_y))
14271520 {
14281521 const auto a(nd_x.get_allocator());
1429- TermNode nd_new(std::allocator_arg, a, {TermNode(a)});
1430-
1522+ TermNode nd_new(a);
1523+
1524+ nd_new.emplace();
14311525 MakeValueListOrMove(nd_new, nd_y, p_ref_y);
14321526 lift(nd_new);
14331527 // XXX: The order is significant.
@@ -1519,6 +1613,8 @@
15191613 auto eformal(CheckEnvFormal(NPL::Deref(++i)));
15201614
15211615 term.erase(term.begin(), ++i);
1616+ // XXX: Allocators are not used on %FormContextHandler for performance in
1617+ // most cases.
15221618 return FormContextHandler(VauHandler(std::move(eformal), std::move(formals),
15231619 std::move(p_env), owning, term, no_lift));
15241620 }
@@ -1532,6 +1628,7 @@
15321628 auto eformal(CheckEnvFormal(NPL::Deref(++i)));
15331629
15341630 term.erase(term.begin(), ++i);
1631+ // XXX: Ditto.
15351632 return FormContextHandler(VauHandler(0, std::move(eformal),
15361633 std::move(formals), std::move(parent), term, no_lift));
15371634 }
@@ -1618,6 +1715,8 @@
16181715 WrapN(TermNode& term, ResolvedTermReferencePtr p_ref,
16191716 FormContextHandler& fch, size_t n)
16201717 {
1718+ // XXX: Allocators are not used on %FormContextHandler for performance in
1719+ // most cases.
16211720 return WrapH(term, MakeValueOrMove(p_ref, [&]{
16221721 return FormContextHandler(fch.Handler, n);
16231722 }, [&]{
@@ -1633,6 +1732,7 @@
16331732 {
16341733 if(p_ref)
16351734 return ReduceForCombinerRef(term, *p_ref, fch.Handler, n);
1735+ // XXX: Ditto.
16361736 term.Value = ContextHandler(std::allocator_arg, term.get_allocator(),
16371737 FormContextHandler(std::move(fch.Handler), n));
16381738 return ReductionStatus::Clean;
@@ -1944,10 +2044,10 @@
19442044 // which can be a non-list.
19452045 lv_l = EvaluateLocalObject(l, d);
19462046 nterm_cons_combine(pred);
1947- // TODO: Blocked. Use C++14 lambda initializers to simplify the
1948- // implementation.
19492047 return Combine<NonTailCall>::ReduceCallSubsequent(nterm, ctx,
19502048 EnvironmentGuard(ctx, d), A1::NameTypedReducerHandler(
2049+ // TODO: Blocked. Use C++14 lambda initializers to simplify the
2050+ // implementation.
19512051 // XXX: Capture of %d by copy is a slightly more efficient.
19522052 std::bind([&, d, f, nterm_cons_combine](TNIter& i_0) -> ReductionStatus{
19532053 auto& base(*++i_0);
@@ -1961,11 +2061,8 @@
19612061 return f(l, base, lv_l, nterm, d, i_1);
19622062 }, std::move(i_0)), "eval-acc-head-next"));
19632063 }
1964- else
1965- {
1966- LiftOther(term, base);
1967- return ReductionStatus::Retained;
1968- }
2064+ LiftOther(term, base);
2065+ return ReductionStatus::Retained;
19692066 }, std::move(i)), "eval-acc-nested"));
19702067 }
19712068
@@ -1978,12 +2075,11 @@
19782075 return DoAcc([&](TermNode& l, TermNode& base, TermNode& lv_l, TermNode&
19792076 nterm, const shared_ptr<Environment>& d, TNIter& i) YB_FLATTEN{
19802077 auto& tail(*++i);
1981- auto lv_sum(EvaluateBoundLValueUnwrapped(*++i, d));
19822078
19832079 base.GetContainerRef() = [&]{
19842080 TermNode::Container tcon(base.get_allocator());
19852081
1986- tcon.push_back(std::move(lv_sum));
2082+ tcon.push_back(std::move(EvaluateBoundLValueUnwrapped(*++i, d)));
19872083 tcon.push_back(std::move(nterm));
19882084 tcon.push_back(std::move(base));
19892085 return tcon;
@@ -1992,12 +2088,10 @@
19922088 return Combine<NonTailCall>::ReduceCallSubsequent(base, ctx, d,
19932089 // XXX: Capture of %d by copy is a slightly more efficient.
19942090 A1::NameTypedReducerHandler([&, d]() YB_FLATTEN{
1995- auto lv_tail(EvaluateBoundLValueUnwrapped(tail, d));
1996-
19972091 nterm.GetContainerRef() = [&]{
19982092 TermNode::Container tcon(nterm.get_allocator());
19992093
2000- tcon.push_back(std::move(lv_tail));
2094+ tcon.push_back(EvaluateBoundLValueUnwrapped(tail, d));
20012095 tcon.push_back(std::move(lv_l));
20022096 return tcon;
20032097 }(),
@@ -2019,16 +2113,14 @@
20192113 YAssert(term.size() == 9, "Invalid recursive call found.");
20202114 return DoAcc([&](TermNode& l, TermNode&, TermNode& lv_l, TermNode& nterm,
20212115 const shared_ptr<Environment>& d, TNIter& i) YB_FLATTEN{
2022- auto& con(term.GetContainerRef());
2023- const auto a(term.get_allocator());
2024- auto& n2term(*std::next(con.rbegin()));
2025- auto lv_tail(EvaluateBoundLValueUnwrapped(*++i, d));
2116+ auto& n2term(*std::next(term.rbegin()));
2117+ auto& tail(*++i);
20262118 const auto& lv_sum_op(*++i);
20272119
20282120 n2term.GetContainerRef() = [&]{
20292121 TermNode::Container tcon(n2term.get_allocator());
20302122
2031- tcon.push_back(std::move(lv_tail));
2123+ tcon.push_back(EvaluateBoundLValueUnwrapped(tail, d));
20322124 tcon.push_back(std::move(lv_l));
20332125 return tcon;
20342126 }(),
@@ -2037,9 +2129,9 @@
20372129 // XXX: Capture of %d by copy is a slightly more efficient.
20382130 A1::NameTypedReducerHandler([&, d]() YB_FLATTEN{
20392131 l = std::move(n2term);
2040- return
2041- A1::ReduceCurrentNext(*term.emplace(ystdex::exchange(con, [&]{
2042- TermNode::Container tcon(con.get_allocator());
2132+ return A1::ReduceCurrentNext(
2133+ *term.emplace(ystdex::exchange(term.GetContainerRef(), [&]{
2134+ TermNode::Container tcon(term.get_allocator());
20432135
20442136 tcon.push_back(lv_sum_op);
20452137 tcon.push_back(std::move(nterm));
@@ -2096,7 +2188,8 @@
20962188 YB_FLATTEN ReductionStatus
20972189 DoFoldR1(TermNode& term, ContextNode& ctx)
20982190 {
2099- // NOTE: Subterms are %nterm, the underlying combiner of 'kons', 'knil', 'l'.
2191+ // NOTE: Subterms are %nterm, the underlying combiner of 'kons', 'knil',
2192+ // temporary 'l'.
21002193 YAssert(term.size() == 4, "Invalid recursive call found.");
21012194
21022195 auto& tm(term.GetContainerRef().back());
@@ -2110,8 +2203,6 @@
21102203 auto& nterm(term.GetContainerRef().front());
21112204
21122205 ExtractFirst(nterm, tm);
2113- // TODO: Blocked. Use C++14 lambda initializers to simplify the
2114- // implementation.
21152206 return A1::ReduceCurrentNext(
21162207 *term.emplace(ystdex::exchange(term.GetContainerRef(), [&]{
21172208 TermNode::Container tcon(term.get_allocator());
@@ -2121,6 +2212,8 @@
21212212 return tcon;
21222213 }())), ctx, DoFoldR1,
21232214 A1::NameTypedReducerHandler(
2215+ // TODO: Blocked. Use C++14 lambda initializers to simplify the
2216+ // implementation.
21242217 std::bind([&](shared_ptr<Environment>& d) YB_FLATTEN{
21252218 return
21262219 Combine<NonTailCall>::ReduceEnvSwitch(term, ctx, std::move(d));
@@ -2130,24 +2223,33 @@
21302223 return ReductionStatus::Retained;
21312224 }
21322225
2226+//! \since build 914
2227+template<typename _func>
2228+YB_FLATTEN ReductionStatus
2229+DoBranchListOrNull(TermNode& term, TermNode& tm, _func f)
2230+{
2231+ // XXX: This should have been guaranteed by the call of %PrepareFoldRList
2232+ // or some other preconditions in the caller site.
2233+ YAssert(IsList(tm), "Invalid non-list term found.");
2234+ if(IsBranch(tm))
2235+ return f(term.GetContainerRef().front());
2236+ term.Clear();
2237+ return ReductionStatus::Regular;
2238+}
2239+
21332240 YB_FLATTEN ReductionStatus
21342241 DoMap1(TermNode& term, ContextNode& ctx)
21352242 {
2136- // NOTE: Subterms are %nterm, the underlying combiner of 'appv', 'l'.
2243+ // NOTE: Subterms are %nterm, the underlying combiner of 'appv',
2244+ // temporary 'l'.
21372245 YAssert(term.size() == 3, "Invalid recursive call found.");
21382246
21392247 auto& tm(term.GetContainerRef().back());
21402248
2141- // XXX: This should have been guaranteed by the call of %PrepareFoldRList.
2142- YAssert(IsList(tm), "Invalid non-list term found.");
2143- if(IsBranch(tm))
2144- {
2249+ return DoBranchListOrNull(term, tm, [&](TermNode& nterm) YB_FLATTEN{
21452250 auto i(term.begin());
2146- auto& nterm(term.GetContainerRef().front());
21472251
21482252 ExtractFirst(nterm, tm);
2149- // TODO: Blocked. Use C++14 lambda initializers to simplify the
2150- // implementation.
21512253 return A1::ReduceCurrentNext(
21522254 *term.emplace(ystdex::exchange(term.GetContainerRef(), [&]{
21532255 TermNode::Container tcon(term.get_allocator());
@@ -2158,33 +2260,36 @@
21582260 return tcon;
21592261 }())), ctx, DoMap1,
21602262 A1::NameTypedReducerHandler(
2263+ // TODO: Blocked. Use C++14 lambda initializers to simplify the
2264+ // implementation.
21612265 std::bind([&](shared_ptr<Environment>& d) YB_FLATTEN{
21622266 return Combine<NonTailCall>::ReduceCallSubsequent(*term.begin(),
2163- ctx, std::move(d), A1::NameTypedReducerHandler([&]() YB_FLATTEN{
2164- return ConsMoveSubNext(term);
2165- }, "eval-map1-cons"));
2267+ ctx, std::move(d), A1::NameTypedReducerHandler(
2268+ ConsMoveSubNextRV<Map1Appv>{term}, "eval-map1-cons"));
21662269 }, ctx.ShareRecord()), "eval-map1-appv"));
2167- }
2168- term.Clear();
2169- return ReductionStatus::Regular;
2270+ });
21702271 }
21712272
2273+// NOTE: This is needed for the last operation called recursively. Also
2274+// unwrap here to avoid redundant unwrapping operations.
2275+//! \since build 914
21722276 YB_FLATTEN void
2173-PrepareSumOp(TermNode& term, ContextNode& ctx, TermNode& rterm, ptrdiff_t n)
2277+PrepareTailOp(TermNode& term, ContextNode& ctx, TermNode& rterm, ptrdiff_t n)
21742278 {
2175- auto& sum(*std::next(rterm.begin(), n));
2176-
2177- sum = EvaluateBoundLValueUnwrapped(*term.emplace(std::move(sum)),
2279+ auto& op(*std::next(rterm.begin(), n));
2280+
2281+ op = EvaluateBoundLValueUnwrapped(*term.emplace(std::move(op)),
21782282 ctx.GetRecordPtr());
21792283 }
21802284
2285+// NOTE: This is only needed to maintain the lifetime of the tail operator
2286+// initialized by the call to %PrepareTailOp in the whole liftime of the
2287+// outmost call.
21812288 YB_FLATTEN ReductionStatus
21822289 DoRLiftSum(TermNode& term, ContextNode& ctx, TermNode& rterm,
21832290 ReductionStatus(&f)(TermNode&, ContextNode&))
21842291 {
21852292 #if NPL_Impl_NPLA1_Enable_Thunked
2186- // TODO: Blocked. Use C++14 lambda initializers to simplify the
2187- // implementation.
21882293 return A1::ReduceCurrentNext(rterm, ctx, f, A1::NameTypedReducerHandler([&]{
21892294 LiftOther(term, rterm);
21902295 return ctx.LastStatus;
@@ -2203,8 +2308,6 @@
22032308 {
22042309 auto& con(term.GetContainerRef());
22052310
2206- // XXX: Like %AccL.
2207- BindMoveNextNLocalSubobjectInPlace(con.begin(), 2);
22082311 // NOTE: Bind the last argument as the local reference to list temporary
22092312 // object.
22102313 PrepareFoldRList(con.back());
@@ -2213,14 +2316,14 @@
22132316 TermNode::Container(term.get_allocator()))));
22142317
22152318 // NOTE: Save 'kons' (for %FoldR1) or 'appv' (for %Map1).
2216- PrepareSumOp(term, ctx, rterm, 1);
2319+ PrepareTailOp(term, ctx, rterm, 1);
22172320 return DoRLiftSum(term, ctx, rterm, f);
22182321 }
22192322
22202323 YB_FLATTEN ReductionStatus
22212324 DoListConcat(TermNode& term, ContextNode& ctx)
22222325 {
2223- // NOTE: Subterms are %nterm, 'x', 'y'.
2326+ // NOTE: Subterms are %nterm, temporary 'x', 'y'.
22242327 YAssert(term.size() == 3, "Invalid recursive call found.");
22252328
22262329 auto i(term.begin());
@@ -2233,18 +2336,14 @@
22332336 auto& nterm(term.GetContainerRef().front());
22342337
22352338 ExtractFirst(nterm, tm);
2236- // TODO: Blocked. Use C++14 lambda initializers to simplify the
2237- // implementation.
22382339 return A1::ReduceCurrentNext(
22392340 *term.emplace(ystdex::exchange(term.GetContainerRef(), [&]{
22402341 TermNode::Container tcon(term.get_allocator());
22412342
22422343 tcon.push_back(std::move(nterm));
22432344 return tcon;
2244- }())), ctx, DoListConcat,
2245- A1::NameTypedReducerHandler([&]() YB_FLATTEN{
2246- return ConsMoveSubNext(term);
2247- }, "eval-list-concat-cons"));
2345+ }())), ctx, DoListConcat, A1::NameTypedReducerHandler(
2346+ ConsMoveSubNextRV<ListConcatCons>{term}, "eval-list-concat-cons"));
22482347 }
22492348 LiftOther(term, *++i);
22502349 return ReductionStatus::Retained;
@@ -2258,11 +2357,7 @@
22582357
22592358 auto& ls(term.GetContainerRef().back());
22602359
2261- YAssert(IsList(ls), "Invalid non-list term found.");
2262- if(IsBranch(ls))
2263- {
2264- auto& nterm(term.GetContainerRef().front());
2265-
2360+ return DoBranchListOrNull(term, ls, [&](TermNode& nterm) YB_FLATTEN{
22662361 ExtractFirst(nterm, ls);
22672362 // NOTE: Bind the first term as the local reference to list temporary
22682363 // object.
@@ -2277,9 +2372,320 @@
22772372 }())), ctx, DoAppend, A1::NameTypedReducerHandler([&]() YB_FLATTEN{
22782373 return DoListConcat(term, ctx);
22792374 }, "eval-append-list-concat"));
2375+ });
2376+}
2377+//@}
2378+
2379+//! \since build 914
2380+//@{
2381+YB_FLATTEN TNIter
2382+PrepareListExtract(TermNode& term)
2383+{
2384+ auto& con(term.GetContainerRef());
2385+ auto i(con.begin());
2386+
2387+ // NOTE: Bind the 1st argument as the local reference to list temporary
2388+ // object.
2389+ PrepareFoldRList(*++i);
2390+ return i;
2391+}
2392+
2393+YB_FLATTEN ReductionStatus
2394+DoListExtract(TermNode& term, ContextNode& ctx)
2395+{
2396+ // NOTE: Subterms are %nterm, temporary 'l', 'extr'.
2397+ YAssert(term.size() == 3, "Invalid recursive call found.");
2398+
2399+ auto i(term.begin());
2400+ auto& tm(*++i);
2401+
2402+ return DoBranchListOrNull(term, tm, [&](TermNode& nterm) YB_FLATTEN{
2403+ const auto& d(ctx.GetRecordPtr());
2404+
2405+ tm.Tags |= TermTags::Temporary;
2406+ nterm.GetContainerRef() = [&]{
2407+ TermNode::Container tcon(nterm.get_allocator());
2408+ auto& o(AccessFirstSubterm(tm));
2409+
2410+ tcon.push_back(EvaluateBoundLValueUnwrapped(*++i, d));
2411+ // XXX: As %BindLocalReference in %ForwardFirst. Note that %tm is
2412+ // supposed as if an operand in the local environment to be
2413+ // accessed by an lvalue via local variable, rather than an rvalue
2414+ // operand. Thus, a virtual %TermReference is always assumed and
2415+ // %BindMoveLocalObject cannot be used here.
2416+ tcon.emplace_back(o.GetContainer(), [&]() -> TermReference{
2417+ if(const auto p = NPL::TryAccessLeaf<TermReference>(o))
2418+ return TermReference(BindReferenceTags(*p), *p);
2419+ return TermReference(GetLValueTagsOf(o.Tags
2420+ | (tm.Tags & TermTags::Nonmodifying) | TermTags::Unique), o,
2421+ d);
2422+ }());
2423+ return tcon;
2424+ }();
2425+ nterm.Value.Clear();
2426+ return Combine<NonTailCall>::ReduceCallSubsequent(nterm, ctx, d,
2427+ A1::NameTypedReducerHandler([&, d](){
2428+ tm.GetContainerRef().pop_front();
2429+ return A1::ReduceCurrentNext(
2430+ *term.emplace(ystdex::exchange(term.GetContainerRef(), [&]{
2431+ TermNode::Container tcon(term.get_allocator());
2432+
2433+ tcon.push_back(std::move(nterm));
2434+ return tcon;
2435+ }())), ctx, DoListExtract,
2436+ A1::NameTypedReducerHandler(ConsMoveSubNextRV<ListConcatCons>{
2437+ term}, "eval-list-extract-cons"));
2438+ }, "eval-list-extract-next"));
2439+ });
2440+}
2441+
2442+template<typename _func, typename _func2>
2443+YB_FLATTEN ReductionStatus
2444+DoListExtractFirstRest(TermNode& term, ContextNode& ctx, _func f, _func2 f2)
2445+{
2446+ // NOTE: Subterms are %nterm, temporary 'l'.
2447+ YAssert(term.size() == 2, "Invalid recursive call found.");
2448+
2449+ auto i(term.begin());
2450+ auto& tm(*++i);
2451+
2452+ return DoBranchListOrNull(term, tm, [&](TermNode& nterm) YB_FLATTEN{
2453+ auto& o(AccessFirstSubterm(tm));
2454+
2455+ nterm.Value.Clear();
2456+ if(const auto p = NPL::TryAccessLeaf<TermReference>(o))
2457+ {
2458+ TermReference ref(BindReferenceTags(*p), *p);
2459+ auto& nd(ref.get());
2460+
2461+ f(nterm, ref, nd);
2462+ }
2463+ else if(IsBranchedList(o))
2464+ f2(nterm, o, tm.Tags);
2465+ else
2466+ ThrowInsufficientTermsError(o, true);
2467+ // XXX: This invalidates the irregular reference in %o (if any).
2468+ tm.GetContainerRef().pop_front();
2469+ return A1::ReduceCurrentNext(
2470+ *term.emplace(ystdex::exchange(term.GetContainerRef(), [&]{
2471+ TermNode::Container tcon(term.get_allocator());
2472+
2473+ tcon.push_back(std::move(nterm));
2474+ return tcon;
2475+ }())), ctx, [=](TermNode& t, ContextNode& c){
2476+ return DoListExtractFirstRest(t, c, f, f2);
2477+ }, A1::NameTypedReducerHandler(ConsMoveSubNextRV<ListConcatCons>{term},
2478+ "eval-list-extract-cons"));
2479+ });
2480+}
2481+
2482+YB_FLATTEN ReductionStatus
2483+DoListExtractFirst(TermNode& term, ContextNode& ctx)
2484+{
2485+ return DoListExtractFirstRest(term, ctx,
2486+ [](TermNode& nterm, TermReference& ref, TermNode& nd){
2487+ if(IsBranchedList(nd))
2488+ RegularizeTerm(nterm,
2489+ ReduceToFirst(nterm, AccessFirstSubterm(nd), &ref));
2490+ else
2491+ ThrowInsufficientTermsError(nd, true);
2492+ }, [](TermNode& nterm, TermNode& o, TermTags){
2493+ auto& x(AccessFirstSubterm(o));
2494+
2495+ if(const auto p = NPL::TryAccessLeaf<const TermReference>(x))
2496+ {
2497+ if(!p->IsReferencedLValue())
2498+ {
2499+ LiftMovedOther(nterm, *p, p->IsMovable());
2500+ return;
2501+ }
2502+ }
2503+ // XXX: Term tags are currently not respected in prvalues.
2504+ LiftOtherOrCopy(nterm, x,
2505+ !bool((o.Tags | x.Tags) & TermTags::Nonmodifying));
2506+ });
2507+}
2508+
2509+YB_FLATTEN ReductionStatus
2510+DoListExtractRestFwd(TermNode& term, ContextNode& ctx)
2511+{
2512+ return DoListExtractFirstRest(term, ctx,
2513+ [](TermNode& nterm, TermReference& ref, TermNode& nd){
2514+ ReduceToRestOrVal(nterm, nd, &ref, [](TermNode&) ynothrow{},
2515+ [&](TermNode& dst, TermNode& tm, ResolvedTermReferencePtr){
2516+ LiftOtherOrCopy(dst, tm, ref.IsMovable());
2517+ });
2518+ }, [](TermNode& nterm, TermNode& o, TermTags tm_tags){
2519+ TermNode::Container con(nterm.get_allocator());
2520+
2521+ auto first(o.begin());
2522+ const auto last(o.end());
2523+
2524+ ++first;
2525+ if(!bool((o.Tags | tm_tags) & TermTags::Nonmodifying))
2526+ con.splice(con.end(), o.GetContainerRef(), first, last);
2527+ else
2528+ for(; first != last; ++first)
2529+ con.emplace_back().CopyContent(*first);
2530+ con.swap(nterm.GetContainerRef());
2531+ });
2532+}
2533+
2534+ReductionStatus
2535+LetCore(TermNode& term, ContextNode& ctx, bool no_lift, bool with_env)
2536+{
2537+ // NOTE: Subterms are %nterm, optional 'e', originally bound 'bindings',
2538+ // trailing 'body'.
2539+ YAssert(term.size() >= (with_env ? 3 : 2), "Invalid nested call found.");
2540+
2541+ auto i(term.begin());
2542+ auto& nterm(*i);
2543+
2544+ // NOTE: The %nterm variable should hold the forwarded 'binding' at this
2545+ // point. It cannot have the unique tag, so it can be directly used as an
2546+ // lvalue as if returned by the name resolution.
2547+ YAssert(!bool(nterm.Tags & TermTags::Unique), "Invalid term found.");
2548+
2549+ auto& con(term.GetContainerRef());
2550+ const auto& d(ctx.GetRecordPtr());
2551+
2552+ if(with_env)
2553+ ++i;
2554+ ++i;
2555+ return Combine<NonTailCall>::ReduceCallSubsequent(*term.emplace([&]{
2556+ const auto a(term.get_allocator());
2557+ TermNode::Container tcon(a);
2558+
2559+ tcon.splice(tcon.end(), con, ++i, con.end());
2560+ term.emplace(std::move(tcon));
2561+ // NOTE: Now subterms are 'bindings', optional 'e',
2562+ // originally bound 'bindings', 'body'.
2563+ tcon.clear();
2564+ tcon.push_back(NPL::AsTermNode(a, ContextHandler(std::allocator_arg, a,
2565+ FormContextHandler(Forms::ListExtractFirst))));
2566+ tcon.push_back(nterm);
2567+ return tcon;
2568+ }()), ctx, d, A1::NameTypedReducerHandler([&, d, no_lift, with_env]{
2569+ // NOTE: Now subterms are 'bindings', optional 'e', originally bound
2570+ // 'bindings', 'body', extracted 'formals' for the lambda abstraction.
2571+ const auto a(nterm.get_allocator());
2572+
2573+ // NOTE: Replace 'bindings' (%nterm) directly to the call expression, as
2574+ // 'bindings' is not needed after this call. Note %nterm.Tags is
2575+ // uninterested.
2576+ nterm.emplace(ystdex::exchange(nterm, TermNode(a)));
2577+ nterm.GetContainerRef().push_front(NPL::AsTermNode(a,
2578+ ContextHandler(std::allocator_arg, a,
2579+ FormContextHandler(Forms::ListExtractRestFwd))));
2580+ return Combine<NonTailCall>::ReduceCallSubsequent(nterm, ctx, d,
2581+ A1::NameTypedReducerHandler([&, d, no_lift, with_env]{
2582+ // NOTE: Now subterms are extracted arguments for the call,
2583+ // optional 'e', originally bound 'bindings', 'body',
2584+ // extracted 'formals' for the lambda abstraction.
2585+ auto j(term.begin());
2586+
2587+ if(with_env)
2588+ ++j;
2589+ // NOTE: The original bound 'bindings' is not needed now.
2590+ j = con.erase(++j);
2591+
2592+ auto& body(*j);
2593+ auto& body_con(body.GetContainerRef());
2594+ const auto ab(body_con.get_allocator());
2595+
2596+ if(with_env)
2597+ {
2598+ body_con.push_front(NPL::AsTermNode(ab, TokenValue("#ignore")));
2599+ body_con.splice(body_con.begin(), con, ++j);
2600+ j = term.begin();
2601+ body_con.splice(body_con.begin(), con, ++j);
2602+ body_con.push_front(NPL::AsTermNode(ab,
2603+ ContextHandler(std::allocator_arg, ab, FormContextHandler(
2604+ no_lift ? Forms::VauWithEnvironmentRef
2605+ : Forms::VauWithEnvironment))));
2606+ body.emplace(ystdex::exchange(body, TermNode(ab)));
2607+ body_con.push_front(NPL::AsTermNode(ab,
2608+ ContextHandler(std::allocator_arg, ab,
2609+ FormContextHandler(Forms::Wrap, 1))));
2610+ }
2611+ else
2612+ {
2613+ body_con.splice(body_con.begin(), con, ++j);
2614+ body_con.push_front(NPL::AsTermNode(ab,
2615+ ContextHandler(std::allocator_arg, ab, FormContextHandler(
2616+ no_lift ? Forms::LambdaRef : Forms::Lambda))));
2617+ }
2618+ return Combine<NonTailCall>::ReduceCallSubsequent(body, ctx, d,
2619+ A1::NameTypedReducerHandler([&]{
2620+ // NOTE: Now subterms are extracted arguments for the call,
2621+ // the constructed lambda abstraction.
2622+ YAssert(term.size() == 2, "Invalid term found.");
2623+ con.splice(con.end(), con.front().GetContainerRef());
2624+ // NOTE: The head term is needed if there is no argument.
2625+ con.front().Clear();
2626+ SetupNextTerm(ctx, term);
2627+ // XXX: As %RelayForCall without guard switch. This also
2628+ // requires that %no_lift handled by the inner abstraction.
2629+ return A1::RelayCurrentOrDirect(ctx,
2630+ std::ref(ContextState::Access(ctx).ReduceOnce), term);
2631+ }, "eval-let-call"));
2632+ }, "eval-let-make-combiner"));
2633+ }, "eval-let-extract-arguments"));
2634+}
2635+
2636+ReductionStatus
2637+LetImpl(TermNode& term, ContextNode& ctx, bool no_lift, bool with_env = {})
2638+{
2639+ Retain(term);
2640+ if(FetchArgumentN(term) > (with_env ? 1 : 0))
2641+ {
2642+ auto i(term.begin());
2643+ auto& forwarded(*i);
2644+
2645+ if(with_env)
2646+ ++i;
2647+
2648+ auto& bindings(*++i);
2649+
2650+ if(const auto p = NPL::TryAccessLeaf<TermReference>(bindings))
2651+ {
2652+ if(p->IsReferencedLValue())
2653+ {
2654+ p->AddTags(TermTags::Nonmodifying);
2655+ forwarded = std::move(bindings);
2656+ forwarded.Tags = TermTags::Unqualified;
2657+ return LetCore(term, ctx, no_lift, with_env);
2658+ }
2659+ else
2660+ {
2661+ // NOTE: This shall behave as an lvalue.
2662+ p->RemoveTags(TermTags::Unique | TermTags::Temporary);
2663+ // XXX: Assume synchrounous.
2664+ ReduceToReferenceList(forwarded, ctx, bindings);
2665+ }
2666+ }
2667+ else
2668+ {
2669+ // NOTE: Ditto.
2670+ forwarded.Value = TermReference(GetLValueTagsOf(bindings.Tags)
2671+ & ~TermTags::Unique, bindings, ctx.GetRecordPtr());
2672+ // NOTE: Ditto.
2673+ ReduceToReferenceList(forwarded, ctx, forwarded);
2674+ }
2675+ for(auto& x : forwarded.GetContainerRef())
2676+ // NOTE: The element 'x' should always owns a %Value of
2677+ // %TermReference.
2678+ NPL::Deref(
2679+ NPL::TryAccessLeaf<TermReference>(x)).AddTags(TermTags::Unique);
2680+ forwarded.Tags = TermTags::Temporary,
2681+ forwarded.Value.Clear();
2682+ return LetCore(term, ctx, no_lift, with_env);
22802683 }
2281- term.Clear();
2282- return ReductionStatus::Regular;
2684+ else
2685+ {
2686+ RemoveHead(term);
2687+ ThrowInsufficientTermsError(term, {});
2688+ }
22832689 }
22842690 //@}
22852691
@@ -2493,6 +2899,9 @@
24932899 });
24942900 auto& x(AccessFirstSubterm(nd));
24952901
2902+ // NOTE: As %BindParameterObject::Match in NPLA1.cpp, never bind a
2903+ // temporary to a glvalue list element. Thus, no tag is checked in
2904+ // the condition here.
24962905 if(p_ref)
24972906 BindLocalReference(p_ref->GetTags()
24982907 & (TermTags::Unique | TermTags::Nonmodifying), x,
@@ -2500,7 +2909,13 @@
25002909 else
25012910 BindMoveLocalObject(x, assign_term);
25022911 ForwardToUnwrapped(appv);
2503- term.GetContainerRef() = {std::move(appv), std::move(op)};
2912+ term.GetContainerRef() = [&]{
2913+ TermNode::Container tcon(term.get_allocator());
2914+
2915+ tcon.push_back(std::move(appv));
2916+ tcon.push_back(std::move(op));
2917+ return tcon;
2918+ }();
25042919 // NOTE: See the precondition of
25052920 // %Combine<TailCall>::ReduceEnvSwitch.
25062921 return Combine<TailCall>::ReduceEnvSwitch(term, ctx,
@@ -2514,52 +2929,8 @@
25142929 ReductionStatus
25152930 First(TermNode& term)
25162931 {
2517- return FirstOrVal(term,
2518- [&](TermNode& tm, ResolvedTermReferencePtr p_ref) -> ReductionStatus{
2519-#if true
2520- // XXX: This is verbose but likely more efficient with %YB_FLATTEN.
2521- const bool list_not_move(p_ref && p_ref->IsReferencedLValue());
2522-
2523- if(const auto p = NPL::TryAccessLeaf<const TermReference>(tm))
2524- {
2525- if(list_not_move)
2526- {
2527- // NOTE: As %LiftOtherOrCopy.
2528- term.CopyContent(tm);
2529- return ReductionStatus::Retained;
2530- }
2531- if(!p->IsReferencedLValue())
2532- {
2533- LiftMovedOther(term, *p, p->IsMovable());
2534- return ReductionStatus::Retained;
2535- }
2536- }
2537- else if(list_not_move)
2538- return ReduceToReferenceAt(term, tm, p_ref);
2539- // XXX: Term tags are currently not respected in prvalues.
2540- LiftOtherOrCopy(term, tm, !p_ref || p_ref->IsModifiable());
2541- return ReductionStatus::Retained;
2542-#else
2543- // NOTE: For exposition only. The optimized implemenation shall be
2544- // equivalent to this.
2545- // XXX: This should be safe, since the parent list is guaranteed an
2546- // lvalue by the false result of the call to %NPL::IsMovable.
2547- if(!(p_ref && p_ref->IsReferencedLValue()))
2548- {
2549- if(const auto p = NPL::TryAccessLeaf<const TermReference>(tm))
2550- {
2551- if(!p->IsReferencedLValue())
2552- {
2553- LiftMovedOther(term, *p, p->IsMovable());
2554- return ReductionStatus::Retained;
2555- }
2556- }
2557- // XXX: Term tags are currently not respected in prvalues.
2558- LiftOtherOrCopy(term, tm, !p_ref || p_ref->IsModifiable());
2559- return ReductionStatus::Retained;
2560- }
2561- return ReduceToReference(term, tm, p_ref);
2562-#endif
2932+ return FirstOrVal(term, [&](TermNode& tm, ResolvedTermReferencePtr p_ref){
2933+ return ReduceToFirst(term, tm, p_ref);
25632934 });
25642935 }
25652936
@@ -2843,6 +3214,8 @@
28433214 {
28443215 return WrapOrRef(term, WrapN,
28453216 [&](ContextHandler& h, ResolvedTermReferencePtr p_ref){
3217+ // XXX: Allocators are not used on %FormContextHandler for performance
3218+ // in most cases.
28463219 return WrapH(term, MakeValueOrMove(p_ref, [&]{
28473220 return FormContextHandler(h, 1);
28483221 }, [&]{
@@ -2856,6 +3229,7 @@
28563229 {
28573230 return WrapOrRef(term, WrapRefN,
28583231 [&](ContextHandler& h, ResolvedTermReferencePtr p_ref){
3232+ // XXX: Ditto.
28593233 return p_ref ? ReduceForCombinerRef(term, *p_ref, h, 1)
28603234 : WrapH(term, FormContextHandler(std::move(std::move(h)), 1));
28613235 });
@@ -2937,27 +3311,34 @@
29373311 const auto tag(in_place_type<ContextHandler>);
29383312 const auto a(term.get_allocator());
29393313 // NOTE: The %p_type handle can be extended to point to a metadata block.
3314+ term.GetContainerRef() = [&]{
3315+ TermNode::Container tcon(a);
3316+ // XXX: Allocator is not used here for better performance.
3317+ shared_ptr<void> p_type(new yimpl(byte));
3318+ // shared_ptr<void> p_type(YSLib::allocate_shared<yimpl(byte)>(a));
3319+
3320+ // XXX: Allocators are not used on %FormContextHandler for performance
3321+ // in most cases.
29403322 #if true
2941- shared_ptr<void> p_type(new yimpl(byte));
2942-
2943- term.GetContainerRef() = {NPL::AsTermNode(a, tag, std::allocator_arg, a,
2944- FormContextHandler(Encapsulate(p_type), 1)),
2945- NPL::AsTermNode(a, tag, std::allocator_arg, a,
2946- FormContextHandler(Encapsulated(p_type), 1)),
2947- NPL::AsTermNode(a, tag, std::allocator_arg, a,
2948- FormContextHandler(Decapsulate(p_type), 1))};
3323+ tcon.push_back(NPL::AsTermNode(a, tag, std::allocator_arg, a,
3324+ FormContextHandler(Encapsulate(p_type), 1)));
3325+ tcon.push_back(NPL::AsTermNode(a, tag, std::allocator_arg, a,
3326+ FormContextHandler(Encapsulated(p_type), 1)));
3327+ tcon.push_back(NPL::AsTermNode(a, tag, std::allocator_arg, a,
3328+ FormContextHandler(Decapsulate(p_type), 1)));
29493329 #else
2950- // XXX: Mixing the %p_type above to following assignment code can be worse
2951- // in performance.
2952- shared_ptr<void> p_type(YSLib::allocate_shared<yimpl(byte)>(a));
2953-
2954- term.GetContainerRef() = {NPL::AsTermNode(a, std::allocator_arg, a, tag,
2955- std::allocator_arg, a, FormContextHandler(Encapsulate(p_type), 1)),
2956- NPL::AsTermNode(a, std::allocator_arg, a, tag,
2957- std::allocator_arg, a, FormContextHandler(Encapsulated(p_type), 1)),
2958- NPL::AsTermNode(a, std::allocator_arg, a, tag,
2959- std::allocator_arg, a, FormContextHandler(Decapsulate(p_type), 1))};
3330+ // XXX: Mixing the %p_type above to following assignment code can be
3331+ // worse in performance.
3332+ tcon.push_back(NPL::AsTermNode(a, std::allocator_arg, a, tag,
3333+ std::allocator_arg, a, FormContextHandler(Encapsulate(p_type), 1)));
3334+ tcon.push_back(NPL::AsTermNode(a, std::allocator_arg, a, tag,
3335+ std::allocator_arg, a,
3336+ FormContextHandler(Encapsulated(p_type), 1)));
3337+ tcon.push_back(NPL::AsTermNode(a, std::allocator_arg, a, tag,
3338+ std::allocator_arg, a, FormContextHandler(Decapsulate(p_type), 1)));
29603339 #endif
3340+ return tcon;
3341+ }();
29613342 return ReductionStatus::Retained;
29623343 }
29633344
@@ -3031,7 +3412,7 @@
30313412 // NOTE: This is for the store of lvalue list.
30323413 rterm.emplace();
30333414 // NOTE: Save 'sum'.
3034- PrepareSumOp(term, ctx, rterm, 6);
3415+ PrepareTailOp(term, ctx, rterm, 6);
30353416 return DoRLiftSum(term, ctx, rterm, DoAccR);
30363417 }
30373418
@@ -3039,6 +3420,11 @@
30393420 FoldR1(TermNode& term, ContextNode& ctx)
30403421 {
30413422 RetainN(term, 3);
3423+
3424+ auto i(term.begin());
3425+
3426+ BindMoveLocalObjectInPlace(*++i);
3427+ BindMoveLocalObjectInPlace(*++i);
30423428 return DoFoldRMap1(term, ctx, DoFoldR1);
30433429 }
30443430
@@ -3046,6 +3432,10 @@
30463432 Map1(TermNode& term, ContextNode& ctx)
30473433 {
30483434 RetainN(term, 2);
3435+
3436+ auto i(term.begin());
3437+
3438+ BindMoveLocalObjectInPlace(*++i);
30493439 return DoFoldRMap1(term, ctx, DoMap1);
30503440 }
30513441
@@ -3065,6 +3455,8 @@
30653455 ReductionStatus
30663456 Append(TermNode& term, ContextNode& ctx)
30673457 {
3458+ Retain(term);
3459+
30683460 auto& con(term.GetContainerRef());
30693461 const auto a(con.get_allocator());
30703462
@@ -3078,6 +3470,57 @@
30783470 return DoAppend(term, ctx);
30793471 }
30803472
3473+ReductionStatus
3474+ListExtract(TermNode& term, ContextNode& ctx)
3475+{
3476+ RetainN(term, 2);
3477+
3478+ auto i(PrepareListExtract(term));
3479+
3480+ BindMoveLocalObjectInPlace(*++i);
3481+ return DoListExtract(term, ctx);
3482+}
3483+
3484+ReductionStatus
3485+ListExtractFirst(TermNode& term, ContextNode& ctx)
3486+{
3487+ RetainN(term);
3488+ PrepareListExtract(term);
3489+ return DoListExtractFirst(term, ctx);
3490+}
3491+
3492+ReductionStatus
3493+ListExtractRestFwd(TermNode& term, ContextNode& ctx)
3494+{
3495+ RetainN(term);
3496+ PrepareListExtract(term);
3497+ return DoListExtractRestFwd(term, ctx);
3498+}
3499+
3500+ReductionStatus
3501+Let(TermNode& term, ContextNode& ctx)
3502+{
3503+ return LetImpl(term, ctx, {});
3504+}
3505+
3506+ReductionStatus
3507+LetRef(TermNode& term, ContextNode& ctx)
3508+{
3509+ return LetImpl(term, ctx, true);
3510+}
3511+
3512+ReductionStatus
3513+LetWithEnvironment(TermNode& term, ContextNode& ctx)
3514+{
3515+ return LetImpl(term, ctx, {}, true);
3516+}
3517+
3518+ReductionStatus
3519+LetWithEnvironmentRef(TermNode& term, ContextNode& ctx)
3520+{
3521+ return LetImpl(term, ctx, true, true);
3522+}
3523+
30813524
30823525 void
30833526 CallSystem(TermNode& term)
diff -r 3db7f6204127 -r 92d04ed3856a YFramework/source/NPL/SContext.cpp
--- a/YFramework/source/NPL/SContext.cpp Fri Mar 05 12:30:06 2021 +0800
+++ b/YFramework/source/NPL/SContext.cpp Sat Mar 13 14:46:42 2021 +0800
@@ -11,13 +11,13 @@
1111 /*! \file SContext.cpp
1212 \ingroup NPL
1313 \brief S 表达式上下文。
14-\version r2037
14+\version r2039
1515 \author FrankHB <frankhb1989@gmail.com>
1616 \since build 329
1717 \par 创建时间:
1818 2012-08-03 19:55:59 +0800
1919 \par 修改时间:
20- 2020-02-28 19:24 +0800
20+ 2021-03-12 18:13 +0800
2121 \par 文本编码:
2222 UTF-8
2323 \par 模块名称:
@@ -117,7 +117,8 @@
117117 void
118118 swap(TermNode& x, TermNode& y) ynothrowv
119119 {
120- x.SwapContent(y);
120+ x.SwapContent(y),
121+ std::swap(x.Tags, y.Tags);
121122 }
122123
123124
diff -r 3db7f6204127 -r 92d04ed3856a doc/ChangeLog.V0.9.txt
--- a/doc/ChangeLog.V0.9.txt Fri Mar 05 12:30:06 2021 +0800
+++ b/doc/ChangeLog.V0.9.txt Sat Mar 13 14:46:42 2021 +0800
@@ -11,13 +11,13 @@
1111 /*! \file ChangeLog.V0.9.txt
1212 \ingroup Documentation
1313 \brief 版本更新历史记录 - V0.9 。
14-\version r2967
14+\version r3098
1515 \author FrankHB <frankhb1989@gmail.com>
1616 \since build 800
1717 \par 创建时间:
1818 2020-10-12 17:19:23 +0800
1919 \par 修改时间:
20- 2021-03-05 12:26 +0800
20+ 2021-03-13 14:20 +0800
2121 \par 文本编码:
2222 UTF-8
2323 \par 模块名称:
@@ -32,6 +32,121 @@
3232
3333 $now
3434 (
35+ * $revert(b909) DD "wrong 1st parameter type"
36+ @ "operatives ('$set!', '$setrec!')" @ %Documentation.NPL $since b909,
37+ / %YFramework.NPL $=
38+ (
39+ / %SContext $=
40+ (
41+ + DD "Doxygen command '\relates'" @ "function template %HasValue",
42+ / @ "class %TermNode" $=
43+ (
44+ * "missing swapping tags" @ "friend function %swap" $since \
45+ b857,
46+ // This also has effects on assignment and exchange \
47+ operations (but not for content setting and lifting).
48+ * "missing setting tags" @ "function %SetContent#3"
49+ // This also has effects on content setting and lifting \
50+ operations.
51+ )
52+ ),
53+ / %NPLA $=
54+ (
55+ / DD "grouped several APIs with Doxygen groups %(LexicalCategory, \
56+ TermAccessAuxiliary, TermReferenceAccess, BindingAccess, \
57+ NPLDiagnostics)",
58+ / DLDI "reordered several declarations",
59+ // More consistent with the documentation.
60+ + "functions %(AddTags, RemoveTags)" @ "class %TermReference"
61+ ),
62+ / DD "grouped several APIs to Doxygen group %NPLADiagnostics" @ %NPLA1
63+ ^ $dep_from ("%NPLADiagnostics" @ %NPLA),
64+ / @ "namespace %Forms" @ %NPLA1Forms $=
65+ (
66+ * DLI "missing %Retain call" @ "%function %Append" $since b912,
67+ / DLDI "simplified functions %(AccL, AccR)",
68+ (
69+ / DLI "functions %(Map1, FoldR1)";
70+ // Loops are less efficient here.
71+ * DLI "redundant list binding in the initial binding"
72+ @ "function %Map1" $since b899
73+ // This is redundant for %Map1 but not %FoldR1 because the \
74+ former has less arguments and the last argument (the list) \
75+ should be bound later in the recursive calls.
76+ ),
77+ (
78+ / $re_ex(b913) "avoided unexpected copy by list initialization via \
79+ %std::initializer_list instance"
80+ @ "%TermNode::Container assignment" $effective
81+ @ "functions %(ForwardListFirst, MakeEncapsulationType)";
82+ * $re_add(b913) $comp "move-only object not allowed in terms \
83+ handled by functions as native implemention of applicative"
84+ @ ("function %ForwardFirst" $orig (@ "%ForwardFirstList"
85+ $since b875), "function %MakeEncapsulationType" $since b855)
86+ // This was introduced by %TermNode construction as first. See \
87+ $2021-03 @ %Documentation::Workflow.
88+ ),
89+ / $re_ex(b913) DLI "avoided unexpected copy by list initialization \
90+ via %std::initializer_list instance"
91+ @ "%TermNode construction" @ "function %(SetRest, SetRestRef)"
92+ + "function %ListExtract",
93+ / DLI "avoided redundant list check" @ "underlying list \
94+ construction calls" @ ("function %Map1",
95+ ("function %ListConcat"; $comp "function %Append")),
96+ // See $2021-03 @ %Documentation::Workflow.
97+ + "function %ListExtractFirst",
98+ + "function %ListExtractRestFwd",
99+ / DLDI "simplified list operations with null value returned at end \
100+ with shared implementation to %ListExtract"
101+ $effective @ "functions %(Map1, Append)",
102+ / DLDI "function %Cond" ^ "%IsBranch" ~ "%TermNode::empty",
103+ + "functions %(Let, LetRef, LetWithEnvironment, \
104+ LetWithEnvironmentRef)"
105+ ^ $dep_from ("%TermReference::(AddTags, RemoveTags)" @ %NPLA)
106+ ),
107+ / @ "function %LoadGroundContext" @ %Dependency $=
108+ (
109+ / @ "applicative 'list-extract%'" $=
110+ (
111+ (
112+ / "applicative 'list-extract'" => 'list-extract';
113+ * $re_add(b913) $comp DD "safety guarantee broken on list \
114+ lvalues handled by the safe operation subset implying call \
115+ to 'list-extract'" $mismatch(Documentation.NPL) $since b875
116+ ),
117+ (
118+ / "guaranteed dynamic enviornment used";
119+ // This is more consistent to other applicatives like \
120+ 'foldr1' and 'map-reverse'.
121+ / $lib "added native derivation enabled by default"
122+ ^ $dep_from ("%ListExtract" @ %NPLA1Forms)
123+ ),
124+ * "redundant lift at the result" @ "derivation"
125+ @ "operative '$let*%'" $since b857
126+ $= (/ $impl ^ '$let%' ~ '$let*'),
127+ / DLDI "simplified native derivation" @ "applicative 'expire'"
128+ ^ $dep_from ("%TermReference::AddTags" @ %NPLA),
129+ + "applicative 'as-const'"
130+ ^ $dep_from ("%TermReference::AddTags" @ %NPLA),
131+ * "unexpected copy on calls"
132+ @ "operatives named with '$let' prefix" $since b791
133+ ),
134+ / $lib "added native derivation enabled by default"
135+ @ ("applicative 'list-extract-first'"
136+ ^ $dep_from ("%ListExtractFirst" @ %NPLA1Forms),
137+ "applicative 'list-extract-rest%'"
138+ ^ $dep_from ("%ListExtractRestFwd" @ %NPLA1Forms),
139+ "operative '$let'" ^ $dep_from ("%Let" @ %NPLA1Forms),
140+ "operative '$let%'" ^ $dep_from ("%LetRef" @ %NPLA1Forms),
141+ "operative '$let/e'" ^ $dep_from
142+ ("%LetWithEnvironment" @ %NPLA1Forms), "operative '$let/e%'"
143+ ^ $dep_from ("%LetWithEnvironmentRef" @ %NPLA1Forms))
144+ )
145+ )
146+),
147+
148+b913
149+(
35150 / %YFramework $=
36151 (
37152 / @ "class %ValueNode" @ %YSLib.ValueNode $=
@@ -145,7 +260,15 @@
145260 (
146261 + "context parameter";
147262 / "preserved reference of the result"
148- )
263+ ),
264+ * "move-only object not allowed in terms handled by functions \
265+ as native implemention of applicatives"
266+ @ (("%AccL", "%AccR") $since b898, ("%FoldR1", "%Map1")
267+ $since b899, ("%ListConcat", "%Append") $since b912))
268+ // See $2021-03 @ %Documentation::Workflow.
269+ $= (/ "avoided unexpected copy by list initialization via \
270+ %std::initializer_list instance"
271+ @ "%TermNode construction")
149272 ),
150273 / @ "function %LoadGroundContext" @ %Dependency $=
151274 (
@@ -171,7 +294,8 @@
171294 );
172295 / $comp "supported copy elision of the list elements",
173296 * "safety guarantee broken on list lvalues handled by the \
174- safe operation subset" $since b858
297+ safe operation subset implying call to 'foldr1'"
298+ $mismatch(Documentation.NPL) $since b858
175299 // See $2021-02 @ %Documentation::Workflow.
176300 ),
177301 / "preserved reference of the result" @ "applicative 'rest&'" $=
@@ -205,18 +329,6 @@
205329 // Since b873, %expire could make %TermTags::Unique \
206330 visible in the object language.
207331 )
208- ),
209- / "avoided unexpected copy by list initialization via \
210- %std::initializer_list instance"
211- $effective "%TermNode construction" @ %NPLA1Forms $=
212- // See $2021-03 @ %Documentation::Workflow.
213- (
214- * "move-only object not allowed in terms handled by functions \
215- as native implemention of applicatives"
216- @ (("%AccL", "%AccR") $since b898, ("%FoldR1", "%Map1")
217- $since b899, ("%ListConcat", "%Append") $since b912),
218- / DLI "optimized without list initialization" $=
219- @ "functions %(SetRest, SetRestRef, MakeEncapsulationType)"
220332 )
221333 )
222334 )
@@ -323,7 +435,7 @@
323435 @ "function %LoadStandardDerived"
324436 @ "function 'ensigil'"
325437 // This get less one line.
326- )
438+ ),
327439 / DLI ^ "allocator" @ "initialization of %ContextHandler"
328440 $effective @ ("functions %Forms::(WrapRef, WrapOnceRef, \
329441 Unwrap, Apply, ForwardFirst)" @ %NPLA1Forms,
diff -r 3db7f6204127 -r 92d04ed3856a doc/NPL.txt
--- a/doc/NPL.txt Fri Mar 05 12:30:06 2021 +0800
+++ b/doc/NPL.txt Sat Mar 13 14:46:42 2021 +0800
@@ -11,13 +11,13 @@
1111 /*! \file NPL.txt
1212 \ingroup Documentation
1313 \brief NPL 规范和实现规格说明。
14-\version r19851
14+\version r22087
1515 \author FrankHB <frankhb1989@gmail.com>
1616 \since build 304
1717 \par 创建时间:
1818 2012-04-25 10:34:20 +0800
1919 \par 修改时间:
20- 2021-03-05 01:20 +0800
20+ 2021-03-13 02:07 +0800
2121 \par 文本编码:
2222 UTF-8
2323 \par 模块名称:
@@ -36,6 +36,7 @@
3636 编码细节和其它规范参见 [Documentation::Designation] 。
3737 本文档指定的语言规则(language rule) 中的形式文法(formal grammar) 包括形式语法(formal syntax) 和特定操作的形式,不提供附加的形式语义(formal semantic) 。
3838 形式语法的语法类别(syntax category) 以 BNF(https://zh.wikipedia.org/zh-cn/%E5%B7%B4%E7%A7%91%E6%96%AF%E8%8C%83%E5%BC%8F) 推导规则表示。
39+C++ API 中仅列出在命名空间作用域需被归类的及其它和其它设计直接关联而被引用的部分。这些 API 的具体描述和其它 API 详见接口文档(可通过 Doxygen 源代码注释生成)。
3940
4041 @1 绪论(Introduction) :
4142
@@ -332,7 +333,7 @@
332333 @1.4.7 其它推论和比较:
333334 从对正确性(@1.4.3.1) 的强调可知,较简单性(@1.4.3.2) 优先考虑通用性(generality) 。
334335 这和 [RnRK] 中讨论的设计哲学虽然相当不同,但仍允许和 Kernel 具有相似的特性。
335-作为典型的 NPL 的一个派生实现(@2.3.4) ,NPLA1(@5) 具有以下和 Kernel 相似的核心设计:
336+作为典型的 NPL 的一个派生实现(@2.3.4) ,NPLA1(@7) 具有以下和 Kernel 相似的核心设计:
336337 相似的求值(@4.1) 算法(差异参见 NPLA1 求值算法(@9.7.1) );
337338 环境(@4.6.1.1) 可作为一等对象(@4.1) ;
338339 支持 vau 抽象(@4.5.2.3) ,且使用词法作用域(@4.6.1.1.2) ;
@@ -616,7 +617,7 @@
616617 区域(region) :和特定位置代码关联的有限实体集合。
617618 范围(range) :一个连续区间。此处“连续”的概念由派生实现定义,默认参照数学的形式定义。
618619 声明(declaration) :引入单一名称的表达式。
619-声明区域(declarative region) :对某一个声明及其引入的名称,通过声明区域规则(@4.3.1) 决定的区域,可由词法分析实现(@5.3.1) 确定的关于这个名称有效的代码片段的最大位置范围。
620+声明区域(declarative region) :对某一个声明及其引入的名称,通过声明区域规则(@4.3.1) 决定的区域。
620621 有效名称(valid name) :可以唯一确定指称的实体的名称。
621622 有效命名实体(valid named entity) :有效名称指称的实体。
622623 名称隐藏(name hiding) :若同一个名称在同一个位置属于超过一个声明区域,则应能通过名称隐藏规则(@4.3.2) 确定唯一有效的声明以指定有效名称和对应的有效命名实体,此时有效名称隐藏其它声明区域声明的名称,有效命名实体隐藏可以使用被隐藏名称指称的实体。
@@ -917,7 +918,7 @@
917918 隐式的共享添加伪依赖(pseudo dependency) 减小可程序中操作共享对象的可并行部分,也会由于对共享同步操作的要求而限制并发程序的设计。
918919
919920 @4.2.3.4.3 普遍的设计限制:
920-除非在语言规则中添加复杂的约束(如通过 @4.6.2 的机制)以证明特定上下文可避免共享引用,无法避免引用引入不必要的别名(aliasing) 。若公开这样的性质作为接口约束,违反 @1.4.2.3 。
921+除非在语言规则中添加复杂的约束(如通过 @4.6.2 的机制)以证明特定上下文可避免共享引用,无法避免引用引入不必要的对象别名(aliasing) 。若公开这样的性质作为接口约束,违反 @1.4.2.3 。
921922 隐式的共享使涉及修改(@4.1) 的操作的特性更难设计,参见共享改变(@4.2.3.5.3) 。
922923
923924 @4.2.3.4.4 普遍的实现限制:
@@ -984,7 +985,7 @@
984985 @4.2.3.6 非必要性和其它限制:
985986 对语言设计中的一些关键规则,要求使用引用同时保留非引用的形式访问对象并非必要,因为满足以上准则实际上仅关心对象(类似作为表达式)直接关联的值,而非其它属性。
986987 这也体现是否仅允许通过引用访问对象应是实现细节,而不影响一等对象的判定。
987-依赖一等对象都是引用保证普遍的间接抽象能提供一部分实现的便利(如 TCO(@5.2.6.4) 可以直接移除活动记录帧(@4.5.3.4) ),但并非没有可忽略的代价(如要求全局 GC(@4.2.2.3) 引入非确定性)和替代实现方法。
988+依赖一等对象都是引用保证普遍的间接抽象能提供一部分实现的便利(如 TCO(@5.10.4) 可以直接移除活动记录帧(@4.5.3.4) ),但并非没有可忽略的代价(如要求全局 GC(@4.2.2.3) 引入非确定性)和替代实现方法。
988989 另见规约实现(@7.4) 中关于 TCO 实现方式的讨论。
989990
990991 @4.2.3.7 其它替代:
@@ -1468,58 +1469,56 @@
14681469 若一个函数的调用总是具有终止保证,则此函数是终止函数(terminating function) 。
14691470 若一个函数的调用总是取得值,则此函数是全函数(total function) 。全函数总是终止函数。
14701471
1471-@5 派生语言设计和实现:
1472-当前维护的派生语言为 NPLA ,是 NPL 的抽象语言实现,约定以下附加规则。
1472+@5 派生语言设计:
1473+当前维护的派生语言为 NPLA ,是 NPL 的抽象语言实现,对实现环境和抽象的对象语言约定以下附加规则。
1474+其部分具体设计(@6) 和实现(@7) 以 C++ API 提供。
14731475 NPLA 的参考实现 NPLA1 是具体语言实现,约定特定于当前参考实现的附加规则和实现。
1474-NPLA1 解释实现参见 @7 ,作为对象语言的规格说明参见 @9 。
1475-
1476-@5.1 整体设计概述:
1476+另见 NPLA1 解释实现(@7) 和作为具体对象语言的规格说明(@9) 。
14771477 作为原型设计,NPLA 重视可扩展性。
14781478
1479-@5.1.1 NPLA 领域语义支持:
1479+@5.1 领域语义支持:
14801480 位(bit) :表示二进制存储的最小单位,具有 0 和 1 两种状态。
14811481 字节(byte) :基本字符集(@3.2) 中一个字符需要的最少的存储空间,是若干位的有序集合。
14821482 八元组(octet) : 8 个位的有序集合。
14831483
1484-@5.1.2 实现支持策略:
1485-NPLA 实现支持可扩展的 API ,作为解释器(interpreter) 的实现基础。
1486-解释器的程序执以对语法分析(@5.3.2) 结果的规约实现,可对应语言或(适合改进执行性能的)某种等价变体的小步语义(@2.2.3) 。
1487-在此基础上,可允许派生实现优化性能,如嵌入编译。
1488-NPLA API 公开解释器的状态,可支持和宿主语言的互操作(@2.4.1) 。
1489-虽然 NPLA 没有给出形式语义(@2.2.3) ,但 NPLA 实现中包含的 API 部分地提供和形式语义方法对应的支持:
1490-通过回调对应依赖宿主语言(@5.2) 实现作为描述的指称语义;
1491-在回调内对项和上下文进行操作,对应小步语义;
1492-在回调内复用其它接口,对应大步语义。
1493-NPLA 可支持非固定的规约规则集合,以 API 的形式体现,详见规约 API(@6.5) ;另见表达式的求值(@5.5) 。
1494-具体实现的编码风格导引参见 [Documentation::CommonRules @@5] 。
1495-
1496-@5.2 NPLA 约定:
1484+@5.2 整体约定:
1485+NPLA 对象语言的设计遵循 NPL 规则,并附加如下限制。
1486+
1487+@5.2.1 实现宿主环境:
14971488 使用宿主语言(@2.4.1) 为 ISO C++11(及其之后的向前兼容的版本)的简单实现模型 NPL-EMA(@2.7.1) 。
1489+一字节占用的位和宿主环境(@2.7.1) 一致(至少占用 8 个二进制位)。
1490+
1491+@5.2.2 词法和语法:
14981492 词法分析可接受多字节文本编码的字符串形式的源代码,但不假设其编码中除 0(空字符 NUL )以外的具体代码点被编码的数值,不转换编码(@2.4.1) 。
14991493 使用可选的语法预处理和 NPL-GA 语法(@3.4.4) 。
1494+
1495+@5.2.3 类型系统:
15001496 默认使用隐式类型而非显式类型(@4.6.2.2) 。
15011497 默认使用潜在类型(@4.6.2.2) :值(@4.1) 具有类型;不指定动态类型以外的类型。
15021498 显式类型(如清单类型)(@4.6.2.3) 的机制可由派生实现指定可选地引入。除非派生实现另行指定,引入的静态类型应同动态类型。
1503-使用和宿主语言相容的动态类型检查(@4.6.2.3) 。除非派生实现另行指定及类型映射(@5.2.3) 的需要,使用的类型检查规则和宿主语言一致。
1499+使用和宿主语言相容的动态类型检查(@4.6.2.3) 。除非派生实现另行指定及类型映射(@5.5) 的需要,使用的类型检查规则和宿主语言一致。
15041500 宿主语言对象的值描述状态且宿主语言要求的对 volatile 左值的操作也属于可观察行为(@4.1.3) 。
1505-名称仅被实现为和字符串(@3.2) 的值的一个真子集一一对应的表示(参见 @5.2.3 和 @5.6.1 )。
1501+
1502+@5.2.4 名称、字面量和表达式:
1503+名称仅被实现为和字符串(@3.2) 的值的一个真子集一一对应的表示(参见类型映射(@5.5) )。内部表示参见记号值(@6.6.3.1) 。
15061504 代码字面量(@3.3.3) 解释为名称。
15071505 数据字面量(@3.3.3) 等价字符串字面量(@3.3.3) 。
15081506 扩展字面量(@3.3.3) 包括以 '#' 、'+' 、'-' 起始的但不全是 '+' 或 '-' 组成的长度大于 1 的标识符或十进制数字字符起始的标识符构成的字面量。
1507+存在不保证先求值的子表达式(@3.4.2) 的形式是特殊形式(special form) 。
1508+
1509+@5.2.5 求值的表示:
15091510 规范形式(@4.4.3) 是特定类型的 C++ 对象。
15101511 名称解析失败(@4.3.3) 可被忽略而不终止(@4.4.3) 实现演绎;
15111512 保证名称表达式求值的强规范化(@4.4.3)。
15121513 不要求提供命名空间(@4.3.4) 实现的可变实体。
1513-一字节占用的位和宿主环境(@2.7.1) 一致(至少占用 8 个二进制位)。
1514-存在不保证先求值的子表达式(@3.4.2) 的形式是特殊形式(special form) 。
15151514 不保证求值都是纯求值;非特殊形式使用热情求值;其它情形使用热情求值或惰性求值(@4.5.4) 由具体特殊形式约定。
15161515 对象语言的函数(@4.5.2) 默认为过程(@4.5.2.1) 。过程默认实现为子例程(@4.5.2.1) 。过程指定的计算结果和函数表达式最终返回结果的关联(@4.5.2.1) 是过程调用(@4.5.3.1) 结果的一一映射。
1517-除非另行指定,实现函数(@4.5.2) 的宿主数据结构生存期要求默认同宿主语言;循环引用(@4.2.4) 可能行为未定义(另见内存泄漏(@5.2.4.4) )。
1518-除非另行指定,按值传递(@4.4.4.5) 支持复制初始化(@5.5.2) 对象的一等作用(@4.2.2.2) 。这和宿主语言的对应语义也保持兼容。
1519-派生实现使用的项不满足特定的表示(@5.9.4) 。
1520-
1521-@5.2.1 本机实现(@2.4.1) 和互操作(@2.3.3) :
1522-NPLA 的宿主语言应能提供 NPLA 及派生实现的本机实现。
1516+除非另行指定,实现函数(@4.5.2) 的宿主数据结构生存期要求默认同宿主语言;循环引用(@4.2.4) 可能行为未定义(另见内存泄漏(@5.6.4) )。
1517+除非另行指定,按值传递(@4.4.4.5) 支持复制初始化(@5.8.2) 对象的一等作用(@4.2.2.2) 。这和宿主语言的对应语义也保持兼容。
1518+派生实现使用的项不满足特定的表示(@6.7.11) 。
1519+
1520+@5.3 互操作(@2.3.3) 支持:
1521+NPLA 的宿主语言应能提供 NPLA 及派生实现的本机实现(@2.4.1) 。
15231522 NPLA 的派生实现提供特定的和宿主语言的互操作支持。
15241523 本机实现可以具有 C++ 的实现兼容的二进制接口的函数提供,这些函数称为本机函数(native function) 。
15251524 本机实现可直接支持本机函数在实现中被调用。若被支持,具体接口由派生实现指定。
@@ -1529,43 +1528,43 @@
15291528 调用后具有项被重写为必要的值以表示函数调用的返回值(@4.5.3.1) 。
15301529 本机函数的返回值应能表达任意的非本机函数调用的返回值,即通过求值函数调用(@4.5.3.1) 中函数体(@4.5.2) 的非本机函数的求值结果(@4.1) 。
15311530
1532-@5.2.2 NPLA 未定义行为(@2.3.4) :
1531+@5.4 NPLA 未定义行为(@2.3.4) :
15331532 NPLA 规则不排除未定义行为。其中,宿主语言的未定义行为是非特定体系结构或其它 ISO C++ 意义上不可预测或不可移植的行为。
15341533 部分 NPLA 未定义行为可能在实现中被检查以预防(尽可能避免)宿主语言的未定义行为,但这种检查不保证完全覆盖所有引起未定义行为的条件,不应预期其行为可移植。
15351534 除非派生实现另行指定,NPLA 约定仅有具有以下情形的程序引起未定义行为:
1536-互操作(@5.2.1) 时引起的宿主语言的未定义行为;
1537-本机实现(@5.2.1) 不支持资源分配而引起宿主语言的未定义行为(如宿主语言函数调用的自动对象无法被分配);
1535+互操作(@5.3) 时引起的宿主语言的未定义行为;
1536+本机实现(@5.3) 不支持资源分配而引起宿主语言的未定义行为(如宿主语言函数调用的自动对象无法被分配);
15381537 违反资源所有权语义(@4.2.2.3) 约束的操作,包括但不限于:
1539- 违反内存安全(@5.2.4.3) 的操作;
1538+ 违反内存安全(@5.6.3) 的操作;
15401539 除非另行指定,构造任意的循环引用。
15411540
1542-@5.2.2.1 常规宿主资源分配要求:
1541+@5.4.1 常规宿主资源分配要求:
15431542 一般地,本机实现要求资源分配失败时,引起(可能派生)std::bad_alloc 或另行指定的宿主异常而非宿主语言的未定义行为;但因为宿主语言缺乏保证,可能并非所有宿主语言实现都能保证实现这项特性。
15441543 实际的实现中非极端条件下(如宿主调用栈接近不可用)通常可支持实现这些行为。
15451544 宿主语言实现支持时,具有可预期的失败(而 NPLA 或宿主语言的非未定义行为)的 NPLA 实现的要求称为常规宿主资源分配要求。
15461545
1547-@5.2.3 类型映射(type mapping) :
1546+@5.5 类型映射(type mapping) :
15481547 类型映射指定对象语言和宿主语言之间的实体类型(@4.2.6) 之间的关系,是前者中的类型到后者中的类型的映射。
15491548 作为类型映射目标的宿主语言类型称为宿主类型(hosted type) 。
15501549 对象语言中的值被对象语言的实体类型表示蕴含它被映射的宿主类型表示,反之亦然。
15511550 类型映射可以是非空的多对一、一对多或一一映射。
15521551 若类型映射是一一映射,其类型等价性同宿主语言的语义规则;否则,由类型的语义规则约定。
1553-类型系统(@4.6.2) 是开放(@1.4.3.6) 的,可能提供不被对象语言支持的宿主语言类型和值,如中间值(@5.6) 。
1552+类型系统(@4.6.2) 是开放(@1.4.3.6) 的,可能提供不被对象语言支持的宿主语言类型和值,如中间值(@6.6.3) 。
15541553 但符合已指定的类型的实体需能被视为同种类型的实体使用,即子类型(@4.6.2) 。
1555-因需提供与作为宿主语言的 C++ 的互操作(@5.2.1) 支持,所以明确约定实现中部分实体类型对应的 C++ 类型:
1554+因需提供与作为宿主语言的 C++ 的互操作(@5.3) 支持,所以明确约定实现中部分实体类型对应的 C++ 类型:
15561555 用于条件判断的单一值的宿主类型是 bool 。
1557-字符串(@3.2) 及和字符串的子集一一对应的词素(@3.3.1) 的宿主类型都是 string 类型(@5.3.1) 。
1556+字符串(@3.2) 及和字符串的子集一一对应的词素(@3.3.1) 的宿主类型都是 string 类型(@6.1.5) 。
15581557 推论:字符串和词素可直接比较相等性或排序。
1559-其它宿主类型所在的命名空间由实现(@5.4.1) 约定。具体宿主类型参见以下各节和对象语言类型对应的描述。
1558+其它宿主类型所在的命名空间由实现(@6.1.7) 约定。具体宿主类型参见以下各节和对象语言类型对应的描述。
15601559 宿主类型在对应的 C++ API 中可能以类型别名的形式引入。
15611560
1562-@5.2.4 存储和对象模型:
1561+@5.6 存储和对象模型:
15631562 NPLA 使用统一的模型对存储和对象进行抽象,并提供若干保证。
15641563 对象语言的存储被视为资源进行管理,称为存储资源(memory resource) 。
15651564 除非另行指定,对象是由对象语言构造的一等对象(@4.1) ,且其它明确的非一等对象遵循相同的规则。
15661565
1567-@5.2.4.1 基本模型:
1568-因需提供宿主语言互操作(@5.2.1) 支持,除不支持静态(static) 存储和没有提供支持的存储操作外,NPLA 的基础存储模型和对象模型和 ISO C++11 相同。
1566+@5.6.1 基本模型:
1567+因需提供宿主语言互操作(@5.3) 支持,除不支持静态(static) 存储和没有提供支持的存储操作外,NPLA 的基础存储模型和对象模型和 ISO C++11 相同。
15691568 当前不支持的存储操作包括分配函数(allocation function) 取得的存储和线程局部(thread-local) 存储。
15701569 NPLA 还允许类似对象具有未指定的存储或不需要存储的实体,以使一等实体(@4.2) 可涵盖宿主语言在功能上等价的非对象类型(如 C++ 的引用)。这些实体若被支持,其存储实现和互操作接口由派生实现定义。
15711570 NPLA 中不是一等对象的一等实体(@4.2.1) 仅由派生实现定义。
@@ -1575,48 +1574,49 @@
15751574 和宿主语言类似,对象的生存期(@4.1) 是存储期的子集。创建对象基于已确保可访问的存储;销毁对象结束后释放存储。
15761575 基于宿主操作等意义,作为一等对象相同方式传递的一等实体都在此都视为一等对象;仅当不依赖一等对象的性质时,实现以非一等对象的方式实现一等实体的操作。
15771576
1578-@5.2.4.2 求值和对象所有权:
1579-被求值的表达式的内部表示即项(@5.5) 或环境(@5.4.3) 中的对象具有 NPLA 对象的所有权。
1580-和宿主语言类似,NPLA 临时对象(@5.5.6) 的存储未指定,但部分临时对象被项所有。
1577+@5.6.2 求值和对象所有权:
1578+被求值的表达式的内部表示中的对象具有 NPLA 对象的所有权。
1579+这些内部表示包括环境对象(@5.7.1) 或被求值的表达式中的项(@5.8) 的情形。
1580+和宿主语言类似,NPLA 临时对象(@5.8.5) 的存储未指定,但部分临时对象被项所有。
15811581 求值结束而不被使用的项的资源在求值终止时被释放,包括被项独占所有权的这些临时对象。
15821582 类似宿主语言,求值终止包括可被实现确定的异常(@4.7.1) 退出。
1583-对名义上被项所有的临时对象,必要时实现可分配内部存储转移项(包括在环境中分配),以满足附加要求(如 @5.2.5 )。
1584-和宿主语言类似,对象的所有权随可随对象被转移,且被转移对象后的项具有有效但未指定(valid but unspecified) 的状态,参见 @5.5.2.3 和 @5.5.3 。
1583+对名义上被项所有的临时对象,必要时实现可分配内部存储转移项(包括在环境中分配),以满足附加要求(如 @5.9 )。
1584+和宿主语言类似,对象的所有权随可随对象被转移,且被转移对象后的项具有有效但未指定(valid but unspecified) 的状态,参见对象的复制和转移(@5.8.2.3) 。
15851585 环境对绑定具有的所有权是独占的。绑定对其中的对象可具有独占或共享的所有权。因此,环境可对绑定中的对象具有独占或共享的所有权。
15861586 求值结果(@4.1) 可包含一等对象(@4.1) ,称为结果对象(result object) 。结果对象和 ISO C++17(由提案 [WG21 P0135R1] 引入)中的概念对应。
15871587 函数调用(@4.5.3.1) 时以活动记录(@4.5.3.4) 保持被引用对象的所有权。活动记录及其帧的具体结构、维护方式和生存期由派生实现定义。
15881588 除非另行指定,NPLA 只有一种作用域(@4.6.1.1.2) ,这种作用域中的名称由环境提供。
1589-因为宿主语言函数调用实现(典型地,调用栈(call stack) 及其中的栈帧)不提供可移植的互操作(@5.2.1) ,除非另行指定,NPLA 的活动记录设计不需要保证直接对应关系。
1590-作为非一等对象的环境对象之间共享所有权,以作为一等对象的环境引用访问(@5.4.3) 。按值传递(@4.4.4.5) 环境引用不引起其中所有的对象被复制。
1591-
1592-@5.2.4.3 内存安全(memory safety) :
1589+因为宿主语言函数调用实现(典型地,调用栈(call stack) 及其中的栈帧)不提供可移植的互操作(@5.3) ,除非另行指定,NPLA 的活动记录设计不需要保证直接对应关系。
1590+作为非一等对象的环境对象之间共享所有权,以作为一等对象的环境引用访问(@5.7.1) 。
1591+
1592+@5.6.3 内存安全(memory safety) :
15931593 (非并发)内存安全是存储资源避免特定类型不可预测错误使用的性质。
15941594 基本的内存安全保证蕴含非并发访问时不引起未定义行为。这至少满足:
1595-对存储的访问总是在提供存储的对象的存储期内,除非有其它另行指定的机制(如宿主环境的互操作(@5.2.1) )保证存储的访问不违反其它语义规则;
1595+对存储的访问总是在提供存储的对象的存储期内,除非有其它另行指定的机制(如宿主环境的互操作(@5.3) )保证存储的访问不违反其它语义规则;
15961596 宿主环境中不访问未被初始化的值。
15971597 派生实现可能扩展内存安全,提供语言规则避免非预期的内存访问错误,提供更一般的高级安全(security) 保证,如保密性(secrecy) 和完整性(integrity) (另见 https://arxiv.org/abs/1705.07354 )。
15981598 除非另行指定,派生实现不提供扩展的内存安全保证。
15991599 NPLA 不对数据竞争避免(data race avoidence) 提供保证。
16001600 用户代码应注意避免违反内存安全的访问,包括非并发的,以及并发访问的内存冲突。
16011601
1602-@5.2.4.3.1 非内存安全操作:
1602+@5.6.3.1 非内存安全操作:
16031603 非内存安全操作是不保证内存安全的操作,在对象语言中即可能引起违反内存安全。
1604-这些操作违反内存安全时,引起 NPLA 未定义行为(@5.2.2) ,且可能未被实现检查而同时引起宿主语言的未定义行为(@5.2.2) 。
1605-NPLA 当前未被实现检查(@5.2.2) 的操作包括:
1606-未关联环境引用计数(@5.6.3.1) 的 NPL::TermReference(@5.6.3) 初始化;
1607-非内存安全提升操作(@6.6.5) 。
1604+这些操作违反内存安全时,引起 NPLA 未定义行为(@5.4) ,且可能未被实现检查而同时引起宿主语言的未定义行为(@5.4) 。
1605+NPLA 当前未被实现检查(@5.4) 的操作包括:
1606+未关联环境引用计数(@6.6.3.3.1) 的 NPL::TermReference(@6.6.3.3) 初始化;
1607+非内存安全提升操作(@6.6.7.5) 。
16081608 对象语言中的非内存安全特性可能直接调用这些操作。NPLA 外依赖此类操作的其它操作也具有类似的性质。
16091609
1610-@5.2.4.3.2 NPLA 对象语言内存安全保证:
1611-NPLA 中,确定地引入具有非内存安全操作(@5.2.4.3.1) 的对象的操作应仅只包括引入特定的间接值(@5.7.3) 或其它派生实现指定类型的值的操作:
1612-调用引入不保证内存安全的间接值的 NPLA API(@6.6.5) ;
1610+@5.6.3.2 NPLA 对象语言内存安全保证:
1611+NPLA 中,确定地引入具有非内存安全操作(@5.6.3.1) 的对象的操作应仅只包括引入特定的间接值(@6.4.3) 或其它派生实现指定类型的值的操作:
1612+调用引入不保证内存安全的间接值的 NPLA API(@6.6.7.5) ;
16131613 调用 NPLA 中其它取 YSLib::ValueNode 存储值的间接值的 API 。
1614-排除非内存安全操作以及非内存安全的本机实现(@5.2.1) ,NPLA 实现的对象语言提供基本内存安全保证。
1615-
1616-@5.2.4.3.3 NPLA 内存安全保证:
1617-满足 @5.2.4.3.2 同时排除引起宿主语言未定义行为的非内存安全的操作(如超出生存期的对象访问),NPLA 实现提供基本内存安全保证。
1618-
1619-@5.2.4.4 内存泄漏(memory leak) :
1614+排除非内存安全操作以及非内存安全的本机实现(@5.3) ,NPLA 实现的对象语言提供基本内存安全保证。
1615+
1616+@5.6.3.3 NPLA 内存安全保证:
1617+满足 @5.6.3.2 同时排除引起宿主语言未定义行为的非内存安全的操作(如超出生存期的对象访问),NPLA 实现提供基本内存安全保证。
1618+
1619+@5.6.4 内存泄漏(memory leak) :
16201620 资源泄漏(resource leak) 是不能预期地(决定性地)访问之前被分配的资源的情形。
16211621 内存泄漏(memory leak) 是存储资源的泄漏。
16221622 强内存泄漏状态是指存在存储无法通过任何途径访问的状态。若存在存储不被任意对象或其它另行指定的代替对象的实体(如宿主环境)所有权的传递闭包包含,即所有权依赖不可达(unreachable) ,则存在强内存泄漏。
@@ -1624,8 +1624,8 @@
16241624 一般意义下,[Cl98] 中定义的任一空间复杂度类都可以作为形式的预期。因为内存作为存储资源被空间复杂度类度量,满足某个空间复杂度类的无空间泄漏(space leak) 蕴含对应的无内存泄漏。
16251625 弱内存泄漏的预期的可实现性和实现细节相关,因此 NPLA 不指定具体预期。
16261626
1627-@5.2.4.4.1 资源回收策略:
1628-单一作用域(@5.2.4.2) 内的资源回收策略有删除(deletion) 和保留(retention) 的策略(详见 [Cl98] )。
1627+@5.6.4.1 资源回收策略:
1628+单一作用域(@5.6.2) 内的资源回收策略有删除(deletion) 和保留(retention) 的策略(详见 [Cl98] )。
16291629 NPLA 不限定具体使用的回收策略,但应支持释放一等对象时副作用(@4.2.2.1) 且不放弃确定性(@4.2.3.6) 。
16301630 为简化语义规则同时避免限制特定的可用资源(如系统中剩余的内存)的变化被派生实现抽象为副作用,除非派生实现指定,不对内存使用保留策略,不使内存超出对象生存期(@4.1) 。
16311631 NPLA 要求完全避免强内存泄漏,但不要求实现 GC(@4.2.3.4.4) 。
@@ -1635,54 +1635,230 @@
16351635 除非即从循环引用的对象中区分出具有不同类所有权的对象子集实现所有权正规化,总是存在无法被释放资源的对象(@4.2.4.2) 。
16361636 基于非预期的循环引用不可避免地造成实现开销而违反 @1.4.2.2(即使这种开销可能并不总是可观察),NPLA 不要求实现 GC 和对一般对象区分强弱引用等机制避免循环引用(@5.2) 。
16371637
1638-@5.2.4.4.2 安全性:
1639-内存泄漏是和内存安全(@5.2.4.3) 不同的另一类非预期的问题,表明语言设计、实现或程序存在缺陷。
1638+@5.6.4.2 安全性:
1639+内存泄漏是和内存安全(@5.6.3) 不同的另一类非预期的问题,表明语言设计、实现或程序存在缺陷。
16401640 注意即便不违反内存安全保证,涉及弱化空间复杂度类预期的内存泄漏仍可引起安全问题。
16411641 内存泄漏和违反内存安全同属违反特定的存储访问不变量的错误条件(error condition) ,但因为不论在语言还是程序的设计和实现中,避免的机制相当不同,在此被区分对待。
1642-存在其它语言使用类似的区分内存泄漏和非内存安全(@5.2.4.3.1) 的设计,如 [Rust](详见 https://doc.rust-lang.org/book/second-edition/ch15-06-reference-cycles.html )。
1643-
1644-@5.2.4.5 自动存储管理:
1642+存在其它语言使用类似的区分内存泄漏和非内存安全(@5.6.3.1) 的设计,如 [Rust](详见 https://doc.rust-lang.org/book/second-edition/ch15-06-reference-cycles.html )。
1643+
1644+@5.6.5 自动存储管理:
16451645 和 C++ 的自动变量类似,函数调用结束、控制退出函数体(@4.5.2) 的作用域(对应 C++ 的函数体最外层块作用域)后,被变量独占所有权的资源被释放。
1646-资源回收策略(@5.2.4.4.1) 允许存储资源和宿主语言对象之间的明确对应,且允许以环境持有所有作用域内的所有资源。
1647-使用自动变量释放的机制,自动存储管理的基本设计可不依赖 GC(@5.2.4.4) ,也不需显式对资源进行释放。
1648-把右值(@5.5.1) 的值传递视为传递独占的引用,这类似 newLISP 的 ORO(One Reference Only) :http://www.newlisp.org/MemoryManagement.html 。
1649-
1650-@5.2.5 生存期附加约定:
1646+资源回收策略(@5.6.4.1) 允许存储资源和宿主语言对象之间的明确对应,且允许以环境持有所有作用域内的所有资源。
1647+使用自动变量释放的机制,自动存储管理的基本设计可不依赖 GC(@5.6.4) ,也不需显式对资源进行释放。
1648+把右值(@5.8.1) 的值传递视为传递独占的引用,这类似 newLISP 的 ORO(One Reference Only) :http://www.newlisp.org/MemoryManagement.html 。
1649+
1650+@5.7 NPLA 环境:
1651+求值环境(@4.6.1.1) 维护名称和作用域。
1652+NPLA 的求值环境可以是一等环境(first-class environment) ,即作为对象语言中的一等对象(@4.2.1) 的环境。
1653+环境可引用关联的其它环境对象为父环境(parent environment) ,用于重定向(@4.3.3) 。
1654+
1655+@5.7.1 环境对象:
1656+环境作为可保持可变状态的对象,是环境对象。
1657+环境对象包含变量名称到表示被绑定对象(bound object) 的映射,称为名称绑定映射(name binding map) 实现变量绑定(@4.1) 。
1658+名称绑定映射对被绑定对象具有所有权。
1659+推论:环境对象对被绑定对象具有所有权。
1660+环境对象非一等对象。
1661+关于环境的内部表示,参见环境数据结构(@6.8.1) 。
1662+环境是名称解析(@4.3.3) 时查找名称的目标(@4.3.3) 。
1663+通过其它环境实现重定向(@4.3.3) 的方式(@5.7) 表示环境是链接的(linked) 而非平坦的(flat) 。
1664+
1665+@5.7.2 环境引用:
1666+对象语言中的环境以表示环境的引用的一等对象(@4.2.1) 表示,称为环境引用。
1667+环境引用共享环境对象的所有权。
1668+按值传递(@4.4.4.5) 环境引用不引起其中所有的对象被复制。另见引用(@4.2.3) 。
1669+根据所有权(@4.2.2.3) 管理机制的不同,环境引用包括环境强引用(strong reference) 和环境弱引用(weak reference) 。
1670+引入环境弱引用作为一般的引用机制,且仅在必要时使用环境强引用,以避免过于容易引入循环引用引起强内存泄漏(@5.6.4.1) ,符合 @1.4.5.2 。
1671+传递环境引用不引起被引用的环境对象被复制。
1672+
1673+@5.7.3 当前环境:
1674+对象语言中,表达式的求值隐含对应一个环境对象,称为当前环境(current environment) 。
1675+
1676+@5.8 表达式和求值(@4.4) :
1677+本节约定对象语言中的表达式求值规则。
1678+被求值的表达式使用被规约的项(@6.2) 作为内部表示。
1679+
1680+@5.8.1 值类别(@4.2.2.1) :
1681+和 ISO C++17(由提案 [WG21 P0135R1] 引入的特性)类似,表达式归类为具有以下值类别之一:
1682+泛左值(glvalue) :求值用于决定被表示的对象的同一性(@4.1) 的表达式;
1683+纯右值(prvalue) :求值不用于决定对象同一性(而仅用于初始化(@5.8.2) 临时对象(@5.8.5) 或计算对象中存储的值)的表达式。
1684+一个表达式可能被标记为消亡值(xvalue) 或附加其它元数据(如通过间接的引用(@4.2.3.3.1) ),以提供基于不同的所有权的行为。
1685+纯右值蕴含对象在可观察行为(@4.1.3) 的意义上不被共享,类似不被别名的引用的被引用对象不被共享(@4.2.3.4.3) 。
1686+左值(lvalue) 是除了消亡值外的泛左值。
1687+右值(rvalue) 是消亡值或纯右值。
1688+求值涉及表达式的值类别仅在必要时约定。
1689+值类别根据是否只关心表达式关联的(对象的或非对象的)值,在需要对象时提供区分两类一等对象(@4.2) 的机制,同时避免在仅需要表达式关联的值时引入不必要的其它对象。
1690+另见引用(@4.2.3) 对一等状态(@4.2.2.1) 的支持(@4.2.3.3.2) 。
1691+
1692+@5.8.1.1 类型系统和表示:
1693+值类别在 NPLA 中被作为一种内建的类型系统,参见 @4.2.6 。
1694+内部表示允许存在附加的相关性(@6.3.1) 。
1695+
1696+@5.8.1.2 设计原理:
1697+按已有的规则,NPLA 约定求值至少需要支持以下的重要的性质:
1698+第一,允许表达式直接表示一等实体而不论它是否是一等对象(@4.1)(即便按 @4.2.1 ,默认不会出现这种情形,派生实现仍然可能改变);
1699+第二,使用支持按引用传递的策略(@4.4.4.5) ,允许通过表达式构造引用不同对象的引用类型(reference type) 的值且被作为一等对象(@4.2) 传递(详见项引用(@6.6.3.3) )。
1700+一般地,这表示引入值类别的差异是必要的。因为,若假设平凡(trivial) 情形下,值类别可退化到都是泛左值或都是纯右值的情形。
1701+若只存在泛左值,则任意的求值都决定一个对象;由于存在和一等对象无关的实体,无法满足第一条性质。
1702+若只存在纯右值,则无法区分不同的对象,形式上无法构造出可用的引用类型的值满足第二条性质。
1703+此外,NPLA 同时约定:
1704+第三,求值满足递归蕴含规则(@4.4.4.1) ;
1705+第四,允许对象的操作保持资源局域性,满足一等对象不依赖引用的抽象(@4.2.3) 同时允许按需区分是否依赖引用的两类一等对象;
1706+第五,避免需要假设存在外部具有被引用对象(@4.2.3) 的所有权的所有者(@5.6.4) 。
1707+第三条性质保证表达式的作用(@2.3.4) 是可组合的并允许求值表达为树规约(@6.2) ,还保证能总是通过子表达式的值类别决定表达式的值类别。因为被求值的表达式是有限的,判定过程是总是能终止,即便求值不满足强规范化性质(@4.4.3) 。
1708+第四条性质要求提供泛左值总是能作为纯右值使用的机制和通过纯右值引入对象的机制,详见值类别转换(@5.8.4) 。
1709+第五条性质要求在表达式之外不存在地位相同的对象的存储资源(@5.6) 的所有者,限定了被决定同一性的对象的外延;存储由环境提供(@5.6) ,其中不需要保存对象的引用。
1710+
1711+@5.8.2 初始化:
1712+部分创建对象的表达式引入对象的初始化(initialization) 。
1713+初始化包括被绑定对象(@5.7) 的初始化和作为函数值的返回值(@4.5.3.1) 对象的初始化。
1714+决定初始化这些对象的作用(@2.3.4) 的表达式是初始化的初值符(initializer) 。
1715+初值符的求值可能有副作用,其求值结果指定特定被初始化的对象的初始值(initial value) 。
1716+初始化被绑定对象可能以修改操作(@4.1.4.2) 的形式体现,此时修改绑定具有副作用。若这样的副作用存在,每个被初始化的值后序(@4.4.1) 于对应初始的计算。
1717+和宿主语言不同,初始化不是独立的依赖特定语法上下文的概念,但此处语义上的作用类似,一般可蕴含宿主对象的初始化。
1718+
1719+@5.8.2.1 复制初始化和直接初始化:
1720+和宿主语言类似,初始化包括直接初始化(direct initialization) 和复制初始化(copy initialization) 。
1721+函数可能接受引用值(@5.8.3) 参数和返回值,是对函数的形式参数或函数值的复制初始化;其它初始化是直接初始化。
1722+复制初始化形式参数和函数值时,函数参数或返回值作为初值符。
1723+
1724+@5.8.2.2 函数参数和函数值传递(@4.4.4.5) :
1725+部分函数可保证被初始化的值和初值符的值及元数据(如项引用的元数据(@6.6.3.3.1) )一致。
1726+类似宿主语言的完美转发(perfect forwarding) ,这样的参数或返回值的初始化的求值称为转发(forwarding) 。
1727+转发也包括只部分保留上述部分元数据的情形。
1728+在允许保留元数据不变的上下文,转发在本机实现(@5.3) 中可直接通过转移项实现。
1729+相对地,完美转发也保持引入这些初始化的表达式(通常是被求值取得函数值的函数表达式)时,其求值结果(函数值)的值类别(@5.8.1) 和初值符保持一致。
1730+
1731+@5.8.2.3 对象的复制和转移:
1732+类似宿主语言中的类类型的值,可使用初值符为参数进行复制或转移操作以复制初始化对象。(其它情形另见复制消除(@5.8.5.3) 。)
1733+类似 ISO C++11 起选择类的转移构造函数代替复制构造函数,可使用转移操作时,不对项进行复制,因此不要求值数据成员保存的宿主对象可复制(而避免抛出 ystdex::invalid_construction 等实现相关的异常)。
1734+和 ISO C++11 起不同,上述可使用转移操作的条件和语法上下文无关:引起选择转移操作的条件由对初值符的谓词而非类似宿主语言的构造函数判断(详见 @5.8.4.1 )。
1735+同宿主语言,对象的复制和转移不改变被转移后的类型(@4.6.2) 。
1736+
1737+@5.8.3 引用值(reference value) :
1738+在对象语言中,引用值(reference value) 作为引用的值,可保存在一等对象中,这样的一等对象是引用对象(reference object) 。
1739+引用值和引用对象的值具有引用类型(@5.8.1.2) 。
1740+在特定上下文中引用和其它一等对象(@4.2.1) 的值的相同具有不同的语义。这主要体现在引用值被按值传递和按引用传递时(@4.4.4.5) 。差异和 ISO C++ 的引用类似。
1741+
1742+@5.8.3.1 有效性:
1743+引用值有效当且仅当存在关联的对象且访问对象不引起未定义行为。
1744+有效的引用值应通过特定的构造引用值方式引入,包括:
1745+在对象语言通过被引用对象初始化引用值;
1746+互操作(@5.3) 引入的保证不引起未定义行为的引用值。
1747+一些对象语言的操作(如改变被引用对象)可能引起已被初始化的引用值成为悬空引用。
1748+指定引用无效化的具体操作(若存在)由派生实现定义。
1749+访问无效的引用值不满足内存安全(@5.6.3) 而引起未定义行为。
1750+更一般的其它的实体有效性参见间接值(@6.4.4) 的有效性。
1751+
1752+@5.8.4 值类别转换:
1753+和宿主语言类似,具有特定值类别的表达式可转换为不同值类别的表达式:
1754+除非另行指定,泛左值总是允许作为纯右值使用,称为左值到右值转换(lvalue-to-rvalue conversion) ;
1755+从纯右值初始化(@5.8.2) 可被对象语言作为一等对象(@4.1) 使用的临时对象(@5.8.5) 作为消亡值(@5.8.1) ,称为临时对象实质化转换(temporary materialization conversion) 。
1756+左值到右值转换没有副作用(@4.2.2.1) 。临时对象实质化转换没有副作用,当且仅当其中初始化临时对象时没有副作用。
1757+临时对象实质化转换中,纯右值被实质化(materialized) 。
1758+在求值子表达式时,按表达式具有的语义,必要时(如按 @5.8.1.1 判断上下文的值类别)进行值类别转换。
1759+为支持引用值(@5.8.3) 作为一等对象,NPLA 在左值到右值转换的基础上提供更精细的引用值提升转换,即以下转换操作:
1760+若操作数是引用值,则结果是操作数引用的被引用对象;
1761+否则,结果是操作数。
1762+根据引用值的性质,易知左值到右值转换的规约是引用值转换的规约的传递闭包,即:
1763+若操作数是已被折叠的引用值,则引用值提升转换等价左值到右值转换;
1764+否则,有限次的引用值提升转换等价左值到右值转换。
1765+
1766+@5.8.4.1 设计原理:
1767+值类别和左值到右值转换在一些上下文的行为类似箱和自动拆箱(@4.2.3.5.3) ,不利于维护简单性(@1.4.3.2) :
1768+特别地,和宿主语言不同,函数不包含充分的信息(参数类型)推断是否接受左值操作数,因此在不提供针对函数的重载(overloading) 一般机制的前提下,本机实现(@5.3) 不能预知输入的操作数是否是左值,通常需分别支持左值和右值的操作数;
1769+即便提供重载,仍然较单一的值类别更复杂。
1770+但 NPLA 的设计中,值类别转换已被通过正确(@1.4.3) 反映需求的存储和对象模型(@5.6) 的设计隐含在项的内部性质中,因此不是可选的。
1771+由正确性的优先规则(@1.4.3) ,完整性(@1.4.3.1.1) 应先于简单性被满足。
1772+而考虑统一性(@1.4.5.1) ,对存储和对象模型的设计,用户自行的实现仍要求这些设施(尽管更困难)。
1773+
1774+@5.8.4.2 返回值转换(return value conversion) :
1775+返回值转换是一次引用值提升转换和可选的临时对象实质化转换的复合。
1776+返回值转换(return value conversion) 用于在对象语言中确定函数调用的返回值(@5.3) 可包含函数体(@4.5.2) 的求值结果到返回值的转换。
1777+返回值转换的应用参见 @7.1.4.2 。
1778+
1779+@5.8.5 临时对象(temporary object) :
1780+特定的 NPLA 非一等对象(@4.1) 是临时对象。
1781+当前设计中临时对象具有特定的表示(@6.3.3) 。
1782+NPLA 允许(但不要求对象语言支持)一等对象通过特定的求值,在中间结果中蕴含这种非一等对象。
1783+和宿主语言类似,NPLA 对象语言在特定的上下文引入其它临时对象,包括:
1784+实质化转换上下文(@5.8.5.1) ;
1785+返回值转换上下文(@5.8.5.2) 。
1786+关于临时对象的存储和所有权,参见求值和对象所有权(@5.6.2) 。
1787+为简化规约和互操作(@5.3) 机制的设计,和 ISO C++17 不同,引入临时对象不包括延迟初始化或异常对象的创建。
1788+关于避免特定相关对象的初始化(@5.8.2) 的要求,参见复制消除(@5.8.5.3) 。
1789+
1790+@5.8.5.1 实质化转换上下文:
1791+可要求临时对象实质化转换的上下文包括:
1792+使用纯右值初始化按引用绑定的变量(如函数的引用类型的形式参数(@4.5.2) );
1793+求值函数调用以初始化返回值对象。
1794+其中,按引用绑定的变量被可选地支持。
1795+一般地,按引用绑定的变量在活动调用(@4.5.3.1) 关联的环境分配临时对象。此时,对象被调用表达式的项独占所有权,同时被绑定的环境独占资源所有权。
1796+临时对象实质化转换引入临时对象的规则和 ISO C++17 不同:
1797+不论表达式是否作为子表达式使其值被使用(未使用的情形对应 ISO C++ 中的 discarded-value expression ),都允许存在临时对象;
1798+
1799+@5.8.5.2 返回值转换上下文:
1800+返回值转换(@5.8.4.2) 可引入实质化的临时对象,其中可能转移求值的中间结果(条件参见 @6.6.3.3.4 );否则,对象被复制。
1801+此处被转移对象符合求值和对象所有权(@5.6.2) 规则中的临时对象的定义,但除非另行指定,被转移的对象不在对象语言中可被访问。
1802+仅在对象被复制且复制具有副作用时,返回值转换具有等价复制的副作用。
1803+
1804+@5.8.5.3 复制消除(copy elision) :
1805+和 ISO C++17 类似,NPLA 要求特定上下文中的复制消除,排除复制或转移操作且保证被消除操作的源和目的对象的同一性(@4.2.1.1) 。
1806+和 ISO C++17 不同,NPLA 的复制消除限于临时对象的消除,但不限制对象的类型(特定的 C++ 类类型),且除以下约定外的所有表达式求值的上下文对复制消除相同(例如,不区分求值结果是否被作为返回值(@4.5.3.1) 或求值是否为常量表达式)。
1807+复制消除仅在以下转换上下文中被要求,即直接使用转换的源表达式中被求值项指称的对象(@6.4.5) 作为实质化的对象而不初始化新的临时对象:
1808+实质化转换上下文(@5.8.5.1) ;
1809+引起对象转移的返回值转换上下文(@5.8.5.2) 。
1810+非本机实现(@5.3) 函数函数体内指定的返回值不属于上述的确定返回值的上下文,但也不要求被复制消除。这和 ISO C++17 要求 return 语句中的特定的表达式被消除不同,而不需要依赖特定上下文的语法性质。
1811+实现仍可能根据当前环境(@5.7.3) 来判断是否在允许消除对象复制的上下文中(当前未实现),而进行复制消除。
1812+在完成实质化转换前的不完整的求值规约(@4.4) 中的临时对象在逻辑上不需要作为一等对象(@4.1) 存在,但纯右值作为对象表示中的子项,随纯右值在宿主语言中作为对象存在,以允许互操作。
1813+复制消除不在被初始化对象以外引入新的对象语言可见的对象。
1814+在使用纯右值初始化引用值时,延长源表达式的项指称的对象(@6.4.5) 的生存期(@5.9) 。这和初始化非引用值类似,但实现需区分是否初始化的是延长生存期的临时对象,以确保之后能区分是否按引用绑定。
1815+
1816+@5.8.6 表达式的类型:
1817+NPLA 的类型系统(@4.6.2) 使用隐式类型(@4.6.2.2) ;和 Scheme 及 Kernel 类似,默认使用潜在类型,保证表达式的值具有类型(@5.2) 。
1818+NPLA 表达式的类型约定为表达式求值结果(@4.1) 的类型。
1819+空求值(@4.4.2) 的求值结果要求未求值的合式的(@2.5.1) 表达式应具有和语法分析(@6.1.6) 的输出兼容的类型,参见 @6.2.1 和 @6.6.3.1 。
1820+注意表达式的类型和 [R7RS] 的 expression type 无关,后者是语法形式(@3.4.3) 的约定;因为存在合并子(@4.5.3.2) 作为一等对象(@4.1) 的类型,不需要这种约定。
1821+实现对特定的上下文的表达式可使用类型推断(@4.6.2.2) 。由此确定的类型类似宿主语言的表达式的类型。
1822+和 Scheme 和 Kernel 不同而类似宿主语言,表达式具有值类别(@5.8.1) 。
1823+值类别的指派是类型系统的一部分(@5.8.1.1) 。但除非另行指定,类似宿主语言,值类别和表达式的类型正交,以简化操作涉及类型的指定。
1824+和宿主语言不同,语言机制不限制值类别和其它类型的判断操作和结果(@4.1) 被作为一等对象(@4.2.5) ,尽管不一定直接提供。
1825+
1826+@5.9 生存期附加约定:
16511827 和宿主语言不同,NPLA 子表达式的求值顺序可被不同的函数(特别允许显式指定对特定操作数(@4.4.3.1) 求值的操作子(@4.5.3.2) )中的求值调整,不需要特别约定。
1652-NPLA 不存在宿主语言意义上的完全表达式(@4.4.4.2) ,但在按宿主语言规则判断生存期时,使用本机实现(@5.2.1) 的函数合并(@4.5.3.2) 视同宿主语言的完全表达式,其本机函数调用不引起函数内创建的对象的生存期被延长。
1653-临时对象(@5.5.6) 的生存期(@5.2.4.1) 同时约束隐含的隐式宿主函数调用(如复制构造)。
1654-为保证求值表达式取得的临时对象的内存安全(@5.2.4.3) ,函数合并同时满足以下规则:
1828+NPLA 不存在宿主语言意义上的完全表达式(@4.4.4.2) ,但在按宿主语言规则判断生存期时,使用本机实现(@5.3) 的函数合并(@4.5.3.2) 视同宿主语言的完全表达式,其本机函数调用不引起函数内创建的对象的生存期被延长。
1829+临时对象(@5.8.5) 的生存期(@5.6.1) 同时约束隐含的隐式宿主函数调用(如复制构造)。
1830+为保证求值表达式取得的临时对象的内存安全(@5.6.3) ,函数合并同时满足以下规则:
16551831 操作符(@4.4.3.1) 和未被求值的操作数的直接或间接子表达式关联的对象以及求值操作数的子表达式引入的临时对象的生存期结束的作用应不后序(@4.4.1) 于活动调用(@4.5.3.1) 结束。
16561832 这同时确保引入临时对象时,其生存期不会任意地被延长(超过函数合并表达式的求值)。
16571833 具体操作可在以上约束下指定被求值的操作数可能引入的临时对象的生存期。
16581834 生存期起始和结束的顺序被确定(determined) 时,和对应所在的表达求值之间的先序(@4.4.1) 关系同构;
16591835 否则,其顺序满足非决定性有序(@4.4.1) 关系。
16601836
1661-@5.2.6 尾上下文约定:
1837+@5.10 尾上下文约定:
16621838 尾上下文(@4.4.7) 在 NPLA 中可满足一些附加的性质。
16631839
1664-@5.2.6.1 真尾规约(proper tail reduction) :
1840+@5.10.1 真尾规约(proper tail reduction) :
16651841 尾上下文涉及的存储在特定情况下满足调用消耗的空间有上界(即空间复杂度 O(1) )。
16661842 满足这种情况下的规约称为真尾规约。
16671843
1668-@5.2.6.2 尾调用(tail call) 和 PTC(proper tail call ,真尾调用):
1844+@5.10.2 尾调用(tail call) 和 PTC(proper tail call ,真尾调用):
16691845 在尾上下文规约的调用(@4.5.3.1) 是尾调用。
1670-以真尾规约(@5.2.6.1) 的实现尾调用允许具有不限定数量的(unbounded) 活动调用(@4.5.3.1) ,称为 PTC 。
1846+以真尾规约(@5.10.1) 的实现尾调用允许具有不限定数量的(unbounded) 活动调用(@4.5.3.1) ,称为 PTC 。
16711847 PTC 占用活动记录(@4.5.3.4) 满足真尾规约的上界的要求。
16721848 当宿主语言提供函数调用支持 PTC 时,可直接使用宿主语言的 PTC 调用,否则,需要使用其它替代实现机制确保 PTC 。
16731849 注意非对象语言的调用的上下文中,若被调用时间接使用,也仍需要保证 PTC 。
16741850 PTC 确保仅有一个活动(@4.5.3.1) 的调用。不满足 PTC 的情形下,语言没有提供用户访问非活动记录帧(@4.5.3.4) 资源的手段,因此可以认为是资源泄漏。但为简化语义规则,NPLA 不要求避免相关的弱内存泄漏(5.2.2.2) 。
16751851 NPL 不保证一般对象存在引用(@4.2.3) ,NPLA 也不添加保证活动记录的帧(@4.5.3.4) 中保存引用,销毁活动记录的帧可能影响环境中的变量生存期而改变语义;因此 NPLA 不保证 PTC 支持,实现一般的 PTC 依赖派生实现定义的附加规则。
1676-为满足 PTC ,在 @5.2.5 的基础上,尾上下文内的可以确定(@5.2.5) 并调整对象生存期结束时机:
1677-作为临时对象(@5.5.6) 的合并子及其参数可以延长生存期至多到尾上下文结束;
1852+为满足 PTC ,在 @5.9 的基础上,尾上下文内的可以确定(@5.9) 并调整对象生存期结束时机:
1853+作为临时对象(@5.8.5) 的合并子及其参数可以延长生存期至多到尾上下文结束;
16781854 被证明不再需要之后引用的对象,或未被绑定到活动记录上的项中的对象,可以缩短生存期;
16791855 被延长生存期的对象生存期起始和结束的相对顺序保持不变;
16801856 被缩短生存期的不同对象生存期结束的相对顺序保持不变。
1681-推论:被缩短生存期和延长生存期的对象的生存期结束的相对顺序保持不变。这由没有被调整生存期的对象与被调整生存期对象之间的生存期结束的顺序关系(@5.2.6) 的传递性保证。
1857+推论:被缩短生存期和延长生存期的对象的生存期结束的相对顺序保持不变。这由没有被调整生存期的对象与被调整生存期对象之间的生存期结束的顺序关系(@5.10) 的传递性保证。
16821858 延长临时对象生存期和宿主语言中允许扩展非完全表达式内的临时对象的效果类似,但条件不同。
1683-注意,理论上 PTC 不要求延长生存期,仅要求特定情形下缩短生存期,且其它情形被释放的对象生存期不延长到尾上下文外;延长生存期是 @5.2.5 的推论。
1684-
1685-@5.2.6.3 PTR(proper tail recursion ,真尾递归):
1859+注意,理论上 PTC 不要求延长生存期,仅要求特定情形下缩短生存期,且其它情形被释放的对象生存期不延长到尾上下文外;延长生存期是 @5.9 的推论。
1860+
1861+@5.10.3 PTR(proper tail recursion ,真尾递归):
16861862 PTC 的活动记录性质也在一般的递归规约时体现,被称为 PTR 。
16871863 和 PTC 不同,PTR 要求的递归规约不一定是对象语言中的调用,以 PTR 描述时仅强调递归,不考虑尾上下文的适用性。
16881864 通过特定的保持语义等价的变换,对象语言可要求尾上下文作用于函数调用以外的上下文中(例如非函数合并表达式(@4.5.3) 的语法上下文)使用真尾规约实现。
@@ -1693,55 +1869,97 @@
16931869 http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.83.8567&rep=rep1&type=pdf
16941870 [Cl98]
16951871 https://www.akalin.com/evlis-tail-recursion
1696-因为本设计使用链接的环境(@5.4.3) ,不支持实现其中更激进的 safe-for-space 保证。
1697-
1698-@5.2.6.4 TCO(Tail Call Optimization ,尾调用优化):
1872+因为本设计使用链接的环境(@5.7.1) ,不支持实现其中更激进的 safe-for-space 保证。
1873+
1874+@5.10.4 TCO(Tail Call Optimization ,尾调用优化):
16991875 TCO 是在以尾上下文规约时,允许减少修改规约状态的优化。
17001876 一般地,TCO 可重新排列规约过程中的被语义允许调整的副作用和其它不影响可观察行为的状态(@4.1.3) 的调用,减小空间开销。
1701-TCO 的这种性质可以在宿主语言不支持 PTC(@5.2.6.2) 时用于实现对象语言的 PTC(@5.2.6.4.1) 。
1877+TCO 的这种性质可以在宿主语言不支持 PTC(@5.10.2) 时用于实现对象语言的 PTC(@5.10.4.1) 。
17021878 关于 TCO 和 PTC 的差异,另见 https://groups.google.com/d/msg/comp.lang.lisp/AezzhxTliME/2Zsq7HUn_ssJ 。
17031879
1704-@5.2.6.4.1 宿主语言中立:
1880+@5.10.4.1 宿主语言中立:
17051881 C++ 不要求实现支持 PTC ,也不保证支持 TCO 。因此,对象语言的 PTC 要求显式的 TCO 实现。
1706-为可移植地支持 TCO ,NPLA 不依赖宿主语言中不可移植的互操作(@5.2.1) 的活动记录(通常是体系结构相关的栈)。
1707-注意尾调用可避免尾上下文中非嵌套调用安全的情形的宿主语言实现的未定义行为(@5.2.7) ,但不保证非尾上下文中具有类似的性质。
1708-
1709-@5.2.6.4.2 策略实现概述:
1882+为可移植地支持 TCO ,NPLA 不依赖宿主语言中不可移植的互操作(@5.3) 的活动记录(通常是体系结构相关的栈)。
1883+注意尾调用可避免尾上下文中非嵌套调用安全的情形的宿主语言实现的未定义行为(@5.11) ,但不保证非尾上下文中具有类似的性质。
1884+
1885+@5.10.4.2 策略实现概述:
17101886 TCO 包括以下形式:
17111887 静态 TCO :实现时替换宿主语言中不保证满足 PTC 的构造为满足 PTC 的构造;
17121888 动态 TCO :运行时调整直接或间接表示对象语言构造的数据结构和状态,使状态占用的空间复杂度满足 PTC 要求。
1713-静态 TCO 也适合非对象语言的调用的上下文(@5.2.6.2) 。
1889+静态 TCO 也适合非对象语言的调用的上下文(@5.10.2) 。
17141890 不依赖宿主语言特性的静态 TCO 包括以下形式:
17151891 替换宿主语言实现中的不保证满足 PTC 的递归调用为满足 PTC 的结构(如循环结构),包括直接编码和自动的变换(transformation) ,称为宿主(host) TCO ;
17161892 替换不满足 PTC 的对象语言原语为满足 PTC 的表达形式,称为目标(target) TCO 。
17171893 不依赖宿主语言特性的动态 TCO 包括以下形式:
17181894 通过合并不同活动调用(@4.5.3.1) 中活动记录占用的冗余状态,减少宿主语言的活动调用同时占用的总空间,称为 TCM(Tail Call Merging ,尾调用合并);
17191895 引入具有便于操作控制作用(@2.3.4) 的构造,同时作为一些其它优化的基础,以消除部分活动记录状态的分配,称为 TCE(Tail Call Elimination ,尾调用消除)。
1720-TCM 一般只减小尾调用(@5.2.6) 需要占用的活动记录,而 TCE 可能直接替换控制结构,同时减小运行操作需要的时间开销。但 TCE 变换自身可能需要额外开销且实现较复杂。因此,当前 NPLA 只支持 TCM 方式的 TCO 。
1896+TCM 一般只减小尾调用(@5.10) 需要占用的活动记录,而 TCE 可能直接替换控制结构,同时减小运行操作需要的时间开销。但 TCE 变换自身可能需要额外开销且实现较复杂。因此,当前 NPLA 只支持 TCM 方式的 TCO 。
17211897 因为只使用 TCM ,相对没有 TCO 的基线版本,无法保证具有可预期的时间性能提升;相反,引入 TCM 可因为增加运行时存储分配和缓存局域性等原因降低性能(但一般实现应不影响复杂度类而只降低可接受的常数)。
1722-TCM 引入的构造通常基于对象语言的内部变换。静态 TCO 使用的对宿主语言的自动变换和这种内部变换可基于相同的策略,如通过对程序全局变换得到的 CPS(@5.4.4) 或 ANF(Adminstrative Norm Form) )的中间表示(@2.4.1) 。
1723-当尾上下文规约涉及的存储(主要是活动记录(@4.5.3.4) )被一并消除时,调用消耗的空间复杂度有静态上界,满足 PTC(@5.2.6.2) 。
1724-
1725-@5.2.7 嵌套调用安全:
1726-宿主语言的 API 提供嵌套调用安全,当且仅当若非调用没有宿主语言无法分配资源的未定义行为(@5.2.2) ,则同时避免嵌套调用深度过大时引起这样的未定义行为。
1727-嵌套调用安全应包括支持 @5.2.6.4.1 不保证的情形。
1898+TCM 引入的构造通常基于对象语言的内部变换。静态 TCO 使用的对宿主语言的自动变换和这种内部变换可基于相同的策略,如通过对程序全局变换得到的 CPS(@6.8.3) 或 ANF(Adminstrative Norm Form) )的中间表示(@2.4.1) 。
1899+当尾上下文规约涉及的存储(主要是活动记录(@4.5.3.4) )被一并消除时,调用消耗的空间复杂度有静态上界,满足 PTC(@5.10.2) 。
1900+
1901+@5.11 嵌套调用安全:
1902+宿主语言的 API 提供嵌套调用安全,当且仅当若非调用没有宿主语言无法分配资源的未定义行为(@5.4) ,则同时避免嵌套调用深度过大时引起这样的未定义行为。
1903+嵌套调用安全应包括支持 @5.10.4.1 不保证的情形。
17281904 嵌套调用安全允许不限制嵌套深度的可靠的调用,如递归调用。
1729-宿主语言实现在宿主语言的尾上下文可能支持宿主 TCO(@5.2.6.4.2) 而使递归调用满足嵌套调用安全,但这并不是语言提供的保证,不应在可移植的实现中依赖。
1905+宿主语言实现在宿主语言的尾上下文可能支持宿主 TCO(@5.10.4.2) 而使递归调用满足嵌套调用安全,但这并不是语言提供的保证,不应在可移植的实现中依赖。
17301906 非嵌套调用安全的情形在过程嵌套调用深度过大时,可因为宿主语言的存储资源消耗导致的宿主语言实现的未定义行为,典型地包括实现中的栈溢出(stack overflow) 。
17311907
1732-@5.3 NPLA 实现架构:
1733-NPLA 实现为对数据结构的管道-过滤器(pipe-filter) 架构模式的处理框架。每个处理节点实现一个或若干个阶段(@2.4.1) 。
1908+@6 NPLA 实现:
1909+本章描述 NPLA(@5) 约定下的对象语言的部分具体实现。
1910+
1911+@6.1 实现方案概述:
1912+NPLA 实现支持可扩展的 C++ API ,作为解释器(interpreter) 的实现基础。
1913+本方案中,除非另行指定(如总体架构(@6.1.4) 中的描述),API 在模块 NPLA 提供。
1914+
1915+@6.1.1 实现支持策略:
1916+解释器的程序执以对语法分析(@6.1.6) 结果的规约实现,可对应语言或(适合改进执行性能的)某种等价变体的小步语义(@2.2.3) 。
1917+在此基础上,可允许派生实现优化性能,如嵌入编译。
1918+NPLA API 公开解释器的状态,可支持和宿主语言的互操作(@2.4.1) 。
1919+虽然 NPLA 的设计(@5) 没有给出形式语义(@2.2.3) ,但 NPLA 实现中包含的 API 部分地提供和形式语义方法对应的支持:
1920+通过回调对应依赖宿主语言(@5.2) 实现作为描述的指称语义;
1921+在回调内对项和上下文进行操作,对应小步语义;
1922+在回调内复用其它接口,对应大步语义。
1923+NPLA 可支持非固定的规约规则集合,以 API 的形式体现,详见规约 API(@6.7) ;另见表达式的求值(@6.3) 。
1924+部分实现的功能由公共 API 的形式提供,以便派生实现复用。本章约定这些 NPLA 公共语言实现接口。
1925+用于 NPLA1 应用实例(@6.11.2) 和 NPLA 其它实现应用实例(@6.11.3) 等部分不直接用于 NPLA 解释实现的 API 可能在本章中被略过。
1926+具体实现的编码风格导引参见 [Documentation::CommonRules @@6] 。
1927+基于 NPLA API 的派生语言的设计详见 NPLA1(@7) 。
1928+
1929+@6.1.2 实现选项:
1930+NPLA 实现中提供构建时确定的实现选项,以宿主语言的宏定义在构建时启用,以宿主语言条件包含的 #if 指令判断。
1931+
1932+@6.1.2.1 运行时检查:
1933+当前实现选项用于指定启用检查:
1934+NPL_NPLA_CheckEnvironmentReferenceCount 环境对象销毁时检查环境引用计数。
1935+NPL_NPLA_CheckParentEnvironment 默认解析父环境(@5.7) 时,检查环境引用是否存在。
1936+要求环境弱引用锁定的强引用指针非空。
1937+NPL_NPLA_CheckTermReferenceIndirection 项引用(@6.6.3.3) 的间接操作(@6.6.5) 时检查关联环境(@6.6.3.3.2) 。
1938+若关联环境,要求关联环境的锚对象指针(@6.6.2.2) 和环境弱引用锁定的强引用指针非空。无法确保没有关联环境的非内存安全操作(@5.6.3.1) 中的引用有效性。
1939+启用以上检查可避免引起不满足检查条件时的一些宿主语言的未定义行为(@5.4) ,即便已引起 NPLA 和对象语言的未定义行为(@5.6.3.1) 。
1940+
1941+@6.1.3 安全保证:
1942+除以下例外,NPLA 实现及对应可能由用户提供的替换的实现应支持嵌套调用安全(@5.11) :
1943+直接递归遍历节点的操作如 NPL::SetContentWith(@6.2.3) 和 NPL::TokenizeTerm(@6.6.4) ;
1944+因为直接或间接调用上述遍历节点实现的操作,详见不支持嵌套调用安全的提升操作(@6.6.7.6) ;
1945+派生实现定义的情形。
1946+
1947+@6.1.4 总体架构:
1948+NPLA 核心实现为对数据结构的管道-过滤器(pipe-filter) 架构模式的处理框架。每个处理节点实现一个或若干个阶段(@2.4.1) 。
17341949 这里的数据结构是语言实现的 IR(@2.4.1) 或通过代码生成(@2.4.1) 得到的代码。在 NPLA 实现中后者是可选的。
1735-本节指定首先经过两个前端(frontend) ,之后的处理见 @5.4 。
1736-本章下文中,除 @5.3 和 @5.4 在 C++ 命名空间 NPL 中引入的类型名称和如下列出的使用 C++ 命名空间 YSLib 的名称外,其余 C++ 名称使用 C++ 限定名称以避免混淆:
1950+本节下文首先指定翻译单元经过词法分析(@6.1.5) 和语法分析(@6.1.6) 这两个前端(frontend) ,之后的处理见本章中的其余节。
1951+除非另行指定,本节中的 API 都位于模块 NPLA ,详见 NPLA 公共语言实现接口。
1952+本章下文中,除本节和本节在 C++ 命名空间 NPL 中引入的类型名称以及如下列出的使用 C++ 命名空间 YSLib 的名称外,其余 C++ 名称使用限定名称(带有 :: )以避免混淆:
17371953 shared_ptr
17381954 observer_ptr
17391955 weak_ptr
17401956 本章下文中,项目的模块命名空间([ProjectRules @@3.2]) 名称若为 NPL 模块,省略 NPL 前缀;其它 YSLib 模块需使用限定名称;非 YSLib 模块还需指定库的来源。
17411957 本章下文中,使用 namespace 关键字指定 C++ 命名空间。
17421958
1743-@5.3.1 词法分析:
1744-参见 @3.3.4 和参考实现模块 Lexical 。
1959+@6.1.5 词法分析:
1960+词法分析以源代码为输入(@5.2.2) ,遵循词法分析规则(@3.3.4) 输出分析的记号序列(@2.4.1) 。
1961+本设计提供为参考实现模块 Lexical 。
1962+词法分析实现可隐含确定关于每个名称的声明区域(@4.1) 有效的代码片段的最大位置范围;但 NPLA 设计(@5.1) 没有要求这种行为在对象语言中可见,因此简化起见,Lexical 不包含对应实现。
17451963 Lexical 模块在 namespace NPL 通过别名声明引入 YSLib::string 类型和 YSLib::string_view 类型。
17461964 Lexical 模块的词法分析器支持分析表示 S 表达式的文本流,但源语言中不包含点(dot) 记号,且支持同宿主语言相同的断行连接和转义字符。和宿主语言不同,不支持的转义前的 \ 不需要被重复,且不支持字符串字面量的直接并置连接。
17471965 词法分析使用二进制模式处理文本,字符序列被逐字节输入。CR(carriage return) 和 LF(line feed) 预期以单字节编码。
@@ -1749,7 +1967,7 @@
17491967 词法分析的结果是记号的序列,以记号列表类型 TokenList 表示。
17501968 Lexical 模块提供类型别名和以下类型:
17511969 LexemeList :词素(@3.3.1) 列表。
1752-词素列表中的元素是在词法分析阶段中表示记号(@3.3.1) 的短字符串。虽然逻辑上并不依赖,但为便于实现,提供分配器(@5.7.1) 支持。
1970+词素列表中的元素是在词法分析阶段中表示记号(@3.3.1) 的短字符串。虽然逻辑上并不依赖,但为便于实现,提供分配器(@6.4.1) 支持。
17531971 SourceLocation :源代码位置。
17541972 词法分析提供以下元函数访问词法解析器中的类型:
17551973 MemberParseResult
@@ -1762,71 +1980,66 @@
17621980 DelimitedByteParser
17631981 其中,DelimitedByteParser 保留更多的中间结果。
17641982
1765-@5.3.2 语法分析:
1766-参考实现模块 SContext ,提供源代码的文本流或其词法分析结果(@5.3.1) 转换到分析结果的转换接口。
1983+@6.1.6 语法分析:
1984+参考实现模块 SContext ,提供源代码的文本流或其词法分析结果(@6.1.5) 转换到分析结果的转换接口。
17671985 分析结果是结构化的递归数据结构,称为分析树(parse tree) 。除非派生实现另行指定,分析树中的节点应同构 NPL 语法(@3.4) 的非终结符(non-terminal) 文法元素。
1768-语法分析通过调用词法分析转换源代码为 TokenList 类型(@5.3.1) 表示的记号列表结果,并在此之上递归解析取得记号序列对应的语法对象(syntax object) ,然后从语法对象构造分析树。
1986+语法分析通过调用词法分析转换源代码为 TokenList 类型(@6.1.5) 表示的记号列表结果,并在此之上递归解析取得记号序列对应的语法对象(syntax object) ,然后从语法对象构造分析树。
17691987 除包含构造分析树的必要信息,语法对象还可能包含来自源代码的附加元数据,如分析结果对应的源代码位置信息。当前 SContext 的语法对象不包含这些附加的元数据,因此和分析树同构。
17701988 分析树包含源代码中语法规则(包括 @3.4 和派生实现定义的其它扩展)关注的所有语法信息,也被称为具体语法树(concrete syntax tree) 。
17711989 与之相对,以 AST(Abstract Syntax Tree ,抽象语法树)作为保存分析结果的 IR 排除了之后的阶段中不需要的信息。
1772-因为 SContext 输入的形式是和 Lexical 模块(@5.3.1) 兼容的 S 表达式,分析树和 AST 同构,不需要保持冗余信息。因此 SContext 只一种提供同时表示分析树和 AST 的数据结构。派生实现可根据扩展语法规则的需要定义不同的分析树。
1773-对语法分析的递归操作应在分配资源失败应满足常规宿主资源分配要求(@5.2.2.1) 。
1990+因为 SContext 输入的形式是和 Lexical 模块(@6.1.5) 兼容的 S 表达式,分析树和 AST 同构,不需要保持冗余信息。因此 SContext 只一种提供同时表示分析树和 AST 的数据结构。派生实现可根据扩展语法规则的需要定义不同的分析树。
1991+对语法分析的递归操作应在分配资源失败应满足常规宿主资源分配要求(@5.4.1) 。
17741992 使用 AST 作为 FOAS(First-Order Abstract Syntax)不需要保留优先级等冗余信息。
17751993 派生实现可能检查更多语法规则。
17761994 SContext 模块在 namespace NPL 通过别名声明引入若干 YSLib 类型名称,包括(但不限于):
17771995 ValueObject 、ValueNode 、observer_ptr 和 LoggedEvent 。
17781996 关于这些类型,另见 [Documentation::YSLib @@3.16] 和 [Documentation::YSLib @@3.2] 。
1779-语法分析提供 TermNode 类型(@5.4.2) 作为表示分析树(根据以上讨论,同时也是语法对象和后续使用的 AST )的处理结果类型,及其相关操作。
1997+语法分析提供 TermNode 类型(@6.2) 作为表示分析树(根据以上讨论,同时也是语法对象和后续使用的 AST )的处理结果类型,及其相关操作。
17801998 以下类型和 TermNode 关联:
17811999 TNCIter
17822000 TNIter
17832001 TermByteAllocator
1784-SContext 模块的其它 API,详见以下小节和 @6.2.1 。
1785-
1786-@5.3.2.1 词素(@3.3.1) 处理:
2002+SContext 模块的其它 API,详见以下小节和 @6.2.3 。
2003+
2004+@6.1.6.1 词素(@3.3.1) 处理:
17872005 以下命名空间 NPL 中的 API 提供词素相关的转换:
17882006 函数模板和函数 ToLexeme
17892007 仿函数 LexemeTokenizer
17902008
1791-@5.3.2.2 读取器(reader) 状态:
2009+@6.1.6.2 读取器(reader) 状态:
17922010 类 ReaderState 保存辅助读取列表表达式边界标点的状态。
17932011
1794-@5.3.2.3 NPL 源代码分析会话(session) :
2012+@6.1.6.3 NPL 源代码分析会话(session) :
17952013 语法分析提供类 Session 表示分析处理一个翻译单元(@3.1) 的 NPL 源代码的会话。
1796-会话利用词法分析器提取源代码中可被分析的信息,并提供使用指定词法解析器(@5.3.1) 取解析结果的 API 。
1797-若不指定词法解析器,默认使用的词法解析器是 ByteParser(@5.3.1) 。
1798-会话的输出应满足可被 @5.4.1 表达的形式。这限制了可识别的外部表示(@2.3.4) 。
2014+会话利用词法分析器提取源代码中可被分析的信息,并提供使用指定词法解析器(@6.1.5) 取解析结果的 API 。
2015+若不指定词法解析器,默认使用的词法解析器是 ByteParser(@6.1.5) 。
2016+会话的输出应满足可被 @6.1.7 表达的形式。这限制了可识别的外部表示(@2.3.4) 。
17992017 由 @4.1.1 ,这限定了翻译单元(@3.1) 的组成。例如,翻译单元中的源代码(@4.1.1) 一般应能对应语法上完整的表达式(@3.4.2) 。
18002018
1801-@5.3.2.4 命名空间 NPL::SContext :
2019+@6.1.6.4 命名空间 NPL::SContext :
18022020 命名空间 NPL::SContext 提供转换节点相关数据和遍历节点的 API 。
18032021 以下命名空间 NPL::SContext 中的 API 递归遍历参数指定的记号范围以转换其中的结构化数据为表示的语法树:
18042022 函数模板 Validate
18052023 函数模板 Reduce
18062024 函数和函数模板 Analyze
18072025
1808-@5.4 NPLA 公共语言实现数据结构:
1809-本节到 @5.8 节之间的内容外其它 NPLA 实现使用的接口详见模块 NPLA 和以下章节(@6) 。
1810-本节中的 API 都位于模块 NPLA ,详见 NPLA 公共语言实现接口(@6) 。
1811-另见其它实现应用实例(@5.11.3) 。
1812-
1813-@5.4.1 类型映射(@5.2.3) 实现:
2026+@6.1.7 类型映射(@5.5) 实现:
18142027 类型映射使用的 C++ 类型在 namespace NPL 中声明,从 namespace YSLib 引入。
1815-一些类型如 string 、ValueNode 和 ValueObject 在词法分析(@5.3.1) 和语法分析(@5.3.2) API 中引入,被以下章节的实现使用,成为实际的映射目标。
1816-以下章节中,IR 节点数据结构(@5.4.2) 和 NPLA 中间值(@5.6) 提供的节点或中间值(@4.4.4.2) 类型(thunk type) 是类型映射的目标(@5.4.1) 。
1817-
1818-@5.4.2 IR 节点数据结构:
2028+一些类型如 string 、ValueNode 和 ValueObject 在词法分析(@6.1.5) 和语法分析(@6.1.6) API 中引入,被以下章节的实现使用,成为实际的映射目标。
2029+以下章节中,IR 节点数据结构(@6.2) 和 NPLA 中间值(@6.6.3) 提供的节点或中间值(@4.4.4.2) 类型(thunk type) 是类型映射的目标(@6.1.7) 。
2030+
2031+@6.2 IR 节点数据结构:
18192032 NPLA 实现使用节点(node) 数据结构表示实现使用的 IR 中间表示递归的构造,如 SContext 产生的 AST 的节点和语义分析使用的项。
1820-这样的节点类型为 TermNode ,也用于表示单一的树(如整个 AST(@5.3.2) )。
2033+这样的节点类型为 TermNode ,也用于表示单一的树(如整个 AST(@6.1.6) )。
18212034 NPLA 实现语义规则时对 TermNode 进行处理,包括节点上的规约(@4.1) ,即树规约(tree reduction) 。TermNode 对象在树规约中表示被规约项(reduced term) 。
1822-树规约的输入和各个规约步骤的结果中,变量不需要直接被表示,规约允许和输入 AST 中一致的未求值的名称,参见 @5.6.1 ;同时,允许在同一个树中包含经不同过程转换的结果,不需要显式区分阶段(@2.4.1) 。
2035+树规约的输入和各个规约步骤的结果中,变量不需要直接被表示,规约允许和输入 AST 中一致的未求值的名称,参见 @6.6.3.1 ;同时,允许在同一个树中包含经不同过程转换的结果,不需要显式区分阶段(@2.4.1) 。
18232036 因为绑定(@4.1) 的表示方法不在 IR 内特设指定,树规约不需要使用 HOAS(Higher-Order Abstract Syntax ,高阶抽象语法)。接受表达式到表达式的映射附加处理可通过派生实现约定对象语言中特定类型的对象实现,不要求被编码在规约的结果中。
1824-名称和作用域通过求值环境(@4.6.1.1) 维护,一般保存在环境数据结构(@5.4.3) 中。
2037+通过求值环境(@5.7) 维护的名称和作用域保存在环境数据结构(@6.8.1) 中。
18252038 TermNode 中保存子节点的容器。当作为被规约项时,TermNode 的子节点对象是子项(@4.1) 的表示(@2.3.4) 。
18262039 TermNode 的 Value 数据成员称为值数据成员(value data member) ,是 ValueObject 类型的对象。用于表示对象语言( NPLA 实现)中表达式或对象储存的值,或者包装的中间值(@4.4.4.2) 。
18272040 TermNode 对子节点和值数据成员具有直接的独占所有权。
18282041 作为子节点的容器,TermNode 子节点的迭代器、指针和引用保持稳定,即移除子节点时不无效化(invalidate) 其它项的迭代器、指针和引用。
1829-TermNode 子节点的稳定性也支持项被转移时(如维护临时对象的内部存储(@5.2.4.2) )不需要附加操作维护已在其它位置被引用的子项有效性。
2042+TermNode 子节点的稳定性也支持项被转移时(如维护临时对象的内部存储(@5.6.2) )不需要附加操作维护已在其它位置被引用的子项有效性。
18302043 针对 TermNode 的一些操作如移除第一个子节点的 RemoveHead 一般使用 ADL 方式调用。
18312044 树规约可按需添加或删除 TermNode 的子节点。具体添加或删除的时机未指定,取决于具体的规约算法。
18322045 除非派生实现另行指定,删除值数据成员和删除子节点的作用非决定性有序(@4.4.1) 。
@@ -1834,77 +2047,796 @@
18342047 TermNode 中存储的值可引用其它节点共享部分数据形成 DAG(@4.2.4) 。当前节点不直接被共享,值数据成员可能共享数据。
18352048 因为节点可能被共享,在被引用节点的意义上的规约为图规约(tree reduction) ,但这不属于一般的规约规则。
18362049 注意实现应避免形成非 DAG 的共享,以保证不出现资源的所有权(@4.2.2.3) 的冲突。
1837-使用 TermNode 进行处理时,若子节点的分配器(@5.7.1) 和所在的项的分配器不相等,则涉及子节点容器的交换操作(包括 swap 及 TermNode::SwapContent )的行为未定义。
2050+使用 TermNode 进行处理时,若子节点的分配器(@6.4.1) 和所在的项的分配器不相等,则涉及子节点容器的交换操作(包括 swap 及 TermNode::SwapContent )的行为未定义。
18382051 除非另行指定,NPLA 及其派生实现的使用 TermNode 的 API 隐含上述关于分配器相等性的前置条件。
18392052
1840-@5.4.2.1 项节点结构分类:
2053+@6.2.1 项节点结构分类:
18412054 节点容器的内容被视为子节点按迭代顺序确定的有限的序列,即真列表(proper list) 。
18422055 真列表不包含环(cycle) 。其它形式的列表(list) 数据结构,如 Scheme 和 Kernel 中可能有环的非真列表(improper list) 不被支持。
18432056 按内容的结构,项节点具有如下互斥的基本分类(使用 TermNode 的 empty() 判断):
1844-枝节点(branch node) 或非叶节点,即具有子节点的节点,表示非空列表或同时具有子节点和叶节点的非正规(@5.4.2.1) 的非列表;
1845-叶节点(leaf node) ,即不具有子节点的节点,表示空列表或不具有子节点的正规的(@5.4.2.1) 非列表。
2057+枝节点(branch node) 或非叶节点,即具有子节点的节点,表示非空列表或同时具有子节点和叶节点的非正规(@6.2.1) 的非列表;
2058+叶节点(leaf node) ,即不具有子节点的节点,表示空列表或不具有子节点的正规的(@6.2.1) 非列表。
18462059 按表示的内容,项节点可进一步使用其它谓词判断其分类:
1847-列表节点(list node) 是值数据成员(@5.4.2) 为空的节点;
2060+列表节点(list node) 是值数据成员(@6.2) 为空的节点;
18482061 空节点(empty node) 同时是叶节点和列表节点;
18492062 分支列表节点(branched list node) 同时是枝节点和列表节点;
18502063 扩展列表节点(extended list node) 是枝节点或列表节点。
18512064 注意扩展的列表节点可能同时具有子节点和非空的值数据成员。
18522065 进一步地,节点可按子节点数(使用 TermNode 的 size() 结果判断)按需进行扩展分类。
1853-列表节点对应对象语言的列表类型。作为列表节点的 TermNode 是对象语言中列表类型的宿主类型(@5.2.3) 。
2066+列表节点对应对象语言的列表类型。作为列表节点的 TermNode 是对象语言中列表类型的宿主类型(@5.5) 。
18542067 考虑 TermNode 的值数据成员是否为空及其实际持有对象的动态类型等,具体实现 API 可约定使用不同的具体结构分类。
18552068 因为次级分类不一定是互斥的,所以可能需要约定对节点的操作顺序以确保结果一致,作为被规约项时典型地可能有以下几种情形:
18562069 先判断是否为枝节点,再判断是否为符合预期类型的非空叶节点,分派不同的操作;
1857-根据项表示的值(@4.1) 考虑子节点及值数据成员(关于规约后的一般表示,参见 @5.9 )。
1858-
1859-@5.4.2.2 项的标签(tag) :
2070+根据项表示的值(@4.1) 考虑子节点及值数据成员(关于规约后的一般表示,参见 @6.7.7 )。
2071+
2072+@6.2.2 项的标签(tag) :
18602073 枚举 TermTagIndices 和枚举 TermTags 作为索引和对应的掩码提供项使用的标签作为元数据。
18612074 每个项中附加 TermTags 的标签数据供用户程序使用。
18622075 TermTags 中的枚举项表示的标签主要有:
18632076 Unqualified :非限定对象。默认值。
18642077 Unique :唯一引用。
18652078 Nonmodifying :不可修改。
1866-Temporary :临时对象(@5.5.6) 。
1867-默认值外仅用于 NPLA 项引用(@5.6.3) 及其元数据(@5.6.3.1) 和派生实现。
2079+Temporary :临时对象(@5.8.5) 。
2080+默认值外仅用于 NPLA 项引用(@6.6.3.3) 及其元数据(@6.6.3.3.1) 和派生实现。
18682081 在 TermTagIndices 中的枚举项带有 Index 后缀,和这些枚举项一一对应。
1869-项的标签指定通过和标签关联的值访问的对象假定允许具有的性质。除非派生实现另行指定,违反这些假定不引起 NPLA 未定义行为(@5.2.2) 。
1870-唯一引用允许通过通过具有唯一引用的项或项引用(@5.6.3) 访问关联的对象,对象可被假定不被其它引用而仅通过这个途径访问,即便实际存在其它途径的引用时可能引起不同的行为;在假定的基础上程序具有何种可能的行为是未指定的。
1871-唯一引用允许实现做出类似 ISO C 约定的 restrict 关键字允许的假定,但程序违反假定的约束时不引起未定义行为。
2082+项的标签指定通过和标签关联的值访问的对象假定允许具有的性质。除非派生实现另行指定,违反这些假定不引起 NPLA 未定义行为(@5.4) 。
2083+唯一引用允许通过通过具有唯一引用的项或项引用(@6.6.3.3) 访问关联的对象,对象可被假定不被其它引用而仅通过这个途径访问,即便实际存在其它途径的引用时可能引起不同的行为;在假定的基础上程序具有何种可能的行为是未指定的。
2084+唯一引用允许实现做出类似 ISO C 约定的 restrict 关键字允许的假定,即引用不被共享,被引用对象不被别名(@4.2.3.4.3) ,但程序违反假定的约束时不引起未定义行为。
2085+和 ISO C++ 核心语言(但不是 [res.on.arguments] 中的标准库绑定到右值引用实际参数的约定)的右值引用类似,唯一引用不总是表示被引用对象非共享。接受唯一引用的操作可能只假定被引用对象的子对象是不被共享,也可能完全不做假定,这依赖具体操作的语义。若需要和具体操作无关的无条件非共享假定,使用纯右值(@5.8.1) 而非作为左值的唯一引用。
2086+若实质化转换上下文(@5.8.5.1) 支持绑定临时对象,按引用绑定的对象具有临时对象标签(@6.2.2) 。引入引用值的形式参数的具体形式由派生实现指定。
18722087 类似地,和宿主语言不同,违反不可修改标签引入的假定不引起未定义行为。
18732088
1874-@5.4.3 环境数据结构:
1875-求值环境(@4.6.1.1) 以 Environment(@6.9.1) 类相关的类型表示。
1876-环境对象包含变量名称到表示被绑定对象(bound object) 的映射,称为名称绑定映射(name binding map) 实现变量绑定(@4.1) 。
1877-名称绑定映射对被绑定对象具有所有权。
1878-推论:环境对象对被绑定对象具有所有权。
1879-TermNode 对象在名称绑定映射中表示被绑定对象。
2089+@6.2.3 项节点访问:
2090+模块 SContext 提供访问项节点的 API 。
2091+命名空间 NPL 中,TermNode 在类定义外提供关于节点内容的辅助 API 简化操作,以 Doxygen 命令标识为 \relates TermNode 。
2092+其中,判断项节点基本分类(@6.2.1) 的谓词以 Is 前缀起始。
2093+函数模板 NPL::Access 和 NPL::AccessPtr 访问值数据成员(@6.2) 中持有对象的引用或指针。重载兼容 TermNode 和 ValueNode 。
2094+其余 API 包括:
2095+函数模板 NPL::TraverseSubnodes
2096+以上操作通常仅在不涉及语义(仅涉及语法,或更基本的底层操作实现)时使用其访问项中非列表类型(@6.2.1) 的值。
2097+涉及更具体的语义时,使用项访问操作(@6.6.4) 、项引用操作(@6.6.5) 或项引用访问操作(@6.6.6) 的 NPLA API 代替。这些接口提供了确切的项结构检查以符合正规化表示的相关假设(@6.7.9) 。
2098+
2099+@6.3 表达式和求值(@4.4) :
2100+NPLA 表达式及其求值过程中得到的值以若干个 TermNode(@6.2) 、TermNode 子项或值数据成员(@6.2) 表示。
2101+用于表示表达式的 TermNode(@6.2) 对象称为表达式项(expression term) 。
2102+TermNode 的子项(@6.2) 在表达式项中作为子表达式(@3.4.2) 的实现。
2103+一个被求值的表达式以一个作为被规约项(@6.2) 的表达式项表示,称为被求值项。
2104+被求值项经求值规约(@4.4) 取得求值结果(@4.1) 。
2105+求值结果也用 TermNode 对象表示,以支持树规约(@6.2) 中直接基于项的替换操作实现求值。
2106+求值结果可以是具有 TermNode 子项的列表(@6.2.1) 数据结构,详见 @6.7.7 。
2107+TermNode 还可能表示求值的中间结果(即求值规约中取得对象语言表达式的值之前出现的值),如表示某些中间值(@6.6.3) 的情形。
2108+除非中间值也作为一等对象的表示(@2.3.4) ,NPLA 的规约范式(@6.7.3) 不会使用这些形式表示。
2109+NPLA 允许(但不要求对象语言支持)以一等对象作为表达式的表示并被求值。
2110+表达式、表达式在求值过程中的中间表示(如中间值(@4.4.4.2) )和求值结果的表示统称规约表示。
2111+在规约时,上下文(@4.6.1) 指定当前被求值项,确定当前被规约的 TermNode 。
2112+表示值的 TermNode 是宿主语言对象具有的特定动态类型(@4.6.2) 的值是宿主值(hosted value) ,具有不固定的宿主类型(@5.5) 。
2113+宿主值可包括值数据成员和子节点中的各个值数据成员中所有被擦除类型存储的对象,即宿主对象(hosted object) 。
2114+典型情况下,值数据成员对应单一的宿主值。
2115+除非另行指定,类型映射(@5.5) 的值是单一的宿主值。
2116+NPLA 表达式求值取得的范式(@4.4.3) 总是其表示(一个 TermNode 对象)中的子项或值数据成员之一,详见 @6.7.9 。
2117+作为 @4 的扩展,作用于非表达式项上的规约规则不是求值规则。
2118+
2119+@6.3.1 值类别(@5.8.1) :
2120+表达式的值类别也在适用表达式项(@6.3) 及在表达式求值的规约中得到的项。
2121+作为名称表达式(@4.5.1) 明确引用直接存储在环境中的对象及其包含的(数据成员)子对象的项都是左值。
2122+右值一般存储在环境以外的项上;必要时,实现可能存储右值在特定的环境中。
2123+在类型的值的内部表示上,值类别和特定的类型相关:
2124+当前 NPLA 支持的左值都是项引用(@6.6.3.3) ,这同时表示引用值(@6.3.4) 。
2125+另见正规性(@6.7.7) 。
2126+
2127+@6.3.2 项的转移:
2128+求值可引起包含宿主对象的被规约项(@6.2) 被整体转移,而避免其中包含的对象的初始化在对象语言中具有可见的作用,包括:
2129+以 TermNode 对象的形式使其子项和值数据成员被转移(转移后的项具有有效但未指定的状态(@5.6.2) 但不可在对象语言中被访问);
2130+以未指定的其它实现相关的方式使 TermNode 成为其它节点的子项(不存在转移后的项)。
2131+项的转移引起对象的析构性转移(destructive move) :对象在转移后被销毁,其生存期结束。
2132+和对象的转移(@5.8.2.3) 不同,因为被转移的对象的生存期结束,项的转移不保证被转移后的项中的对象的类型不改变。
2133+除了不初始化新的对象和使被转移项中对象的类型改变,项的转移在对象语言中效果可能类似宿主对象的转移。
2134+项的转移保证不无效化(@6.2) 子项,也不转移或复制值数据成员(@6.2) 所有的宿主对象。
2135+被转移对象后的项具有有效但未指定的状态(@5.6.2) ,允许实现为项的转移(@6.3.2) 。
2136+
2137+@6.3.2.1 宿主对象转移消除:
2138+一般地,当对象需要被转移且没有约定转移后要求类型不变(@5.8.2.3) 时,项的转移(@6.3.2) 可代替对象的转移(@5.8.2.3) ,避免初始化新的宿主对象。
2139+项的转移也类似 ValueObject 自身的转移,其实现(当前通过 ValueObject 内部的 ystdex::any 的转移构造函数)保证直接转移对象而不保证转移构造所有的值。
2140+若需调用宿主对象的转移构造函数,需明确避免宿主对象转移消除的上下文中进行操作。派生实现可提供这些操作。
2141+返回值转换上下文(@5.8.5.2) 的转移引起宿主对象转移消除(@6.3.2.1) 。
2142+若被复制消除(@5.8.5.1) 的对象来自不同的项,则复制消除为宿主对象转移消除。这包括所有对象转移的返回值转换上下文的情形。
2143+
2144+@6.3.3 临时对象(@5.8.5) 的表示:
2145+具有特定表示的 NPLA 非一等对象(@4.1) 是临时对象,包括:
2146+所有权可被项独占(@6.4.5) 而不能作为一等对象访问的对象;
2147+内部表示具有临时对象标签(@6.2.2) 的引用对象(@6.3.4) 关联的被引用对象(@4.2.3) 。
2148+其中,直接构成项的对象可以是通过外部表示(@4.1.1) 通过翻译(@2.4.1) 变换得到的具有内部表示的数据结构(@6.2) 的非一等对象。
2149+
2150+@6.3.4 引用值(@5.8.3) 的表示:
2151+引用值可使用以下方式表示:
2152+引用项(reference term) ;
2153+使用 TermTags::Temporary 标签(@6.2.2) 标记的非引用项。
2154+其中,前者是值数据成员为项引用(@6.6.3.3) 对象的项。
2155+引用项中的项引用对象引用一个(其它的)项,用于在必要时引入可被引用的一个项而不在 TermNode 中直接储存这个项的值。
2156+对实现作为一等对象(@4.1) 的列表(@6.2.1) 的引用(@4.2.3) ,支持引用整个项的中间值是必要的;但项引用也支持引用非列表项。
2157+临时对象(@5.8.5) 可作为引用值的被引用对象。
2158+因为临时对象不是一等对象(@5.8.5) ,临时对象的引用值可代替关联的被引用对象使之作为一等对象被访问,因此使用被引用对象添加 TermTags::Temporary(@6.2.2) 标签表示。
2159+与此不同,非临时对象的引用值可作为一等对象而总是需要区分作为不同对象的同一性(@4.1) 。
2160+引用值可能是左值引用(lvalue reference) 或右值引用(rvalue reference) 。
2161+前者是排除引用项表示且不是唯一引用(@6.2.2) 的引用值;后者是以引用项表示的唯一引用或临时对象初始化的非引用项的引用值。
2162+左值引用和左值引用与宿主语言中的对象类型的左值引用与右值引用分别类似。
2163+引用值是否作为左值(@5.8.1.1) 使用取决于上下文。除非另行指定,引用值都是左值。其中,在要求右值的上下文发生左值到右值转换(@5.8.4) 。
2164+
2165+@6.4 资源管理:
2166+NPLA 的实现约定统一的资源管理语义,并以此支持存储和对象模型(@5.6) 。
2167+除非另行指定(如明确资源被共享使用),NPLA API 提供类型的对象按值传递(@4.4.4.5) 传递资源所有权(复制或转移资源)。
2168+违反 NPLA 资源管理要求可引起宿主语言的未定义行为并违反 NPLA 内存安全保证(@5.6.3.3) 。
2169+基本的资源所有权约束由宿主语言的规则蕴含。
2170+部分数据结构可有更进一步的上下文相关的约定,如 TermNode 对象在作为被归约项(@6.2) 和被绑定对象(@6.8.1) 时具有不同的规则(参见有关的所有权规则(@6.4.5) )。
2171+NPLA 还引入称为间接值(indirect value) 的对象访问和管理特定的资源,详见 @6.4.3 。
2172+
2173+@6.4.1 分配器(allocator) :
2174+分配器分配资源,用于提供供实现指定的宿主语言的存储资源的来源。这和对象语言的存储资源(@5.6) 管理机制类似,但不保证对应。分配器隐含被映射的类型中,不显式出现在对象语言中。
2175+NPLA 中的分配器具有可和 TermNode::allocator_type 互相隐式转换的类型,接受 YSLib::pmr::memory_resource 类型的存储资源。
2176+这些分配器通常来自 TermNode(@6.2) 、ContextNode(@6.8.3) 的存储资源以及 Environment::BindingMap(@6.8.1.1) 的分配器,用于这些类型和 NPL::EnvironmentList(@6.8.1.2) 以及其它被运行时使用相同存储资源管理的项中的对象的初始化。
2177+NPLA 和派生实现的本机实现(@5.3) 应保证分配器的使用符合以下小节的约定。
2178+
2179+@6.4.1.1 相等性要求:
2180+除非另行指定,对 ValueObject 等支持不同目标类型的对象,可选地使用分配器。一般地,这表示不使用运行时检查分配器类型相等的带有分配器参数的复制或转移构造函数。
2181+预期来自等价存储资源的分配器应相等,以避免分配器不相等的容器交换操作引起行为未定义(如 TermNode 交换,参见 @6.2 )。
2182+由 C++ 分配器要求,分配一个宿主语言对象时使用的分配器和其它特定分配器之间的相等性此宿主语言对象的生存期中确定不变。
2183+以下关于 TermNode 的分配器相等性是 API 隐含的:
2184+TermNode::Container 满足 C++ 标准库容器的 allocator-aware 要求,容器和作为容器元素的子节点使用的分配器相等;
2185+TermNode::get_allocator 决定 TermNode 的分配器等价 TermNode 中的节点容器的分配器。
2186+以下情形的分配器在实现中应保证相等:
2187+由同一个 ContextNode 对象决定的分配器在 ContextNode 生存期内应相等(且通常使用这些分配器的对象的生存期是此生存期的子集);
2188+若分配器来自超过一个宿主语言对象,这些对象提供的分配器应相等。
2189+一般地,以下关于 TermNode 的分配器之间不保证相等性:
2190+TermNode 对象和它的值数据成员(@6.2) 的分配器;
2191+TermNode 的值数据成员和其中持有的对象可能具有的分配器。
2192+
2193+@6.4.1.2 分配器来源:
2194+宿主语言对象的分配器可通过从存储资源初始化或由其它对象使用的分配器复制初始化取得。
2195+部分类型(如 ContextNode )的对象初始化可接受存储资源参数。提供实际参数作为存储资源的用户代码应适当维护被引用的存储,确保这些对象的生存期是存储资源的生存期的子集,以避免引用已释放的存储而引起宿主语言的未定义行为(@5.4) 。
2196+NPLA 使用的多态分配器可保证分配器和初始化分配器的存储资源的对应。但若已存在(满足操作的接口语义需要的)可用的分配器,取分配器的副本而不是使用存储资源初始化新的分配器。
2197+由 C++ 分配器要求,使用相等的分配器效果一致,但除非另行指定或以下所有途径都不支持取得能兼容被分配的对象的分配器,若需要分配器(以满足 @6.4.1.1 或提供优化实现等),仍按以下规则(自上而下优先)约定确定用于复制初始化的分配器的来源:
2198+引起表示对象语言的值的 TermNode 对象创建时,若分配器(根据接口语义)必须依赖操作中的一个或若干个已知分配器相等的、表示对象语言值(如对象语言中函数的参数)的 TermNode 对象,后者中的任一个 TermNode 对象提供分配器;
2199+引起 TermNode 对象创建时,若对象被一个或多个 TermNode 对象共同所有(如节点的值被值数据成员所有),TermNode 的分配器以表示关联所有者的 TermNode 类型的值提供;
2200+引起 Environment 对象创建时,需要的分配器由关联的 Environment::BindingMap(@6.8.1.1) 的分配器提供;
2201+引起满足 allocator-aware 的容器要求的对象(支持迭代器和所有权)的元素的创建时,由被添加目标对象的分配器提供;
2202+其它情形的分配器对象由 ContextNode 的存储资源提供。
2203+
2204+@6.4.1.3 取分配器的方式:
2205+提供分配器的对象可能若干种隐式(如转移构造)或显式(如 TermNode::get_allocator 调用)方式决定被使用的分配器。
2206+满足上述预期的分配器相等性能决定同一来源的不同方式取得的分配器相等时,取分配器使用的方式未指定,如:
2207+一个表示对象语言的值的 TermNode 对象提供分配器时,可直接取其分配器或子节点(若存在)的分配器。
2208+否则,一般不能通用,如:
2209+引起 TermNode 对象创建时,被初始化的值数据成员不能通过某个未知分配器来源也不具有相等性保证的被 ValueObject 值持有的对象取得。
2210+
2211+@6.4.2 值所有权:
2212+TermNode 对构成值的表示的子节点和值数据成员(@6.2) 具有所有权。和 @6.2 不同,非直接的所有权可能不是独占的。
2213+TermNode 表示对象语言中一等对象的所有权规则符合求值和对象所有权(@5.6.2) 规则,且作为被归约项时,符合被规约项的所有权(@6.4.5) 或派生实现指定的其它规则的所有权约束。
2214+值对宿主对象(@6.3) 的所有权的机制由值数据成员(具有 ValueObject 类型)相关的 API 提供。
2215+默认使用的 ValueObject 使用值的持有者 YSLib::ValueHolder ,直接作为对象的表示,同时具有对象的所有权。
2216+引用一个项可能有多种方式,不具有所有权。关于引用相关的值的所有权,参见以下各个小节。
2217+
2218+@6.4.3 间接值分类和所有权:
2219+使用 ValueObject 的特定持有者或约定特定的中间值(@6.6.3) 实现间接值允许和所有权分离的其它形式的表示,提供和非间接值不同的对象所有权和附加操作。
2220+间接值的实例总是具有关联对象。间接值可间接访问关联对象。
2221+对作为对象语言中的一等对象的间接值,允许复制或转移关联的对象以恢复对应的非间接值作为一等对象直接访问。
2222+间接值可用于代替非间接值,避免求值时改变环境(@6.8.1) 所有的非临时对象的所有权(@5.6.2) 。
2223+间接值可实现和 C++ 引用类型表达式类似的行为。
2224+NPLA 提供引入间接值的 API ,参见 @6.6.7.2 。
2225+实现通过值数据成员以外的机制也可隐含固定(不通过用户代码修改定制)的所有权关系,如上下文(@6.8.3) 和环境(@6.8.1) 。
2226+所有权机制和中间值(@6.6.3) 正交:具有间接值的对象可能作为中间值,也可能不作为中间值。
2227+本节以下约定要求被 NPLA 实现支持的间接值。派生实现可以定义 NPLA 扩展间接值。
2228+
2229+@6.4.3.1 环境引用(@6.6.3.2) :
2230+环境引用间接访问环境对象。
2231+环境强引用(@6.6.3.2) 可能共享环境对象的所有权,对环境对象的名称绑定映射(@6.8.1) 持有的项具有间接的所有权。
2232+
2233+@6.4.3.2 引用值:
2234+使用项引用(@6.6.3.3) 作为间接值引用一个项,访问被引用对象(@4.2.3) 。
2235+这也包括使用子对象引用(@6.6.3.3.5) 的情形。
2236+
2237+@6.4.3.3 引用持有者:
2238+使用持有者 YSLib::RefHolder 的实例可实现间接值,访问其它 ValueObject 对象。
2239+被间接引用的值的类型和 TermNode 的值数据成员(@6.2) 取得的类型一致,可直接替换非间接引用的对象。
2240+引用持有者一般并不能替代引用值(@6.4.3.2) ,因为持有者仅影响存储来源和所有权,不区分类型,也无法要求用户代码使用明确的引用操作。
2241+一般引用的持有者的项仅作为上述明确的引用操作的结果,作为中间结果继续以和非间接值一致的方式被规约(使用 NPL::LiftTermRef(@6.6.7.2) )。
2242+
2243+@6.4.4 间接值的有效性:
2244+间接值有效当且仅当存在关联的对象且访问对象不引起未定义行为。
2245+其它间接值是无效(invalid) 的。
2246+类似实现中项的无效化(@6.2) ,有效的引用值可能被无效化(invalidate) 不再有效。
2247+因关联的对象存储期(@5.6) 结束而被无效化的是悬空(dangling) 间接值。
2248+引用值(@6.4.3.2) 作为一种间接值,其规则是上述规则的实例。
2249+以下约定要求被 NPLA 实现支持的有效的引用值总是无条件地允许访问对象。
2250+
2251+@6.4.5 被规约项(@6.2) 所有权:
2252+具有值类别的被规约项若指称对象,对应的对象是这个项的项对象(term object) 。项对象是求值结果(@4.1) 或求值的中间结果(@6.3) 对应的对象。
2253+项对象可能不直接由项自身表示,即可以通过项引用其它途径引入的对象。
2254+基于 @6.4.3 ,为保证内存安全,避免临时对象被引用,仅在泛左值(@5.8.1) 中允许引入被可能引用的间接值。
2255+推论:泛左值的项对象不是临时对象,被环境所有。
2256+通常纯右值作为其它项的子项而被独占所有权,求值时可能通过临时对象实质化转换(@5.8.4) 标识创建的临时对象(@5.8.5) 。
2257+为实现临时对象求值和对象所有权(@5.6.2) ,临时对象以项自身作为表示(@6.3) ,被纯右值所有,也间接被其它项所有。
2258+特定情况下纯右值可能被环境所有,但应只通过复制等方式访问其值而不依赖所有权关系(如仅复制记号值(@6.4.6.2) )。
2259+
2260+@6.4.6 间接值使用规则:
2261+在特定的适当情形(详见以下小节)下复制或转移间接值引用的对象以保证满足生存期要求(@6.4.5) ,维护内存安全。
2262+不能满足上述适当情形条件的若不明确引起错误,则行为未定义。
2263+间接值生存期规则:被规约对象中间接值的生存期总是不超过被引用的环境中的对象,以保证内存安全。
2264+不满足间接值生存期规则的情形,除非提供派生实现定义的其它保证,不保证内存安全。
2265+以含间接值的项替代不含间接值的项称为引入间接值。
2266+包含间接值的项可被不含引用值的项替代,称为消除。
2267+如需直接替换项表示的值,需消除间接值。否则,没有必要提前对项进行操作以提前移除间接值。
2268+另见被求值的被规约项中的对象的所有权(@6.4.5) 。
2269+派生实现可基于本节约定其它规则。
2270+
2271+@6.4.6.1 引用值(@6.3.4) 作为间接值:
2272+由于左值(@5.8.1) 的项对象被环境所有(@6.4.5) ,在项上的求值需要其它项对象作为中间值。
2273+这种中间值通过间接引用左值以确保左值标识的对象可作为一等对象(@4.1) 使用,也是一种间接值,即引用值(@6.3.4) ,是引用(@4.2.3) 的实例。
2274+NPL::TermReference(@6.6.3.3) 类型是可能需要区分引用与被引用对象的值的内部表示。
2275+这类似宿主语言的对象类型的左值引用和右值引用的差异(@6.3.4) 。
2276+消亡值(@5.8.1) 以作为其表示的引用项(@6.3.4) 内部的 TermTags::Unique 标签(@6.2.2) 标记,即唯一引用。
2277+基于引用的左值到右值转换(@5.8.4) 可通过 NPL::ReferenceTerm(@6.6.5) 实现。
2278+和宿主语言的涉及成员访问的表达式类似,直接使用标识符进行名称查找(@4.3.3) 得到的表达式是左值或消亡值。在此环境总是被视为左值,所以结果由环境中的对象类型确定:当且仅当对象是左值时,结果是左值引用;否则是右值引用。
2279+和宿主语言类似,右值引用类型的表达式可作为左值使用。
2280+一般地,并非所有对象需要引用,详见 @4.2.3 。
2281+引用值在必要时被消除(@6.6.3.3.3) ,以被进一步求值。
2282+
2283+@6.4.6.2 非引用值间接值:
2284+规约实现在特定情形的求值中使用 NPL::LiftTermRef 等(@6.6.7.2) 引入基于引用持有者(@6.4.3.3) 的间接值避免求值时改变泛左值(@5.8.1) 标识的非临时对象的所有权。
2285+否则,引入的间接值引用环境所有的对象。
2286+应注意存储环境以外的间接值时,不超出持有对象的存储期,避免未定义行为(@5.6) 。当前实现不对此附加检查。
2287+注意不求值而仅替换项时,使用 NPL::LiftOther 或 NPL::LiftTerm(@6.6.7.1) ,这不引入间接值。
2288+除构造间接值的例程(@6.6.7) ,当前不直接引入引用持有者。
2289+如需要引入环境引用(@6.4.3.1) 外的间接值,一般使用引用值(@6.3.4) 而不是非引用值间接值,以统一处理无法直接持有的列表项和非列表项的引用。
2290+
2291+@6.4.6.3 间接值的消除:
2292+访问间接值涉及维护内存安全保证(@5.6.3) 时,可能需要提升项(@6.6.7) 以移除允许非内存安全(@5.6.3.1) 访问的间接值。
2293+引用值作为间接值可被消除(@6.6.3.3.3) 。
2294+用于按值传递参数时,一般使用 NPL::LiftTerm(@6.6.7.2) 和确保创建值副本的 NPL::SetContentWith(@6.2.3) 实现;前者取非引用类型的右值(@5.8.1) ,后者提升间接值确保结果不是中间值。
2295+用于按值传递返回值时(@4.5.3.1) ,除显式分别对是否为引用值的情形进行处理,可使用 NPL::LiftToReturn(@6.6.7.3)(其中也使用以上方式实现),实现临时对象实质化转换(@5.8.4) ,详见 @6.4.6.4 。
2296+
2297+@6.4.6.4 返回值转换(@5.8.4.2) :
2298+作为提升项的使用用例(@6.4.6.3) ,在返回值转换上下文(@5.8.5.2) 中确定函数返回值的实质化转换上下文(@5.8.5.1) 的部分操作消除引用值(@6.6.3.3.3) ,即返回值转换。
2299+这可约束作为间接值的引用值不逃逸(escape)(即使用被引用对象的值时不超出指向对象的生存期),而保证只考虑项的值数据成员(@6.2) 可能是引用值时的内存安全。
2300+返回值转换不保证未折叠的引用值在消除引用值后的结果不逃逸。为确保内存安全,程序仍需要保证被引用的对象的间接引用的对象生存期结束后,不能访问间接引用的对象。
2301+除非证明不需要临时对象(当前未实现),返回值转换中初始化临时对象作为返回值的项对象(@6.4.5) 。
2302+是否需要返回值转换由实质化转换上下文中的被调用的函数而非上下文是否需要使用右值决定,无关被转换的表达式是否是左值,因此返回值转换不是左值到右值转换(@5.8.4) 。
2303+不论是否存在返回值转换,返回值的项对象来自返回的右值关联的临时对象实质化转换(@5.8.5) 。这可能在 NPL::LiftToReturn 或之前的求值规约的调用中蕴含。
2304+
2305+@6.4.7 被归约项稳定性:
2306+基于 TermNode 的性质,未被规约修改的项的子项的迭代器、指针和引用保持稳定(@6.2) 。
2307+被规约的项在取得求值结果(@6.7.1) 前不被删除,以避免项的迭代器、指针和引用失效。
2308+结合 @4.2.4.1 得到推论:被规约的项在取得求值结果起可被非内部对象(@4.2.4.1) 引用。
2309+由 @5.6.1 ,项中的值数据成员(@6.2) 表示的宿主对象(@6.3) 保持固定。
2310+子项的稳定性保证只要被规约的整个项不被作为对象被引用,直接转移被规约的项仍保持子项中的宿主对象保持固定。
2311+子项不直接保证其它稳定性。
2312+在取得求值结果前,若项不改变可观察行为(@4.1.3)(例如,项不被作为对象语言中的表达式或求值结果的表示(@6.3) 或表示的值在对象语言中不可见),可能被转移。
2313+这允许规约的本机实现内部在取得求值结果前保存表示不属于求值结果的中间结果的子项。
2314+
2315+@6.4.7.1 规约操作资源:
2316+规约时保持决定当前规约操作的状态的资源的独占所有权([Documentation::CommonRules @@2.3.4.6]) 的项应具有足够长的生存期,以避免调用时引起未定义行为。
2317+实现 WHNF(@4.4.3.1) 时,第一个子项对此资源具有独占所有权。
2318+在无法预知子项是否需要被进一步使用而不会在被调用前另行存储包含这些资源的项时,实现使用的操作应避免删除这里的子项(对实现 WHNF 而言即第一个子项),以免无法使用其中的状态。
2319+
2320+@6.4.7.2 临时对象资源:
2321+表示临时对象的项的子项的迭代器、指针和引用应保持稳定,以支持子项被绑定到对象语言中的引用值(@6.3.4) 。
2322+表示临时对象的项自身不需被绑定到引用值,不保证稳定,可被直接存储为 TermNode(而不需要保存为指针等间接值)。
2323+对临时对象分配资源可能转移表示临时对象的项,但不影响其子项的稳定性。
2324+
2325+@6.4.8 项修改限制:
2326+若规约需调整项的结构以便使用特定 API ,可使用 TermNode& 作为参数的规约函数(@6.7.5)(另见规约函数形式约定(@6.7.6) );
2327+如不可行,一般需转移到新创建的项上进行调用,以免改变现有的项导致无法使用项独占所有权的资源(如 @6.4.7.1 的状态)。
2328+单独转移可能具有宿主类型对象的子项会违反固定对象的约定(@6.4.7) ,因此除非确保子项不被引用,不单独转移或销毁可能带有宿主对象(@6.3) 的子项(包括引起子项对象被销毁的删除操作)。
2329+若规约内部确定符合内存安全(@5.6.3) 或派生实现指定的假定满足的条件(至少应满足 @6.4 中的资源管理要求),子项可被转移或销毁。这些操作包括:
2330+被规约项取得自求值结果起,在非内部对象引用此项前被整体转移的操作;
2331+NPLA 指定删除或其它可能转移或销毁子项的操作;
2332+派生实现指定的其它要求假定或可证明安全的操作。
2333+其它情形应避免这类操作。特别地,一般应避免在被处理的 TermNode 上直接调用 RemoveHead(@6.2) 。
2334+
2335+@6.5 异常处理:
2336+NPL::NPLException 是 NPL 实现的异常基类。
2337+其它 NPL 异常都从此类直接或间接派生。
2338+这些异常类在 NPLA 模块中以 Doxygen 命令标识为 \ingroup exceptions 。
2339+一般地,使用确切的异常类型以明确更具体的错误条件,如列表类型(@6.2.1) 错误代替一般的类型错误。
2340+除非规约节点操作(@6.11.3.1) 外,NPLA 实现可能抛出这些异常或派生实现定义的派生这些类的其它异常。
2341+NPLA 实现可能抛出标准库异常。
2342+一般地,不通过对象语言构造(而仅通过互操作(@5.3) 或实现缺陷)引起的异常,不使用 NPL 异常基类。
2343+
2344+@6.6 上下文无关非节点处理 API :
2345+NPLA 实现提供不依赖一般项规约逻辑(@5.8) 的公共 TermNode(@6.2) 操作 API 。
2346+以下 API 处理和 TermNode 或其中的成员数据类型相关但和 ContextNode 等上下文状态(@6.8) 无关的数据。
2347+
2348+@6.6.1 词法类别支持:
2349+在 NPLA 模块中以 Doxygen 命令标识为 \ingroup LexicalCategory 。
2350+
2351+@6.6.2 规约相关类型别名:
2352+
2353+@6.6.2.1 记号值:
2354+参见 NPLA 记号值(@6.6.3.1) 。
2355+
2356+@6.6.2.2 锚对象(@6.6.3.2) 指针:
2357+NPL::AnchorPtr 是锚对象的指针的类型,是不确定类型的 shared_ptr 实例。
2358+相关接口详见上下文 API(@6.8.3.1) 。
2359+
2360+@6.6.3 NPLA 中间值:
2361+中间值(@4.4.4.2) 可在 TermNode 的值数据成员(@6.2) 中存储。
2362+和具体求值(@4.1) 的求值结果(@4.1) 及其它情形存储的对象不同,中间值可能参与特定的规约作为求值的中间步骤的表示。
2363+为允许区分中间值和被映射的其它值,约定中间值具有特定的类型。但以中间值引入的类型,其值仍可能作为求值结果。后者实质被对象语言作为一等对象(@4.1) ,称为一等中间值(first-class thunk) 。
2364+NPLA 提供名义(nominal) 中间值,即不是其它 NPLA 外类型的别名的类型,但这些类型考虑由其它类型转换或构造。
2365+派生实现不提供不依赖于非 NPLA 和 NPLA 实现定义的类型的别名作为非名义中间值类型,即若提供非名义中间值类型,应为参数化类型,且其中至少包含一个实现自定义的类型作为模板实际参数。
2366+其中:
2367+记号值(@6.6.3.1) 构成的项可表示未求值的表达式。
2368+记号值和项引用(@6.6.3.3) 可作为某些对象语言的表达式的表示(@6.3) 。
2369+NPLA 扩展中间值(extended thunk) 是其它由派生实现定义的其它中间值。
2370+
2371+@6.6.3.1 记号值(token value) :
2372+NPL::TokenValue 类型的值表示记号(@3.3.1) ,可使用 string 类型的值构造,记号值类型。
2373+由 @5.5 ,记号值对应的词素(@3.3.1) 作为名称,宿主类型为 string 。记号值对应的词素的宿主值和构造记号的 string 类型的值相等。
2374+记号值和词素在逻辑上非空,但因为除翻译单元外的外部表示(@2.3.4) 一般未指定(@4.1.1) ,不保证在 API 中取得的这些类型对应非空串,因此除特定的内部实现外不省略空串检查。
2375+和字符串值不同,记号值求值的结果不会继续是记号值,以避免不经意的递归求值或无法和宿主语言字符串值区分的情形出现。
2376+但在 API 层次上,记号值的求值不排除无限循环或递归,不保证对应的项作为表达式时具有强规范化性质(@4.4.3)。实现需注意保证作为名称表达式时满足强规范化要求(@5.2) 。
2377+表示一个未被求值的非字面量(@3.3.3) 记号的记号值称为符号(@2.3.4) 。符号可构成名称表达式(@4.5.1) ,也可作为一等中间值。
2378+符号的宿主类型(@5.5) 是 NPL::TokenValue 。
2379+和 Kernel 类似,NPL 设计强调区分程序是否被求值的差异。符号是引入和表示为自求值项(@4.4.3) 的其它表达式的关键数据类型。
2380+符号可能有外部表示。以未求值的记号值作为符号是其和外部表示关联的设计。由于外部表示未指定,这种关联是有限的。记号值蕴含了词素和符号的映射关系,即源代码文本;其余关系(包括源代码支持的文本编码)由派生实现指定。
2381+记号值的相等性等价其对应的词素的相等性。
2382+记号值可出现在词法分析(@6.1.5) 阶段,由单独的规约过程(@6.7.4) 通过调用 NPL::TokenizeTerm(@6.6.4) 转换词素或直接初始化分析的中间结果得到。
2383+调用 NPL::TermToNamePtr(@6.6.4) 访问具有记号值的名称节点对应的字符串。
2384+
2385+@6.6.3.2 环境引用(@6.8.1.3) :
2386+环境引用是引用 Environment 类型的环境(@6.8.1) 并支持其共享所有权(@5.6.2) 的中间值。
2387+环境引用类似引用值(@6.3.4) ,但隐含更复杂的所有权关系,因此不具有相同的类型(@5.8.6) 。
2388+shared_ptr<Environment> 可引用环境对象,其非空值作为环境强引用(@6.8.1) 。
2389+NPL::EnvironmentReference 可引用环境对象,封装 weak_ptr<Environment> 和锚对象(anchor object) 指针,其非空值表示可能具有共享所有权的环境弱引用(@6.8.1) 。
2390+环境对象的弱引用的所有权关系使用其中的 weak_ptr<Environment> 确定。
2391+因为 weak_ptr<Environment> 不提供可靠的弱引用计数,引用锚对象附加的引用计数用于确定弱引用的数量,详见上下文 API(@6.8.3.1) 。
2392+默认构造的 shared_ptr<Environment> 是空的环境引用。
2393+默认构造的 NPL::EnvironmentReference 具有空的 weak_ptr<Environment> 值,是空的环境引用。
2394+非空的 shared_ptr<Environment> 和 NPL::EnvironmentReference 分别可作为对象语言的环境强引用和环境弱引用的宿主值。
2395+
2396+@6.6.3.3 项引用(term reference) :
2397+宿主语言中的 NPL::TermReference 是项引用。
2398+NPL::TermReference 和所在的项中可能包含的子对象是引用值的宿主值(@6.3) 类型。
2399+其中,子项只被子对象引用(@6.6.3.3.5) 的情形使用。
2400+子对象引用使用的这种形式的表示通常因为需要更多的操作比其它引用值的类似操作低效,但这种表示可避免依赖宿主语言中的本机对象内部表示(如成员布局)的依赖。
2401+一般地,在 C++ 的意义上不存在能满足语义的更有效的可移植表示,所以这种表示在和宿主语言的互操作(@5.3) 上是必要的。
2402+对 NPL::TermReference 相关操作详见 @6.6.5 ;可能使用 NPL::TermReference 访问项的操作详见 @6.6.6 。
2403+
2404+@6.6.3.3.1 项引用的元数据(@4.2.3.3.1) :
2405+NPL::TermReference 包含一些附加的只通过引用形式访问对象的元数据作为优化设计(@4.2.3.3.2) ,包括标签(@6.2.2) 和关联的环境。
2406+部分元数据在初始化时决定。
2407+NPL::TermReference 初始化时,初始化保存标签(@6.2.2) 。
2408+NPL::TermReference 通过 NPL::EnvironmentReference(@6.6.3.2) 可选地指定关联的环境。
2409+当关联的环境不存在时,不提供对引入的对象内存安全(@5.6.3) 的检查(@5.4)。
2410+引用值关联的环境是可靠的,当且仅当关联的环境是被引用对象的所有者。
2411+不保证可靠的关联环境的引用值是不安全引用值(unsafe reference value) 。除非另行指定,仅有不存在关联环境的引用值是不安全引用值。
2412+另见非内存安全操作(@5.6.3.1) 和环境(@6.8.1) 。
2413+
2414+@6.6.3.3.2 引用折叠(reference collapse) :
2415+和 ISO C++ 类似,引用值在 NPLA 中默认不被继续引用,使用引用初始化引用会引用到被引用对象(@4.2.3) 上,即引用折叠。
2416+内部表示可支持间接的引用。和 ISO C++ 不同,NPLA 不限制派生实现利用未折叠的引用值,但 NPLA 的其它接口不和其与其它值区分。这允许在对象语言中实现一等引用(@4.2.3) 。
2417+未折叠的引用值被折叠时,用于初始化的被引用对象可能仍然是未折叠的引用值。
2418+完全折叠的引用值得被引用对象不是引用值。除非另行指定,以下的引用值指已完全折叠的引用值。
2419+
2420+@6.6.3.3.3 引用值的消除:
2421+引用值可被消除,即被其关联的被引用对象替代。修改引用值为关联的被引用对象消除引用值;折叠引用值消除被折叠的引用值。
2422+未折叠的引用值消除引用值结果仍是引用值。配合引用折叠,消除已折叠的引用值确保结果总是右值(@5.8.1) 。
2423+
2424+@6.6.3.3.4 可转移条件:
2425+根据项是否具有特定元数据的引用值可判断使用复制代替对象转移的条件(@5.8.2.3) 。
2426+可转移条件的判断基于表示表达式的值的项。可转移项(movable term) 通过以下方式确定:
2427+非引用项(@6.3.4) 总是可转移的(非引用项表示右值);
2428+否则,若项引用具有的标签(@6.6.3.3.1) 决定它是被引用对象的唯一引用且非不可修改(@6.2.2) ,项是可转移的。
2429+一般地,非引用值的对象是可转移的。
2430+确定引用值关联的被引用对象可转移的条件有两类:
2431+由可转移项确定:引用值由可转移项表示,在特定的上下文使被引用对象能转换为右值(用例如 @7.1.4.2 );
2432+由被转发(@5.8.2.2) 表达式的值确定:除上述条件外,表示绑定临时对象的引用值(具有临时对象标签(@6.2.2) )的项蕴含被引用对象(@4.2.3) ,也是可转移的(用例如 @10.5 )。
2433+
2434+@6.6.3.3.5 子对象引用(subobject reference) :
2435+特定的引用值是子对象引用,通过一个引用值和它的子对象构造,引用被对象所有的一等对象。
2436+子对象引用的表示详见 @6.7.11 。
2437+子对象引用使用的这种形式的表示通常因为需要更多的操作比其它引用值的类似操作低效,但这种表示可避免依赖宿主语言中的本机对象内部表示(如成员布局)的依赖。
2438+一般地,在 C++ 的意义上不存在能满足语义要求且总是更简单高效的可移植表示,所以这种表示在和宿主语言的互操作上是必要的。
2439+
2440+@6.6.4 项辅助访问操作:
2441+NPLA 提供访问若干访问 TermNode 的辅助接口,在 NPLA 模块中以 Doxygen 命令标识为 \ingroup TermAccessAuxiliary 。
2442+
2443+@6.6.5 项引用操作:
2444+NPL::TermReference(@6.6.3.3) 包括若干成员,其中主要有以下 API :
2445+IsMovable
2446+GetEnvironmentReference
2447+get
2448+对 NPL::TermReference 的相关操作包括以下 API :
2449+NPL::Collapse
2450+NPL::PrepareCollapse
2451+NPL::ReferenceTerm
2452+除以上 API ,NPLA 还提供以下关于项引用操作的便利接口:
2453+ç±» NPL::ReferenceTermOp
2454+ç±» NPL::ComposeReferencedTermOp
2455+
2456+@6.6.6 项引用访问操作:
2457+类型 NPL::ResolvedNPL::TermReferencePtr 表示解析项后作为访问参数的项引用指针。
2458+函数 NPL::ResolveToNPL::TermReferencePtr 转换项引用指针为项引用的访问参数指针。
2459+函数和函数模板 NPL::IsMovable 判断解析后的项是否指示可转移项(@6.6.3.3.4) 。
2460+若被判断的参数是 NPL::TermReference(@6.6.3.3) 值,则同 NPL::TermReference::IsMovable(@6.6.5) 。
2461+否则,被判断的参数是指向指向项的指针(支持包括项引用指针和项引用的访问参数指针)。
2462+NPLA 还针对考虑到项可能是 NPL::TermReference ,提供若干访问 TermNode 中的项引用的接口,在 NPLA 模块中以 Doxygen 命令标识为 \ingroup TermReferenceAccess 。
2463+
2464+@6.6.7 提升项:
2465+对项的提升(lifting) 指一类对项的替换变换,使用项进行一定的变换后取代其它项或其一部分,并满足下述提升条件。
2466+决定替换变换是提升的条件为:被提升的项是提升后的项的一个直接或间接子项,以树表示项则为叶节点取代枝节点。
2467+这可以视为作为语法变换的消去 λ 抽象的 lambda 提升(详见 https://en.wikipedia.org/wiki/Lambda_lifting )的一般化,但此处和 λ 抽象没有直接关联。
2468+被提升的项往往被转移,因此一般地,需要可修改。
2469+提升时对抽象的值表示进行操作实现基本的语义功能,可能进行检查,包括为满足接口行为的语义检查和实现为预防宿主语言的未定义行为的附加检查(@5.4) 。
2470+提升项对象通过变换操作取作为项的值数据成员(@6.2) 。在此基础上有递归版本。
2471+提升项可引入或消除(@6.4.6) 间接值(@6.4.3) 。
2472+提升项通过被引用的对象替换作为项的值数据成员的引用值而消除引用值(@6.6.3.3.3) 。提升项的求值结果(@6.7.1) 是消除引用值的结果。
2473+提升操作辅助对项的操作,可用于实现规约函数,包括以下各节中描述的 API 。其中:
2474+名称前缀为 Lift 的函数是一元提升操作,具有一个 TermNode& 参数和其它可选的参数,提升第一参数指定的项,提升后的项保存在第一参数指定的项;
2475+名称前缀为 Move 的函数是二元提升操作,具有两个 TermNode& 参数和其它可选的参数,要求参数指定的两个项不相同,提升第二参数指定的项,提升后的项保存在第一参数指定的项。
2476+
2477+@6.6.7.1 基本提升操作:
2478+基本提升操作包括直接转移赋值及以下 API :
2479+成员函数 ValueObject::MakeIndirect 取记号值或持有其它类型的值的引用的间接值(@6.4.3.3) 。
2480+成员函数 ValueObject::MakeMoveCopy 转移或复制对象以消除间接值。
2481+函数 NPL::LiftOther
2482+函数 NPL::LiftTerm
2483+函数 NPL::LiftOtherOrCopy
2484+函数 NPL::LiftTermOrCopy
2485+函数 NPL::LiftTermValueOrCopy
2486+
2487+@6.6.7.2 可能引入间接值(@6.4.5) 的提升操作:
2488+以下值操作可能引入间接值:
2489+函数 NPL::LiftCollapsed
2490+函数 NPL::MoveCollapsed
2491+函数 NPL::LiftTermRef 提升项引用:提升项的内容为参数指定的项或值的引用值(@6.3.4) 。
2492+引用值通过参数指定的值对象(@6.2) 上创建得到。
2493+函数 NPL::LiftToReference 提升项对象为引用。
2494+若项对象表示引用值则提升项,否则对 ValueObject 进行基于 ValueObject 所有权的检查(间接进行生存期检查)并取引用这个项的引用值。
2495+运行时进行的检查类似于强制 C++ 的一元 & 对表达式值类别(另见 @4.2.3 )的要求但更严格(尽管仍然不能保证避免未定义行为),避免临时对象(@5.8.5) 被保存为引用值。
2496+
2497+@6.6.7.3 消除中间值的提升操作:
2498+函数 NPL::LiftMoved
2499+函数 NPL::LiftMovedOther
2500+函数 NPL::LiftToReturn
2501+函数 NPL::MoveRValueToReturn
2502+函数 NPL::MoveRValueToForward
2503+函数 NPL::LiftSubtermsToReturn
2504+本节的函数作用在被规约项时,对引用值的操作实现临时对象实质化转换(@5.8.4) 所在的求值规约的最后部分的操作,一般在尾上下文(@4.4.7) 中进行处理。
2505+提升消除中间值只作用在被提升项,不作用在子项。需要递归复制的操作不在这里提供,以避免抽象泄漏。
2506+为消除中间值需要进行复制消除(@5.8.5.3) ,其中使用不同的可转移条件(@6.6.3.3.4) 决定使用转移而非复制。
2507+当前实现在转移项时使用的宿主转移操作总是使用宿主对象的转移(基于 NPL::SetContentWith(@6.2.3) )而不是 TermNode 的转移。
2508+另见 @6.4.6.3 。
2509+
2510+@6.6.7.4 辅助提升操作:
2511+函数 NPL::LiftFirst 和 NPL::LiftLast 提升第一个和最后一个子项。
2512+
2513+@6.6.7.5 非内存安全(@5.6.3.1) 的提升操作:
2514+以上操作中,不保证引入的对象内存安全(@5.6.3) 且不提供检查(@5.4) 的操作有:
2515+NPL::LiftTermRef :通过 ValueObject::MakeIndirect 引入间接值。
2516+NPL::LiftToReference :非内存安全的项引用操作(@5.6.3.1) 。
2517+
2518+@6.6.7.6 不支持嵌套调用安全(@5.11) 的提升操作:
2519+以上操作中,作为例外,当前(因为直接或间接调用 NPL::SetContentWith(@6.2.3) 遍历节点)不支持嵌套调用安全的函数包括:
2520+NPL::LiftTermValueOrCopy(@6.6.7.1) ;
2521+NPL::LiftTermRef(@6.6.7.2) ;
2522+@6.6.7.3 的操作。
2523+
2524+@6.7 规约接口:
2525+NPLA 规约 API 约定默认使用 TermNode 和 ContextNode 类型的引用作为参数类型,分别表示被规约项(@6.2) 和使用的上下文(@4.6.1) 。
2526+更具体的其它约定参见规约函数形式约定(@6.7.6) 。
2527+一次规约局限在当前被规约项(一个 TermNode 对象)进行修改(@6.4.8) ,这个被规约项称为此次规约的当前项(current term) 。
2528+表达式求值起始时,当前项是当前被求值项(@6.3) 。
2529+规约 API 的 TermNode 参数指定当前项。必要时,当前项可被其它方式指定。
2530+NPLA 约定列表表达式(@3.4.2.3) 子项作为被规约项需进行递归的树规约(@6.2) 。
2531+部分规约 API 以函数(如 @6.7.6 )的形式提供,其中:
2532+ContextNode 可被递归地作为子项规约(@6.7.2) 的参数,因此可以不使用其它参数。
2533+其它兼容实现可能使用其它参数。
2534+规约 API 通常返回规约结果类型(@6.7.1) 。
2535+以下的规约 API 和其它 API(如项提升 API(@6.6.7) )配合,可用于实现规约操作:
2536+规约范式检查 API(@6.7.3) ;
2537+迭代规约结果合并(@6.7.4.4) ;
2538+正规化 API(@6.7.7) ;
2539+辅助规约函数(@6.7.12) 。
2540+
2541+@6.7.1 规约结果:
2542+规约过程返回描述一次规约调用操作结束后的状态的结果,包括以下几种情形:
2543+部分规约(partial reduction) :需要继续进行规约。表示不是完整的求值规约,也可以仅是管理规约(@4.4) 。一般仅用于异步规约(@6.9) 。
2544+中立规约(neutral reduction) :规约成功终止(@4.4.3) ,且未指定是否需要保留子项。
2545+纯值规约:规约成功终止,且不需要保留子项。
2546+非纯值规约:规约成功终止,且需要保留子项。
2547+重规约:当前项需重新进行规约迭代(@6.7.4) ,而首先需要跳过当前项上当前一轮迭代的剩余的规约动作(@6.8.3) 。
2548+取得重规约外的规约结果时,被规约项表示求值结果(@4.1) 。另见关于使用规约实现表达式求值(@6.3) 。
2549+纯值规约的求值结果总是由值数据成员(@6.2) 决定。
2550+非纯值规约为纯列表规约,当且仅当求值结果仅由子项决定而不需要访问值数据成员。
2551+除不指定要求保留子项,中立规约和非纯值规约的作用相同,仅影响 @6.7.4 中的处理。
2552+若被规约的项已符合保留子项的要求,中立规约可以通过判断当前项是否已保留子项而分别指定为非纯值规约或纯值规约代替,但这带来不必要的项访问,不利于性能;因此本设计直接在规约结果中提供。
2553+除非实现逻辑需要或另行指定,规约结果默认为纯值规约。因为通常求值结果的子项来自被规约项的子项,和表示规约后的项经常并没有关联而应被清除,默认规约结果设计为纯值规约便于处理这种常见情形。
2554+规约结果作为规约实现的状态,设计原理如下:
2555+基于实现的可扩展性、可复用性、复杂性和性能,判断规约终止的谓词(范式判断(@6.7.3.1) 谓词)不一定适合在单独的规约过程中指定。
2556+引入可指定规约不终止的显式的重规约状态代替范式判断谓词应对这些问题,允许项在最终取得范式前附加可变状态,并优化范式判断(@6.7.3.1) 。
2557+区分规约终止的不同情形能在一定程度上复用已有的列表节点(@6.2.1) ,避免规约时节点或其它数据结构的一些冗余创建。
2558+规约结果(@6.7.1) 以枚举 NPL::ReductionStatus 表示,其枚举项的值具有含义:
2559+ReductionStatus::Partial 指定部分规约;
2560+ReductionStatus::Neutral 指定中立规约;
2561+ReductionStatus::Clean 指定纯值规约;
2562+ReductionStatus::Retained 指定非纯值规约;
2563+ReductionStatus::Regular 指定已取得正规表示(@6.7.7) 的规约(当前实现的枚举值的数值同指定非纯值规约);
2564+ReductionStatus::Retrying 指定重规约。
2565+已取得正规表示的规约未指定公开的规约结果覆盖行为(@6.7.4.3) ,可以实现为中立规约、纯值规约或非纯值规约;当前实现同非纯值规约会覆盖默认的纯值规约,避免不必要的清理(@6.7.2.1) 而有利于性能。
2566+
2567+@6.7.2 子项规约:
2568+由树规约的性质(@6.2) ,可假定规约不改变参数外部的项的有效性,不需要外部项的存在性及结构进行额外的检查以确保继续满足相关前置条件(如子项被规约后所在的项总是继续为枝节点(@6.2.1) )。
2569+
2570+@6.7.2.1 子项的清理(cleanup) :
2571+纯值规约(@6.7.1) 要求清理操作,即移除不被需要(@6.7.1) 的子项。
2572+清理时子项所有的对象被销毁,可具有副作用(@2.3.4) 。
2573+子项的清理属于删除子项的操作(@6.4.8) 。
2574+
2575+@6.7.3 规约范式:
2576+被规约项可能通过规约得到范式(@4.4.3) 。
2577+基于规约范式,提供以下规约检查 API :
2578+函数 NPL::CheckReducible 检查参数指定的规约结果是否可继续规约。
2579+函数模板 NPL::CheckedReduceWith 循环规约直至不满足 NPL::CheckReducible 的判断结果。
2580+
2581+@6.7.3.1 范式判断:
2582+基于范式的定义,终止的(@6.7.1) 规约迭代应对 TermNode 参数进行规范化(@4.4.3) 以得到范式。
2583+NPLA 提供以下等价的方式判断规约迭代后是否在 TermNode 得到范式:
2584+除非另行指定,可直接使用 NPL::CheckReducible 以规约结果(@6.7.1) 决定是否为可继续规约的非范式;
2585+通过检查节点结构的谓词确定是否为枝节点(@6.2.1) 等作为范式判断谓词进行判断;
2586+其它派生实现指定的范式的判断方式。
2587+以上等价性由特定的 NPLA 规则(如 @6.7.8 )和规约实现机制保证。
2588+因为只检查规约结果的值而不访问项,使用 NPL::CheckReducible 代替范式判断谓词(@6.7.1) 一般能优化性能。
2589+基于上述等价性保证,上述等价方式中的范式判断谓词可蕴含 NPL::CheckReducible 的结果,必要时也可用范式判断谓词代替规约结果,详见 @6.7.9 。
2590+
2591+@6.7.3.2 规范化规约(@4.4.3) 约定:
2592+除非另行指定(@6.7.9) ,一次终止的规约迭代中若存在规范化规约,其发生的次数和时机未指定;一般在最后一遍(@6.7.4) 或之前存在一次即可。
2593+注意规范化规约可能有副作用(@2.3.4) ,实现应保证规约行为可被预期。
2594+
2595+@6.7.4 规约迭代:
2596+对项的一次规约可分解为若干个对这个项的分步规约的有序的迭代过程,每个过程称为一个遍(pass) ;另见 @6.7.4.3 。
2597+一次规约中有且仅有最后一遍规约迭代终止(@6.7.1) ;重规约(@6.7.1) 或通过抛出异常退出的迭代是一遍非终止的迭代。
2598+一次不异常退出的规约按规约结果总是对应部分规约以外的规约结果(@6.7.1) 之一。
2599+
2600+@6.7.4.1 规约迭代结果:
2601+一次规约取得一个规约结果。
2602+规约结果被用于在每次规约后维护被规约项的表示(@6.7.7) 。
2603+若无法利用规约结果,在不需要保留项时,可能需要其它的显式的清理(@6.7.2.1) 操作。
2604+
2605+@6.7.4.2 规约结果合并:
2606+为避免保存规约结果的不必要开销,每一遍的规约结果不被提供给其它遍共享地访问。
2607+不同遍的规约结果可能不同。项的一次规约的结果由被调用的一个或多个遍的结果顺序地迭代决定:
2608+未经遍调用迭代的规约结果为中立规约(参见 @6.7.4.3 );
2609+之后每一遍返回后,逐一合并(combined) 之前遍决定的规约结果和新的遍的结果,作为新一轮迭代后的规约结果。
2610+(注意此处的合并操作不同于函数合并(@4.5.3) 。)
2611+项的一次规约的结果为规约迭代的最后一遍合并后得到的结果。
2612+迭代处理不同遍的规约结果仍有开销,但即便作为纯软件实现,通常仍远小于直接保存每个遍规约结果的开销。这也允许减少一次规约中不同遍之间的重复的维护操作(@6.7.9) 。
2613+
2614+@6.7.4.3 规约结果覆盖:
2615+一次规约中,某一轮被迭代处理的遍的规约结果可能和处理结果相同,即覆盖当前处理的规约结果。
2616+规约成功终止的规约结果不被不表示规约成功终止的规约结果覆盖,以满足范式的语义(@6.7.3) 。
2617+规约成功终止的不同规约结果可能支持互相覆盖。
2618+由于规约结果作用不同,不同规约覆盖不一定被同等地支持。(这同时避免需要过多的不同的规约结果表示不同的覆盖操作。)
2619+覆盖行为不应影响单一遍的规约结果的语义正确性。对子项的影响明确不同的规约不能互相替换。
2620+例如,中立规约实现处理为纯值规约(@6.7.1) 或非纯值规约是未指定的。但中立规约明确不清理子项,一般不能用纯值规约代替,不论纯值规约是否按以下规则覆盖其它规约结果。而为避免实现细节,中立规约也不能用非纯值规约替换。
2621+当前规约结果覆盖使用以下对称覆盖设计:
2622+未被规约的初始状态假定为中立规约,不影响子项并允许被之后的规约结果覆盖。
2623+规约成功终止时,明确指定纯值规约或非纯值规约的规约结果在合并过程中可覆盖其它规约结果。
2624+一般地,一个项上的规约成功终止时已决定是否需要保留子项,通常不需要覆盖非默认规约结果。
2625+所以相对地,被确定为非纯值规约的情形不严格需要支持被下一轮规约结果覆盖为纯值规约。若仍要求不保留项,则需其它的显式的清理操作(@6.7.2.1) 。
2626+和纯值规约类似而和非纯值规约不同,中立规约不覆盖其它规约成功终止的规约结果,不影响子项是否需要被清理。
2627+
2628+@6.7.4.4 迭代规约结果的合并算法:
2629+根据以上讨论中的要求,可确定规约结果的合并算法。
2630+成功终止(@6.7.1) 的遍规约迭代结果的合并符合以下规则:
2631+若被迭代的新一轮的遍的指定可覆盖其它结果的规约结果(@6.7.4.3) ,则指定合并的规约结果为此规约结果;
2632+否则,新一轮合并的规约结果为之前的遍合并的规约结果。
2633+一般的序列规约结果合并符合以下规则:
2634+若之前的遍合并的规约结果指定规约成功终止,则使用以上关于成功终止的规约结果的合并规则决定新一轮合并后的规约结果;
2635+否则,新一轮合并的规约结果同之前的遍合并的结果。
2636+注意和非纯值规约(@6.7.1) 不同,中立规约(@6.7.1) 不会影响保留的规约结果。
2637+NPLA 实现提供支持可调用的遍(@6.7.4) 的集合作为一次规约迭代的基础可配置的边界。
2638+NPLA 提供以下处理遍的规约结果合并(@6.7.4.2) 结果的 API :
2639+函数 NPL::IsOverridableReductionResult 判断参数指定的规约结果在合并中是否可被覆盖(@6.7.4.3) 。
2640+函数 NPL::CombineReductionResult 和 NPL::CombineSequenceReductionResult 提供规约迭代结果(@6.7.4.1) 的成功终止和一般规约结果的合并操作。
2641+
2642+@6.7.5 规约函数:
2643+接受 TermNode 和其它参数进行规约的 API 主要以宿主语言的函数(子例程)的形式提供,称为规约函数。
2644+规约函数以被规约的项作为第一形式参数。
2645+规约函数可能返回规约结果(@6.7.1) 。
2646+规约函数可以调用其它规约函数实现。
2647+注意因为可直接忽略被调用的规约函数的返回值并指定其它值,规约函数的实现的分类不一定具有组合性。
2648+
2649+@6.7.6 规约函数(@6.7.5) 形式约定:
2650+规约函数(@6.7.5) 包括以下形式:
2651+第一参数为被规约的项,类型为 TermNode& ;
2652+或者,第一参数是和 TermNode& 对应的容器的以下至少之一:
2653+ 有效的 TermNode::Container& 值或 TermNode::Container 对象上的连续序列的迭代器范围之一;
2654+ 可作为值数据成员(@6.2) 的有效的 ValueObject& 值。
2655+替代 TermNode& 的参数的形式允许不构造完整的 TermNode 而允许更好的性能,但仅在保证不要求直接使用 TermNode (如取得 O(1) 的 size() 或调用遍(@7.4.1) )时适用。
2656+规约函数的返回类型是 ReductionStatus(@6.7.1) 或 void 。
2657+返回 void 的规约函数在规约结果(@6.7.1) 的语义上同总是返回 ReductionStatus::Clean(@6.7.1) 。
2658+部分遍(@6.7.4) 及规约函数以 ContextNode(@6.8.3) 的引用作为参数。声明时,ContextNode 不需要是完整类型。
2659+
2660+@6.7.6.1 直接和间接规约函数:
2661+一些规约函数被设计为兼容签名 ReductionStatus(TermNode&, ContextNode&) ,称为直接规约函数。
2662+其余规约函数是间接规约函数。
2663+直接规约函数可直接用于作为派生实现中的 EvaluationPass(@7.4.1.2) 遍的处理器(@7.2.1) 。
2664+间接规约函数可能直接或间接调用直接规约函数。
2665+规约函数对表示被规约的项以及上下文(若存在)或其上述对应形式的形式参数的使用应符合 NPLA 实现的规约迭代默认的约定(@6.7) 。
2666+构成直接规约函数类型的一个必要非充分条件是:
2667+第一参数的类型是 TermNode& ,且当第二参数存在时,其类型可隐式转换为 ContextNode& 。
2668+
2669+@6.7.6.2 名称约定:
2670+以下规约函数的名称以 Reduce 起始:
2671+直接规约函数(@6.7.6.1) ;
2672+起始两个形式参数符合直接规约函数要求,且可能直接或间接调用其它直接规约函数的间接规约函数。
2673+不调用其它直接规约函数的间接规约函数的名称以 Evaluate 起始。
2674+其余规约函数的名称不以 Reduce 或 Evaluate 起始。
2675+注意不同实现中的命名空间可能不同,在此不作限制。
2676+
2677+@6.7.6.3 主规约函数:
2678+NPLA 的派生实现可提供对一般的规约(@4.1) 的实现以蕴含对象语言表达式的求值规则。
2679+NPL 的表达式的语法是递归表示的,因此包括对列表表达式子项的递归规约。使用单一规约函数实现的这种规约一般即树规约(@6.2) 。
2680+规约实现作用在对象语言表达式上通用的求值算法(evaluaton algorithm) ,符合规约性质(@6.3) 。
2681+主规约函数是提供这种单一规约函数实现的直接规约函数(@6.7.6.1) 。
2682+间接值(@6.4.3) 对主规约函数透明,不被直接处理。
2683+推论:主规约函数实现的求值算法中,被求值的表达式不发生值类别转换(@5.8.4) 。这简化实现的复杂性,并允许派生实现单独处理需要间接值(如引用值)的情形,并在其它情形避免不需要的开销。
2684+
2685+@6.7.7 规约表示(@6.3) 的正规性(regularity) :
2686+规约结果(@6.7.1) 和求值结果(@4.1) 或求值的中间结果(@6.3) 的表示有如下关系:
2687+纯值规约(@6.7.1) 后,仅由被规约的项的值数据成员(@6.2) 决定规约得到的值;
2688+纯列表规约(@6.7.1) 后,仅由被规约的项的子项决定规约得到的值,即得到真列表(@6.2.1) 。
2689+满足上述约定的求值结果的表示(@6.3) 是正规(regular) 表示;否则,求值结果的表示是非正规(irregular) 表示。
2690+表达式具有正规表示。
2691+空求值(@4.4.2) 不改变表示的正规性。
2692+平凡(trivial) 非正规表示保证可被(此次规约中剩余的操作)安全忽略而不改变规约的语义。
2693+对被求值项(@6.3) 的任一遍(@6.7.4) 规约都可引入非正规表示,但应满足取得范式的要求:
2694+被求值项取得平凡非正规以外的表示是取得范式(@6.7.3) 的必要非充分条件。
2695+为取得范式,可能需要继续进行规范化规约(@4.4.3) 。
2696+求值规约(@4.4) 可包含规范化规约。
2697+作为正规表示的表达式项的值数据成员(@6.2) 称为正规值(regular value) 。
2698+不论是否正规表示,项应可复制和转移,且不影响其中值的有效性。
2699+推论:非正规表示中不能依赖特定对象的同一性(如值数据成员直接引用子项)。
2700+
2701+@6.7.8 规约表示基本性质:
2702+除非另行指定(参见非平凡非正规表示(@6.7.11) ),NPLA 实现应保证规约表示满足以下基本性质:
2703+任一遍规约中,非正规表示应是平凡的。
2704+规约表示基本性质允许在已知 TermNode 得到范式时通过直接判断项的节点结构(@6.2.1) 是否存在子项代替推断此次规约中之前的规约结果。
2705+在对象语言输入可保证不存在非正规表示的前提下,这样的设计满足原则 @1.4.1.1 和 @1.4.2.2 。
2706+若不能保证不引入非正规表示,实现可通过正规化操作(@6.7.9) 约束规约后的项,以确保接口行为满足上述规约语义的保证。
2707+
2708+@6.7.9 正规化(regularization) 操作:
2709+一次规约后,被规约的项中的值数据成员或子项仍然可保留其它状态而非范式;对表示求值的情况,也不是正规表示(@6.7.7) 。
2710+因为规范化规约可能存在副作用(@6.7.3.2),NPLA 约定求值得到正规表示的规范化规约在抽象机(@2.6) 的意义上总是被进行,称为正规化操作。
2711+纯值规约(@6.7.1) 的正规化操作对子项进行清理(@6.7.2.1) 。由纯值规约的语义,被清理的子项不应影响项作为求值结果的表示。若应清理的子项存在,则清理前为平凡(trivial) 的非正规表示。
2712+若需避免子项的生存期扩展到所在的项,需确保对平凡非正规表示总是存在可预期的清理操作。
2713+纯值规约中存在的清理和修改值数据成员的作用仍非决定性有序(@6.2) ,因此一个项中的清理可能先序(@4.4.1) 最终确定作为值的表示的对值数据成员的修改操作。
2714+因为子项的删除时机未指定(@6.2) ,不假定 NPLA 实现的规约清理求值后的节点,即清理由派生的具体规约实现指定。
2715+一般在一个表达式取得求值结果(@6.7.1) 前,清理纯值规约要求排除的子项。
2716+纯列表规约的规范化标记值数据成员为特定的值,可以是默认构造的空值或派生实现定义的表达式项(@6.3) 内的值数据成员外的记号值(@6.6.3.1) 。
2717+除非平凡非正规表示(@6.7.11)(通过先行判断值数据成员而排除)外,当前未从非纯值规约结果中区分纯列表规约,因此不对纯列表规约进行操作。
2718+注意正规化操作和之前的清理操作可能会影响项的生存期;另见规约操作资源(@6.4.7.1) 。
2719+对同一个规约结果的正规化操作是幂等(@4.1) 的,超过一次的连续正规化操作无其它作用。
2720+正规化提供以下 API :
2721+函数 NPL::RegularizeTerm 按规约结果正规化(@6.7.9) 项。
2722+
2723+@6.7.10 正规表示分类:
2724+基于规约表示基本性质(@6.7.8) ,除非另行指定(参见非平凡非正规表示(@6.7.11) ),不需要单独判断正规表示。
2725+基于正规化操作规则(@6.7.9) ,通过以下逻辑对作为表示(@6.3) 的 TermNode 分类:
2726+具有非空子项的 TermNode 表示非空列表(@6.2.1) ;
2727+空节点表示空列表(@6.2.1) ;
2728+其它项当值数据成员不是特定的值时,表示非列表表达式的值。
2729+值数据成员为特定的值(如没有约定在对象语言中可表示的中间值(@6.6.3) )的没有子项的 TermNode 不是任何表达式的表示。
2730+注意值数据成员为 TermNode 类型的节点不表示列表节点。
2731+
2732+@6.7.11 非平凡(non-trivial) 非正规表示:
2733+基于非纯列表规约的非纯值规约,允许存在子项和值数据成员都有意义的非平凡非正规表示。
2734+这样的表示要求先通过值数据成员进行判断,以排除为列表项。
2735+NPLA 使用的子对象引用的非正规表示应符合以下约定:
2736+只存在以下和派生实现另行指定的特定有限种类的非平凡非正规表示受支持;否则,行为未定义。
2737+当前实现只支持以下有限情形的非平凡非正规表示。
2738+
2739+@6.7.11.1 子对象引用(@6.6.3.3.5) :
2740+值数据成员持有 NPL::TermReference 类型的值,保留某个子项的引用。
2741+子对象引用的项的子项数应为 1 ,该子项持有 shared_ptr 的实例的非空值且其指向的对象和值数据成员持有的 NPL::TermReference 值的 get() 结果应引用同一个项对象。
2742+
2743+@6.7.11.2 带有记号值的列表:
2744+表达式在求值过程中的中间表示可在列表的表示的基础上,用值数据成员附加记号值的表示作为辅助信息。
2745+
2746+@6.7.12 辅助规约函数:
2747+可作为规约函数(@6.7.6) 的不依赖上下文的 API 的辅助函数称为辅助规约函数,能处理一般情形的项,不存在作为断言的要求项非空的前置条件。
2748+
2749+@6.7.12.1 简单规约操作:
2750+NPLA 提供只依赖项既有结构的项简单规约操作。这些操作是直接规约函数(@6.7.6.1) 。
2751+函数 NPL::ReduceBranchToList 要求参数是枝节点(@6.2.1) ;移除第一个子项,剩余项作为列表的元素,并返回 ReductionStatus::Retained(@6.7.1) 。
2752+函数 NPL::ReduceBranchToListValue 要求参数是枝节点;移除第一个子项,剩余项作为列表的元素,调用 NPL::LiftSubtermsToReturn(@6.6.7.2) 提升子项的值,并返回 ReductionStatus::Retained(@6.7.1) 。
2753+这保证最外的第一级引用被提升,不影响被引用项自身包含的引用。
2754+函数 NPL::ReduceForLiftedResult
2755+函数 NPL::ReduceHeadEmptyList
2756+函数 NPL::ReduceToList
2757+函数 NPL::ReduceToListValue
2758+
2759+@6.7.12.2 间接辅助规约操作:
2760+以下辅助规约函数转移参数实现规约:
2761+函数 NPL::ReduceToReference
2762+函数 NPL::ReduceToReferenceAt
2763+函数 NPL::ReduceToValue
2764+
2765+@6.8 运行时状态管理:
2766+NPLA 提供维护程序运行状态的数据结构的接口,包括环境、上下文和相关的其它 API 。
2767+
2768+@6.8.1 环境数据结构:
2769+环境对象(@5.7.1) 以 Environment(@6.8.1.1) 类相关的类型表示。
2770+显式的数据结构直接支持求值环境是一等环境(@5.7) 。
2771+TermNode 对象在名称绑定映射中表示被绑定对象(@5.7) 。
2772+当前实现中,环境中的绑定是平坦的映射,未依赖节点的递归性质;但是其中的被绑定对象(@6.8.1) 使用 TermNode ,也适用以下操作。
18802773 名称绑定映射释放变量绑定中的资源时,其中不同被绑定对象的销毁之间非决定性有序(@4.4.1) ,其它作用顺序未指定。
18812774 注意名称绑定映射中不同被绑定对象的生存期的起始和结束的顺序一般是不可追溯的,这和宿主语言在一些上下文中静态确定并保证销毁自动对象和创建对象的顺序相反不同。
1882-在对象语言的环境以表示环境的引用(而不是环境对象自身的)一等对象(@4.2.1) 表示,称为环境引用。
1883-环境引用共享环境对象的所有权。
1884-根据所有权(@4.2.2.3) 管理机制的不同,环境引用包括环境强引用(strong reference) 和环境弱引用(weak reference) 。
1885-引入环境弱引用作为一般的引用机制,且仅在必要时使用环境强引用,以避免过于容易引入循环引用引起强内存泄漏(@5.2.4.4.1) ,符合 @1.4.5.2 。
1886-传递环境引用不直接复制环境对象。另见引用(@4.2.3) 。
1887-环境是名称解析(@4.3.3) 时查找名称的目标(@4.3.3) 。
1888-环境可引用关联的其它环境对象为父环境(parent environment) ,用于重定向(@4.3.3) 。
1889-通过其它环境实现重定向的方式表示环境是链接的(linked) 而非平坦的(flat) 。
1890-Environment 支持定制名称解析和名称解析的重定向,其中包含以下相关的定制状态:
2775+Environment 支持定制名称解析和名称解析的重定向(@4.3.3) ,其中包含以下相关的定制状态:
18912776 重定向算法;
18922777 名称解析算法;
1893-可在运行时确定宿主类型(@5.2.3) 的父环境引用(用于重定向算法的实现)。
2778+可在运行时确定宿主类型(@5.5) 的父环境(@5.7) 引用(用于重定向算法的实现)。
18942779 默认的名称解析算法首先在名称绑定映射中查找局部变量的绑定;若失败,调用重定向算法进行重定向,从父环境引用中取目标环境代替环境后代替环境继续使用其中的名称解析算法查找,直至没有可重定向的目标。
1895-默认的重定向算法依次检查重定向目标的宿主值(@5.5) 的类型。
1896-对象语言中的环境引用类型的宿主类型是非递归的重定向目标类型。
2780+默认的重定向算法依次检查重定向目标的宿主值(@6.3) 的类型。
2781+对象语言中的环境引用(@5.7.2) 类型的宿主类型是非递归的重定向目标类型。
18972782 不同宿主值类型具有不同的所有权。作为一等对象的环境引用的一般表达式值中,应根据是否需要具有所有权区分使用这些宿主类型的环境引用。
18982783 和 Kernel 类似,默认的重定向算法默认使用 DFS(Depth-First Search ,深度优先搜索)遍历目标。
1899-相关 API 参见 @6.9 。
1900-
1901-@5.4.4 上下文数据结构:
2784+
2785+@6.8.1.1 环境类:
2786+类 Environment 中,指定变量绑定的名称绑定映射(@6.8.1) 类型为 Environment::BindingMap 。
2787+类 Environment 提供父环境(@5.7) 以及基本的名称解析(@4.3.3) 等操作的接口。其中的操作可被针对特定的环境对象重新设置。
2788+类 Environment 包含以下相关的数据成员:
2789+Bindings :类型为 Environment::BindingMap 的变量绑定容器,实现变量名称到表示被绑定对象的映射(@6.8.1) 。
2790+Parent :作为可在运行时确定类型的父环境引用,用于重定向(@4.3.3) 算法的实现。
2791+类 Environment 还包含其它一些维护内部状态和绑定(@4.1) 的 API 。
2792+成员函数 IsOrphan 判断是否为孤立的环境,即锚对象(@6.6.3.2) 未被外部引用。
2793+成员函数 GetAnchorPtr 取锚对象指针(@6.6.2.2) 。
2794+成员函数 GetMapRef 取名称绑定映射的引用。
2795+静态成员函数 DefaultResolve
2796+的成员函数 Define 、Redefine 和 Remove 修改上下文中的值。
2797+成员函数 Define 和 DefineChecked 添加定义。后者在定义已存在时抛出异常。
2798+成员函数 LookupName 在绑定集合中查找(一般作为局部变量的)标识符指定的名称,实现名称查找(@4.3.3) 。输入字符串指定(假定名称验证已通过的)名称。
2799+成员函数 Remove 和 RemoveChecked 以字符串为标识符在指定上下文移除定义。后者在定义不存在时抛出异常。
2800+成员函数 Replace 和 ReplaceChecked 以字符串为标识符在指定上下文的名称查找结果中替换定义。后者在定义不存在时抛出异常。
2801+
2802+@6.8.1.2 环境和变量绑定相关类型:
2803+NPL::EnvironmentList 是 NPL::vector<ValueObject> 的别名,表示一个环境列表,作为父环境(@5.7) 时可用于递归重定向(@4.3.3) 。
2804+Environment::DefaultResolve(@6.8.1.1) 中的重定向(@4.3.3) 依次检查如下之一的重定向目标的宿主值(@6.3) 类型:
2805+NPL::EnvironmentList ;
2806+shared_ptr<Environment> ;
2807+NPL::EnvironmentReference 。
2808+实现可提供其它宿主类型,如 observer_ptr<Environment> ,用于内部实现而不要求所有使用环境的操作支持。
2809+派生实现可提供其它公开的宿主类型。
2810+
2811+@6.8.1.3 环境引用相关类型和检查:
2812+类型 shared_ptr<Environment> 和类 NPL::EnvironmentReference(作为中间值另见 @6.6.3.2 )是环境强引用和环境强引用(@5.7.2) 的宿主类型(@5.5) 。
2813+访问环境的 API 可使用非空的环境引用值引用对象,或空的引用值指定不存在环境对象。
2814+使用可能具有这些值的代码需要对空值进行检查。因为这些空值不出现在对象语言,一般通过 Environment::ThrowForInvalidValue(@6.8.1.1) 抛出 std::invalid_argument 而非 NPL 异常(@6.5) 。
2815+使用环境弱引用可避免非预期的所有权(@4.2.2.3) 关系构成循环引用(@5.2) 。
2816+当前支持循环引用检查的上下文有:
2817+Environment::DefaultResolve(@6.8.1.1) 重定向父环境(@5.7) ;
2818+以引用的环境作为动态环境创建 vau 抽象(@8.4.5) 且创建的对象不对环境有所有权。
2819+因为循环引用是对象语言的未定义行为,检查循环引用失败抛出 std::bad_weak_ptr 而非 NPL 异常(@6.5) 。
2820+对象语言中支持环境引用的宿主值类型是 Environment::DefaultResolve(@6.8.1.1) 中的重定向目标的宿主值类型(@6.8.1.2) 中公开的类型,即:
2821+shared_ptr<Environment> ;
2822+NPL::EnvironmentReference ;
2823+派生实现定义的提供明确支持的其它类型。
2824+除 shared_ptr<Environment> 的 use_count 的结果外,环境的引用计数具体值是实现细节,不保证具有明确含义的稳定的结果。
2825+
2826+@6.8.2 上下文相关的规约函数类型别名:
2827+类型 ReducerFunctionType
2828+类型 NPL::Reducer 是以上下文引用作为参数的规约器(@6.9.6) ,保存动作(@6.8.3) 。
2829+NPL::Reduer 能以兼容 ReducerFuntionType 的方式调用,可选提供一个 ContextNode& 参数。
2830+
2831+@6.8.3 上下文数据结构:
19022832 上下文(@4.6.1) 使用 ContextNode 类表示。
1903-ContextNode 对象保存上下文相关的公共状态,具体接口详见上下文 API(@6.9.4) 。
2833+ContextNode 对象保存上下文相关的公共状态。
19042834 上下文的公共状态包括当前处理的环境,称为环境记录(environment record) 。
1905-环境记录指定了活动记录中当前的处理的帧(@4.5.3.4) ,指定对象语言中对应当前处理的帧的当前环境(current environment) 。
1906-上下文对环境具有共享所有权(@5.2.4) ,通过环境对象的引用可以取指向它的引用(@6.9.1) 。
1907-上下文支持设置对应规约迭代(@5.8.3) 中对应的规约动作(action) ,保存特定的当前动作(current action) 和若干后继的定界动作(delimited actions) 组成当前动作序列(current action sequence) 。
2835+环境记录的引用指定了活动记录中当前的处理的帧(@4.5.3.4) ,指定对象语言中对应当前处理的帧的当前环境(@5.7.3) 。
2836+上下文对环境具有共享所有权(@5.6) ,通过环境对象的引用可以取指向它的引用(@6.8.1.1) 。
2837+只使用上下文中包含的当前环境时,可使用 ContextNode(@6.8.3.1) 和 Environment(@6.8.1.1) 的成员访问环境的数据。
2838+需要上下文时,一般使用现有(通过规约函数的参数得到的)上下文,可能替换和重置状态;或通过现有上下文和环境创建新的上下文。
2839+上下文支持设置对应规约迭代(@6.7.4) 中对应的规约动作(action) ,保存特定的当前动作(current action) 和若干后继的定界动作(delimited actions) 组成当前动作序列(current action sequence) 。
19082840 其中,当前动作和后继的动作分别表示元语言中处理当前和后继的求值上下文中的操作。
19092841 规约动作可选地指定上下文参数。
19102842 当前动作作为宿主语言的函数对象被调用,激活(activate) 其中的规约操作,其中可覆盖当前动作为后继的动作。
@@ -1914,1222 +2846,255 @@
19142846 以抽象机描述操作语义(@2.2.3) 时,定界动在尾上下文外提供剩余的动作,作为一个序列,称为尾部动作(trailing actions) ,是当前动作的后继动作(subsequent action) 。另见 http://www.brics.dk/RS/03/41/BRICS-RS-03-41.pdf 。
19152847 当前动作起始的当前动作序列表示语言实现中(不作为一等对象(@4.1) 的)未被捕获的当前续延(@4.5.2.1) 。
19162848 动作和尾部动作整体构成求值上下文(@4.4.7) 的实现。后者在元语言中提供操作,又称为元上下文(meta-context) 。另见 http://www.brics.dk/RS/05/16/BRICS-RS-05-16.pdf 。
1917-当前动作可作为尾上下文(@4.4.7) 的实现,称为尾动作(tail action) 。特定的尾动作可实现符合 PTC 的尾调用(@5.2.6.2) 。
1918-配合重写循环提供基于尾动作的循环 API 可实现 CPS(contiuation passing style) 风格的异步调用。相关 API 参见 @6.9 。
1919-
1920-@5.5 表达式和求值(@4.4) :
1921-NPLA 表达式及其求值过程中得到的值以若干个 TermNode(@5.4.2) 、TermNode 子项或值数据成员(@5.4.2) 表示。
1922-用于表示表达式的 TermNode(@5.4.2) 对象称为表达式项(expression term) 。
1923-TermNode 的子项(@5.4.2) 在表达式项中作为子表达式(@3.4.2) 的实现。
1924-一个被求值的表达式以一个作为被规约项(@5.4.2) 的表达式项表示,称为被求值项。
1925-被求值项经求值规约(@4.4) 取得求值结果(@4.1) 。
1926-求值结果也用 TermNode 对象表示,以支持树规约(@5.4.2) 中直接基于项的替换操作实现求值。
1927-求值结果可以是具有 TermNode 子项的列表(@5.4.2.1) 数据结构,详见 @5.9 。
1928-TermNode 还可能表示求值的中间结果(即求值规约中取得对象语言表达式的值之前出现的值),如表示某些中间值(@5.6) 的情形。
1929-除非中间值也作为一等对象的表示(@2.3.4) ,NPLA 的规约范式(@5.8.4) 不会使用这些形式表示。
1930-NPLA 允许(但不要求对象语言支持)以一等对象作为表达式的表示并被求值。
1931-表达式、表达式在求值过程中的中间表示(如中间值(@4.4.4.2) )和求值结果的表示统称规约表示。
1932-在规约时,上下文(@4.6.1) 指定当前被求值项,确定当前被规约的 TermNode 。
1933-表示值的 TermNode 是宿主语言对象具有的特定动态类型(@4.6.2) 的值是宿主值(hosted value) ,具有不固定的宿主类型(@5.2.3) 。
1934-宿主值可包括值数据成员和子节点中的各个值数据成员中所有被擦除类型存储的对象,即宿主对象(hosted object) 。
1935-典型情况下,值数据成员对应单一的宿主值。
1936-除非另行指定,类型映射(@5.2.3) 的值是单一的宿主值。
1937-NPLA 表达式求值取得的范式(@4.4.3) 总是其表示(一个 TermNode 对象)中的子项或值数据成员之一,详见 @5.9.2 。
1938-作为 @4 的扩展,作用于非表达式项上的规约规则不是求值规则。
1939-
1940-@5.5.1 值类别(@4.2.2.1) :
1941-和 ISO C++17 (由提案 [WG21 P0135R1] 引入的特性)类似,表达式项(@5.5) 及在表达式求值的规约中得到的项具有以下值类别之一:
1942-泛左值(glvalue) :求值用于决定被表示的对象的同一性(@4.1) 的项;
1943-纯右值(prvalue) :求值不用于决定对象同一性(而仅用于初始化(@5.5.2) 临时对象(@5.5.6) 或计算对象中存储的值)的项。
1944-一个项可能被标记为消亡值(xvalue) 或附加其它元数据(如通过间接的引用(@4.2.3.3.1) ),以提供基于不同的所有权的行为。
1945-左值(lvalue) 是除了消亡值外的泛左值。
1946-右值(rvalue) 是消亡值或纯右值。
1947-求值涉及项的值类别仅在必要时约定。
1948-表达式的值类别同表示表达式的项的值类别。
1949-作为名称表达式(@4.5.1) 明确引用直接存储在环境中的对象及其包含的(数据成员)子对象的项都是左值。
1950-右值一般存储在环境以外的项上;必要时,实现可能存储右值在特定的环境中。
1951-值类别根据是否只关心表达式关联的(对象的或非对象的)值,在需要对象时提供区分两类一等对象(@4.2) 的机制,同时避免在仅需要表达式关联的值时引入不必要的其它对象。
1952-另见引用(@4.2.3) 对一等状态(@4.2.2.1) 的支持(@4.2.3.3.2) 。
1953-
1954-@5.5.1.1 类型系统和表示:
1955-值类别在 NPLA 中被作为一种内建的类型系统,参见 @4.2.6 。
1956-但在类型的值的内部表示上,值类别和特定的类型相关:
1957-当前 NPLA 支持的左值都是项引用(@5.6.3) ,这同时表示引用值(@5.5.4) 。
1958-另见正规性(@5.9) 。
1959-
1960-@5.5.1.2 设计原理:
1961-按已有的规则,NPLA 约定求值至少需要支持以下的重要的性质:
1962-第一,允许表达式直接表示一等实体而不论它是否是一等对象(@4.1)(即便按 @4.2.1 ,默认不会出现这种情形,派生实现仍然可能改变);
1963-第二,使用支持按引用传递的策略(@4.4.4.5) ,允许通过表达式构造引用不同对象的引用类型(reference type) 的值且被作为一等对象(@4.2) 传递(详见项引用(@5.6.3) )。
1964-一般地,这表示引入值类别的差异是必要的。因为,若假设平凡(trivial) 情形下,值类别可退化到都是泛左值或都是纯右值的情形。
1965-若只存在泛左值,则任意的求值都决定一个对象;由于存在和一等对象无关的实体,无法满足第一条性质。
1966-若只存在纯右值,则无法区分不同的对象,形式上无法构造出可用的引用类型的值满足第二条性质。
1967-此外,NPLA 同时约定:
1968-第三,求值满足递归蕴含规则(@4.4.4.1) ;
1969-第四,允许对象的操作保持资源局域性,满足一等对象不依赖引用的抽象(@4.2.3) 同时允许按需区分是否依赖引用的两类一等对象;
1970-第五,避免需要假设存在外部具有被引用对象(@4.2.3) 的所有权的所有者(@5.2.4.4) 。
1971-第三条性质保证表达式的作用(@2.3.4) 是可组合的并允许求值表达为树规约(@5.4.2) ,还保证能总是通过子表达式的值类别决定表达式的值类别。因为被求值的表达式是有限的,判定过程是总是能终止,即便求值不满足强规范化性质(@4.4.3) 。
1972-第四条性质要求提供泛左值总是能作为纯右值使用的机制和通过纯右值引入对象的机制,详见值类别转换(@5.5.5) 。
1973-第五条性质要求在表达式之外不存在地位相同的对象的存储资源(@5.2.4) 的所有者,限定了被决定同一性的对象的外延;存储由环境提供(@5.2.4) ,其中不需要保存对象的引用。
1974-
1975-@5.5.2 初始化:
1976-部分创建对象的表达式引入对象的初始化(initialization) 。
1977-初始化包括被绑定对象(@5.4.3) 的初始化和作为函数值的返回值(@4.5.3.1) 对象的初始化。
1978-决定初始化这些对象的作用(@2.3.4) 的表达式是初始化的初值符(initializer) 。初值符的求值可能有副作用,其求值结果指定特定被初始化的对象的初始值(initial value) 。
1979-初始化被绑定对象可能以修改操作(@4.1.4.2) 的形式体现,此时修改绑定具有副作用。若这样的副作用存在,每个被初始化的值后序(@4.4.1) 于对应初始的计算。
1980-和宿主语言不同,初始化不是独立的依赖特定语法上下文的概念,但此处语义上的作用类似,一般可蕴含宿主对象的初始化。
1981-
1982-@5.5.2.1 复制初始化和直接初始化:
1983-和宿主语言类似,初始化包括直接初始化(direct initialization) 和复制初始化(copy initialization) 。
1984-函数可能接受引用值(@5.5.4) 参数和返回值,是对函数的形式参数或函数值的复制初始化;其它初始化是直接初始化。
1985-复制初始化形式参数和函数值时,函数参数或返回值作为初值符。
1986-
1987-@5.5.2.2 函数参数和函数值传递(@4.4.4.5) :
1988-部分函数可保证被初始化的值和初值符(@5.5.2) 的值及元数据(如项引用的元数据(@5.6.3.1) )一致。
1989-类似宿主语言的完美转发(perfect forwarding) ,这样的参数或返回值的初始化的求值称为转发(forwarding) 。
1990-转发也包括只部分保留上述部分元数据的情形。
1991-在允许保留元数据不变的上下文,转发在本机实现(@5.2.1) 中可直接通过转移项实现。
1992-相对地,完美转发也保持引入这些初始化的表达式(通常是被求值取得函数值的函数表达式)时,其求值结果(函数值)的值类别(@5.5.1) 和初值符保持一致。
1993-
1994-@5.5.2.3 对象的复制和转移:
1995-类似宿主语言中的类类型的值,可使用初值符为参数进行复制或转移操作以复制初始化对象。(其它情形另见 @5.5.6.3 。)
1996-类似 ISO C++11 起选择类的转移构造函数代替复制构造函数,可使用转移操作时,不对项进行复制,因此不要求值数据成员保存的宿主对象可复制(而避免抛出 ystdex::invalid_construction 等实现相关的异常)。
1997-和 ISO C++11 起不同,上述可使用转移操作的条件和语法上下文无关:引起选择转移操作的条件由对初值符的谓词而非类似宿主语言的构造函数判断(详见 @5.5.5.1 )。
1998-同宿主语言,对象的复制和转移不改变被转移后的类型(@4.6.2) 。
1999-
2000-@5.5.3 项的转移:
2001-求值可引起包含宿主对象的被规约项(@5.4.2) 被整体转移,而避免其中包含的对象的初始化在对象语言中具有可见的作用,包括:
2002-以 TermNode 对象的形式使其子项和值数据成员被转移(转移后的项具有有效但未指定的状态(@5.2.4.2) 但不可在对象语言中被访问);
2003-以未指定的其它实现相关的方式使 TermNode 成为其它节点的子项(不存在转移后的项)。
2004-项的转移引起对象的析构性转移(destructive move) :对象在转移后被销毁,其生存期结束。
2005-和对象的转移(@5.5.2.3) 不同,因为被转移的对象的生存期结束,项的转移不保证被转移后的项中的对象的类型不改变。
2006-除了不初始化新的对象和使被转移项中对象的类型改变,项的转移在对象语言中效果可能类似宿主对象的转移。
2007-项的转移保证不无效化(@5.4.2) 子项,也不转移或复制值数据成员(@5.4.2) 所有的宿主对象。
2008-
2009-@5.5.3.1 宿主对象转移消除:
2010-一般地,当对象需要被转移且没有约定转移后要求类型不变(@5.5.2.3) 时,项的转移(@5.5.3) 可代替对象的转移(@5.5.2.3) ,避免初始化新的宿主对象。
2011-项的转移也类似 ValueObject 自身的转移,其实现(当前通过 ValueObject 内部的 ystdex::any 的转移构造函数)保证直接转移对象而不保证转移构造所有的值。
2012-若需调用宿主对象的转移构造函数,需明确避免宿主对象转移消除的上下文中进行操作。派生实现可提供这些操作。
2013-
2014-@5.5.4 引用值(reference value) :
2015-在对象语言中,引用值(reference value) 作为引用的值,可保存在一等对象中,这样的一等对象是引用对象(reference object) 。
2016-引用值和引用对象的值具有引用类型(@5.5.1.2) 。
2017-在特定上下文中引用和其它一等对象(@4.2.1) 的值的相同具有不同的语义。这主要体现在引用值被按值传递和按引用传递时(@4.4.4.5) 。差异和 ISO C++ 的引用类似。
2018-引用值可使用以下方式表示:
2019-引用项(reference term) ;
2020-使用 TermTags::Temporary 标签(@5.4.2.2) 标记的非引用项。
2021-其中,前者是值数据成员为项引用(@5.6.3) 对象的项。
2022-引用项(@5.5.4) 中的项引用对象引用一个(其它的)项,用于在必要时引入可被引用的一个项而不在 TermNode 中直接储存这个项的值。
2023-对实现作为一等对象(@4.1) 的列表(@5.4.2.1) 的引用(@4.2.3) ,支持引用整个项的中间值是必要的;但项引用也支持引用非列表项。
2024-临时对象(@5.5.6) 可作为引用值的被引用对象。
2025-因为临时对象不是一等对象(@5.5.6) ,临时对象的引用值可代替关联的被引用对象使之作为一等对象被访问,因此使用被引用对象添加 TermTags::Temporary(@5.4.2.2) 标签表示。
2026-与此不同,非临时对象的引用值可作为一等对象而总是需要区分作为不同对象的同一性(@4.1) 。
2027-引用值可能是左值引用(lvalue reference) 或右值引用(rvalue reference) 。
2028-前者是排除引用项表示且不是唯一引用(@5.4.2.2) 的引用值;后者是以引用项表示的唯一引用或临时对象初始化的非引用项的引用值。
2029-左值引用和左值引用与宿主语言中的对象类型的左值引用与右值引用分别类似。
2030-引用值是否作为左值(@5.5.1.1) 使用取决于上下文。除非另行指定,引用值都是左值。其中,在要求右值的上下文发生左值到右值转换(@5.5.5) 。
2031-
2032-@5.5.4.1 有效性(@5.7.4) :
2033-有效的引用值应通过特定的构造引用值方式引入,包括:
2034-在对象语言通过被引用对象初始化引用值;
2035-互操作(@5.2.1) 引入的保证不引起未定义行为的引用值。
2036-一些对象语言的操作(如改变被引用对象)可能引起已被初始化的引用值成为悬空引用。
2037-指定引用无效化的具体操作(若存在)由派生实现定义。
2038-访问无效的引用值不满足内存安全(@5.2.4.3) 而引起未定义行为。
2039-
2040-@5.5.5 值类别转换:
2041-和宿主语言类似,具有特定值类别的表达式可转换为不同值类别的表达式:
2042-除非另行指定,泛左值总是允许作为纯右值使用,称为左值到右值转换(lvalue-to-rvalue conversion) ;
2043-从纯右值初始化(@5.5.2) 可被对象语言作为一等对象(@4.1) 使用的临时对象(@5.5.6) 作为消亡值(@5.5.1) ,称为临时对象实质化转换(temporary materialization conversion) 。
2044-左值到右值转换没有副作用(@4.2.2.1) 。临时对象实质化转换没有副作用,当且仅当其中初始化临时对象时没有副作用。
2045-临时对象实质化转换中,纯右值被实质化(materialized) 。
2046-在求值子表达式时,按表达式具有的语义,必要时(如按 @5.5.1.1 判断上下文的值类别)进行值类别转换。
2047-为支持引用值(@5.5.4) 作为一等对象,NPLA 在左值到右值转换的基础上提供更精细的引用值提升转换,即以下转换操作:
2048-若操作数是引用值,则结果是操作数引用的被引用对象;
2049-否则,结果是操作数。
2050-根据引用值的性质,易知左值到右值转换的规约是引用值转换的规约的传递闭包,即:
2051-若操作数是已被折叠的引用值,则引用值提升转换等价左值到右值转换;
2052-否则,有限次的引用值提升转换等价左值到右值转换。
2053-
2054-@5.5.5.1 设计原理:
2055-值类别和左值到右值转换在一些上下文的行为类似箱和自动拆箱(@4.2.3.5.3) ,不利于维护简单性(@1.4.3.2) :
2056-特别地,和宿主语言不同,函数不包含充分的信息(参数类型)推断是否接受左值操作数,因此在不提供针对函数的重载(overloading) 一般机制的前提下,本机实现(@5.2.1) 不能预知输入的操作数是否是左值,通常需分别支持左值和右值的操作数;
2057-即便提供重载,仍然较单一的值类别更复杂。
2058-但 NPLA 的设计中,值类别转换已被通过正确(@1.4.3) 反映需求的存储和对象模型(@5.2.4) 的设计隐含在项的内部性质中,因此不是可选的。
2059-由正确性的优先规则(@1.4.3) ,完整性(@1.4.3.1.1) 应先于简单性被满足。
2060-而考虑统一性(@1.4.5.1) ,对存储和对象模型的设计,用户自行的实现仍要求这些设施(尽管更困难)。
2061-
2062-@5.5.5.2 返回值转换(return value conversion) :
2063-返回值转换是一次引用值提升转换和可选的临时对象实质化转换的复合。
2064-返回值转换(return value conversion) 用于在对象语言中确定函数调用的返回值(@5.2.1) 可包含函数体(@4.5.2) 的求值结果到返回值的转换。
2065-返回值转换的应用参见 @7.1.4.2 。
2066-
2067-@5.5.6 临时对象(temporary object) :
2068-特定的 NPLA 非一等对象(@4.1) 是临时对象(@5.5.6) ,包括:
2069-所有权可被项独占(@5.7.5) 而不能作为一等对象访问的对象;
2070-内部表示具有临时对象标签(@5.4.2.2) 的引用对象(@5.5.4) 关联的被引用对象(@4.2.3) 。
2071-其中,直接构成项的对象可以是通过外部表示(@4.1.1) 通过翻译(@2.4.1) 变换得到的具有内部表示的数据结构(@5.4.2) 的非一等对象。
2072-NPLA 允许(但不要求对象语言支持)一等对象通过特定的求值,在中间结果中蕴含这种非一等对象。
2073-和宿主语言类似,NPLA 对象语言在特定的上下文引入其它临时对象,包括:
2074-实质化转换上下文(@5.5.6.1) ;
2075-返回值转换上下文(@5.5.6.2) 。
2076-关于临时对象的存储和所有权,参见求值和对象所有权(@5.2.4.2) 。
2077-为简化规约和互操作(@5.2.1) 机制的设计,和 ISO C++17 不同,引入临时对象不包括延迟初始化或异常对象的创建。
2078-关于避免特定相关对象的初始化(@5.5.2) 的要求,参见复制消除(@5.5.6.3) 。
2079-
2080-@5.5.6.1 实质化转换上下文:
2081-可要求临时对象实质化转换的上下文包括:
2082-使用纯右值初始化按引用绑定的变量(如函数的引用类型的形式参数(@4.5.2) );
2083-求值函数调用以初始化返回值对象。
2084-其中,按引用绑定的变量被可选地支持;若被支持,按引用绑定的对象具有临时对象标签(@5.4.2.2) ,引入引用值的形式参数的具体形式由派生实现指定。
2085-一般地,按引用绑定的变量在活动调用(@4.5.3.1) 关联的环境分配临时对象。此时,对象被调用表达式的项独占所有权,同时被绑定的环境独占资源所有权。
2086-临时对象实质化转换引入临时对象的规则和 ISO C++17 不同:
2087-不论表达式是否作为子表达式使其值被使用(未使用的情形对应 ISO C++ 中的 discarded-value expression ),都允许存在临时对象;
2088-
2089-@5.5.6.2 返回值转换上下文:
2090-返回值转换(@5.5.5.2) 可引入实质化的临时对象,其中可能转移求值的中间结果(条件参见 @5.6.3.4 );否则,对象被复制。
2091-此处被转移对象符合求值和对象所有权(@5.2.4.2) 规则中的临时对象的定义,但除非另行指定,被转移的对象不在对象语言中可被访问。
2092-此处的转移引起宿主对象转移消除(@5.5.3.1) 。
2093-仅在对象被复制且复制具有副作用时,返回值转换具有等价复制的副作用。
2094-
2095-@5.5.6.3 复制消除(copy elision) :
2096-和 ISO C++17 类似,NPLA 要求特定上下文中的复制消除,排除复制或转移操作且保证被消除操作的源和目的对象的同一性(@4.2.1.1) 。
2097-和 ISO C++17 不同,NPLA 的复制消除限于临时对象的消除,但不限制对象的类型(特定的 C++ 类类型),且除以下约定外的所有表达式求值的上下文对复制消除相同(例如,不区分求值结果是否被作为返回值(@4.5.3.1) 或求值是否为常量表达式)。
2098-复制消除仅在以下转换上下文中被要求,即直接使用转换的源表达式中被求值项指称的对象(@5.7.5) 作为实质化的对象而不初始化新的临时对象:
2099-实质化转换上下文(@5.5.6.1) ;
2100-引起对象转移的返回值转换上下文(@5.5.6.2) 。
2101-非本机实现(@5.2.1) 函数函数体内指定的返回值不属于上述的确定返回值的上下文,但也不要求被复制消除。这和 ISO C++17 要求 return 语句中的特定的表达式被消除不同,而不需要依赖特定上下文的语法性质。
2102-实现仍可能根据当前环境(@5.4.4) 来判断是否在允许消除对象复制的上下文中(当前未实现),而进行复制消除。
2103-在完成实质化转换前的不完整的求值规约(@4.4) 中的临时对象在逻辑上不需要作为一等对象(@4.1) 存在,但纯右值作为对象表示中的子项,随纯右值在宿主语言中作为对象存在,以允许互操作。
2104-复制消除不在被初始化对象以外引入新的项。若被消除的对象来自不同的项,则复制消除为宿主对象转移消除(@5.5.3.1) 。这包括所有对象转移的返回值转换上下文的情形(@5.5.6.2) 。
2105-在使用纯右值初始化引用值时,延长源表达式的项指称的对象(@5.7.5) 的生存期(@5.2.5) 。这和初始化非引用值类似,但实现需区分是否初始化的是延长生存期的临时对象,以确保之后能区分是否按引用绑定。
2106-
2107-@5.5.7 表达式的类型:
2108-NPLA 的类型系统(@4.6.2) 使用隐式类型(@4.6.2.2) ;和 Scheme 及 Kernel 类似,默认使用潜在类型,保证表达式的值具有类型(@5.2) 。
2109-NPLA 表达式的类型约定为表达式求值结果(@4.1) 的类型。
2110-空求值(@4.4.2) 的求值结果要求未求值的合式的(@2.5.1) 表达式应具有和语法分析(@5.3.2) 的输出兼容的类型,参见 @5.4.2.1 和 @5.6.1 。
2111-注意表达式的类型和 [R7RS] 的 expression type 无关,后者是语法形式(@3.4.3) 的约定;因为存在合并子(@4.5.3.2) 作为一等对象(@4.1) 的类型,不需要这种约定。
2112-实现对特定的上下文的表达式可使用类型推断(@4.6.2.2) 。由此确定的类型类似宿主语言的表达式的类型。
2113-和 Scheme 和 Kernel 不同而类似宿主语言,表达式具有值类别(@5.5.1) 。
2114-值类别的指派是类型系统的一部分(@5.5.1.1) 。但除非另行指定,类似宿主语言,值类别和表达式的类型正交,以简化操作涉及类型的指定。
2115-和宿主语言不同,语言机制不限制值类别和其它类型的判断操作和结果(@4.1) 被作为一等对象(@4.2.5) ,尽管不一定直接提供。
2116-
2117-@5.6 NPLA 中间值:
2118-中间值(@4.4.4.2) 可在 TermNode 的值数据成员(@5.4.2) 中存储。
2119-和具体求值(@4.1) 的求值结果(@4.1) 及其它情形存储的对象不同,中间值可能参与特定的规约作为求值的中间步骤的表示。
2120-为允许区分中间值和被映射的其它值,约定中间值具有特定的类型。但以中间值引入的类型,其值仍可能作为求值结果。后者实质被对象语言作为一等对象(@4.1) ,称为一等中间值(first-class thunk) 。
2121-NPLA 提供名义(nominal) 中间值,即不是其它 NPLA 外类型的别名的类型,但这些类型考虑由其它类型转换或构造。
2122-派生实现不提供不依赖于非 NPLA 和 NPLA 实现定义的类型的别名作为非名义中间值类型,即若提供非名义中间值类型,应为参数化类型,且其中至少包含一个实现自定义的类型作为模板实际参数。
2123-其中:
2124-记号值(@5.6.1) 构成的项可表示未求值的表达式。
2125-记号值和项引用(@5.6.3) 可作为某些对象语言的表达式的表示(@5.5) 。
2126-NPLA 扩展中间值(extended thunk) 是其它由派生实现定义的其它中间值。
2127-
2128-@5.6.1 记号值(token value) :
2129-NPL::TokenValue 类型的值表示记号(@3.3.1) ,可使用 string 类型的值构造,记号值类型。
2130-由 @5.2.3 ,记号值对应的词素(@3.3.1) 作为名称,宿主类型为 string 。记号值对应的词素的宿主值和构造记号的 string 类型的值相等。
2131-记号值和词素在逻辑上非空,但因为除翻译单元外的外部表示(@2.3.4) 一般未指定(@4.1.1) ,不保证在 API 中取得的这些类型对应非空串,因此除特定的内部实现外不省略空串检查。
2132-和字符串值不同,记号值求值的结果不会继续是记号值,以避免不经意的递归求值或无法和宿主语言字符串值区分的情形出现。
2133-但在 API 层次上,记号值的求值不排除无限循环或递归,不保证对应的项作为表达式时具有强规范化性质(@4.4.3)。实现需注意保证作为名称表达式时满足强规范化要求(@5.2) 。
2134-表示一个未被求值的非字面量(@3.3.3) 记号的记号值称为符号(@2.3.4) 。符号可构成名称表达式(@4.5.1) ,也可作为一等中间值。
2135-符号的宿主类型(@5.2.3) 是 NPL::TokenValue 。
2136-和 Kernel 类似,NPL 设计强调区分程序是否被求值的差异。符号是引入和表示为自求值项(@4.4.3) 的其它表达式的关键数据类型。
2137-符号可能有外部表示。以未求值的记号值作为符号是其和外部表示关联的设计。由于外部表示未指定,这种关联是有限的。记号值蕴含了词素和符号的映射关系,即源代码文本;其余关系(包括源代码支持的文本编码)由派生实现指定。
2138-记号值的相等性等价其对应的词素的相等性。
2139-记号值可出现在词法分析(@5.3.1) 阶段,由单独的规约过程(@5.8.3) 通过调用 NPL::TokenizeTerm(@6.4.3) 转换词素或直接初始化分析的中间结果得到。
2140-记号值相关 API 参见 @6.4.2.1 。
2141-
2142-@5.6.2 环境引用(@5.4.3) :
2143-环境引用是引用 Environment 类型的环境(@5.4.3) 并支持其共享所有权(@5.2.4.2) 的中间值。
2144-环境引用类似引用值(@5.5.4) ,但隐含更复杂的所有权关系,因此不具有相同的类型(@5.5.7) 。
2145-shared_ptr<Environment> 可引用环境对象,其非空值作为环境强引用(@5.4.3) 。
2146-NPL::EnvironmentReference 可引用环境对象,封装 weak_ptr<Environment> 和锚对象(anchor object) 指针,其非空值表示可能具有共享所有权的环境弱引用(@5.4.3) 。
2147-环境对象的弱引用的所有权关系使用其中的 weak_ptr<Environment> 确定。
2148-因为 weak_ptr<Environment> 不提供可靠的弱引用计数,引用锚对象附加的引用计数用于确定弱引用的数量,详见上下文 API(@6.9.4) 。
2149-默认构造的 shared_ptr<Environment> 是空的环境引用。
2150-默认构造的 NPL::EnvironmentReference 具有空的 weak_ptr<Environment> 值,是空的环境引用。
2151-非空的 shared_ptr<Environment> 和 NPL::EnvironmentReference 分别可作为对象语言的环境强引用和环境弱引用的宿主值。
2152-
2153-@5.6.3 项引用(term reference) :
2154-宿主语言中的 NPL::TermReference 是项引用。
2155-NPL::TermReference 和所在的项中可能包含的子对象是引用值的宿主值(@5.5) 类型。
2156-其中,子项只被子对象引用(@5.6.3.5) 的情形使用。
2157-子对象引用使用的这种形式的表示通常因为需要更多的操作比其它引用值的类似操作低效,但这种表示可避免依赖宿主语言中的本机对象内部表示(如成员布局)的依赖。
2158-一般地,在 C++ 的意义上不存在能满足语义的更有效的可移植表示,所以这种表示在和宿主语言的互操作(@5.2.1) 上是必要的。
2159-对 NPL::TermReference 相关操作详见 @6.4.4 ;可能使用 NPL::TermReference 访问项的操作详见 @6.4.5 。
2160-
2161-@5.6.3.1 项引用的元数据(@4.2.3.3.1) :
2162-NPL::TermReference 包含一些附加的只通过引用形式访问对象的元数据作为优化设计(@4.2.3.3.2) ,包括标签(@5.4.2.2) 和关联的环境。
2163-部分元数据在初始化时决定。
2164-NPL::TermReference 初始化时,初始化保存标签(@5.4.2.2) 。
2165-NPL::TermReference 通过 NPL::EnvironmentReference(@5.6.2) 可选地指定关联的环境。
2166-当关联的环境不存在时,不提供对引入的对象内存安全(@5.2.4.3) 的检查(@5.2.2)。
2167-引用值关联的环境是可靠的,当且仅当关联的环境是被引用对象的所有者。
2168-不保证可靠的关联环境的引用值是不安全引用值(unsafe reference value) 。除非另行指定,仅有不存在关联环境的引用值是不安全引用值。
2169-另见非内存安全操作(@5.2.4.3.1) 和环境(@5.4.3) 。
2170-
2171-@5.6.3.2 引用折叠(reference collapse) :
2172-和 ISO C++ 类似,引用值在 NPLA 中默认不被继续引用,使用引用初始化引用会引用到被引用对象(@4.2.3) 上,即引用折叠。
2173-内部表示可支持间接的引用。和 ISO C++ 不同,NPLA 不限制派生实现利用未折叠的引用值,但 NPLA 的其它接口不和其与其它值区分。这允许在对象语言中实现一等引用(@4.2.3) 。
2174-未折叠的引用值被折叠时,用于初始化的被引用对象可能仍然是未折叠的引用值。
2175-完全折叠的引用值得被引用对象不是引用值。除非另行指定,以下的引用值指已完全折叠的引用值。
2176-
2177-@5.6.3.3 引用值的消除:
2178-引用值可被消除,即被其关联的被引用对象替代。修改引用值为关联的被引用对象消除引用值;折叠引用值消除被折叠的引用值。
2179-未折叠的引用值消除引用值结果仍是引用值。配合引用折叠,消除已折叠的引用值确保结果总是右值(@5.5.1) 。
2180-
2181-@5.6.3.4 可转移条件:
2182-根据项是否具有特定元数据的引用值可判断使用复制代替对象转移的条件(@5.5.2.3) 。
2183-可转移条件的判断基于表示表达式的值的项。可转移项(movable term) 通过以下方式确定:
2184-非引用项(@5.5.4) 总是可转移的(非引用项表示右值);
2185-否则,若项引用具有的标签(@5.6.3.1) 决定它是被引用对象的唯一引用且非不可修改(@5.4.2.2) ,项是可转移的。
2186-一般地,非引用值的对象是可转移的。
2187-确定引用值关联的被引用对象可转移的条件有两类:
2188-由可转移项确定:引用值由可转移项表示,在特定的上下文使被引用对象能转换为右值(用例如 @7.1.4.2 );
2189-由被转发(@5.5.2.2) 表达式的值确定:除上述条件外,表示绑定临时对象的引用值(具有临时对象标签(@5.4.2.2) )的项蕴含被引用对象(@4.2.3) ,也是可转移的(用例如 @10.5 )。
2190-
2191-@5.6.3.5 子对象引用(subobject reference) :
2192-特定的引用值是子对象引用,通过一个引用值和它的子对象构造,引用被对象所有的一等对象。
2193-子对象引用的表示详见 @5.9.4 。
2194-子对象引用使用的这种形式的表示通常因为需要更多的操作比其它引用值的类似操作低效,但这种表示可避免依赖宿主语言中的本机对象内部表示(如成员布局)的依赖。
2195-一般地,在 C++ 的意义上不存在能满足语义要求且总是更简单高效的可移植表示,所以这种表示在和宿主语言的互操作上是必要的。
2196-
2197-@5.7 资源管理:
2198-NPLA 的实现约定统一的资源管理语义,并以此支持存储和对象模型(@5.2.4) 。
2199-除非另行指定(如明确资源被共享使用),NPLA API 提供类型的对象按值传递(@4.4.4.5) 传递资源所有权(复制或转移资源)。
2200-违反 NPLA 资源管理要求可引起宿主语言的未定义行为并违反 NPLA 内存安全保证(@5.2.4.3.3) 。
2201-基本的资源所有权约束由宿主语言的规则蕴含。
2202-部分数据结构可有更进一步的上下文相关的约定,如 TermNode 对象在作为被归约项(@5.4.2) 和被绑定对象(@5.4.3) 时具有不同的规则(参见有关的所有权规则(@5.7.5) )。
2203-NPLA 还引入称为间接值(indirect value) 的对象访问和管理特定的资源,详见 @5.7.3 。
2204-
2205-@5.7.1 分配器(allocator) :
2206-分配器分配资源,用于提供供实现指定的宿主语言的存储资源的来源。这和对象语言的存储资源(@5.2.4) 管理机制类似,但不保证对应。分配器隐含被映射的类型中,不显式出现在对象语言中。
2207-NPLA 中的分配器具有可和 TermNode::allocator_type 互相隐式转换的类型,接受 YSLib::pmr::memory_resource 类型的存储资源。
2208-这些分配器通常来自 TermNode(@5.4.2) 、ContextNode(@5.4.4) 的存储资源以及 Environment::BindingMap(@6.9.1) 的分配器,用于这些类型和 NPL::EnvironmentList(@6.9.2) 以及其它被运行时使用相同存储资源管理的项中的对象的初始化。
2209-NPLA 和派生实现的本机实现(@5.2.1) 应保证分配器的使用符合以下小节的约定。
2210-
2211-@5.7.1.1 相等性要求:
2212-除非另行指定,对 ValueObject 等支持不同目标类型的对象,可选地使用分配器。一般地,这表示不使用运行时检查分配器类型相等的带有分配器参数的复制或转移构造函数。
2213-预期来自等价存储资源的分配器应相等,以避免分配器不相等的容器交换操作引起行为未定义(如 TermNode 交换,参见 @5.4.2 )。
2214-由 C++ 分配器要求,分配一个宿主语言对象时使用的分配器和其它特定分配器之间的相等性此宿主语言对象的生存期中确定不变。
2215-以下关于 TermNode 的分配器相等性是 API 隐含的:
2216-TermNode::Container 满足 C++ 标准库容器的 allocator-aware 要求,容器和作为容器元素的子节点使用的分配器相等;
2217-TermNode::get_allocator 决定 TermNode 的分配器等价 TermNode 中的节点容器的分配器。
2218-以下情形的分配器在实现中应保证相等:
2219-由同一个 ContextNode 对象决定的分配器在 ContextNode 生存期内应相等(且通常使用这些分配器的对象的生存期是此生存期的子集);
2220-若分配器来自超过一个宿主语言对象,这些对象提供的分配器应相等。
2221-一般地,以下关于 TermNode 的分配器之间不保证相等性:
2222-TermNode 对象和它的值数据成员(@5.4.2) 的分配器;
2223-TermNode 的值数据成员和其中持有的对象可能具有的分配器。
2224-
2225-@5.7.1.2 分配器来源:
2226-宿主语言对象的分配器可通过从存储资源初始化或由其它对象使用的分配器复制初始化取得。
2227-部分类型(如 ContextNode )的对象初始化可接受存储资源参数。提供实际参数作为存储资源的用户代码应适当维护被引用的存储,确保这些对象的生存期是存储资源的生存期的子集,以避免引用已释放的存储而引起宿主语言的未定义行为(@5.2.2) 。
2228-NPLA 使用的多态分配器可保证分配器和初始化分配器的存储资源的对应。但若已存在(满足操作的接口语义需要的)可用的分配器,取分配器的副本而不是使用存储资源初始化新的分配器。
2229-由 C++ 分配器要求,使用相等的分配器效果一致,但除非另行指定或以下所有途径都不支持取得能兼容被分配的对象的分配器,若需要分配器(以满足 @5.7.1.1 或提供优化实现等),仍按以下规则(自上而下优先)约定确定用于复制初始化的分配器的来源:
2230-引起表示对象语言的值的 TermNode 对象创建时,若分配器(根据接口语义)必须依赖操作中的一个或若干个已知分配器相等的、表示对象语言值(如对象语言中函数的参数)的 TermNode 对象,后者中的任一个 TermNode 对象提供分配器;
2231-引起 TermNode 对象创建时,若对象被一个或多个 TermNode 对象共同所有(如节点的值被值数据成员所有),TermNode 的分配器以表示关联所有者的 TermNode 类型的值提供;
2232-引起 Environment 对象创建时,需要的分配器由关联的 Environment::BindingMap(@6.9.1) 的分配器提供;
2233-引起满足 allocator-aware 的容器要求的对象(支持迭代器和所有权)的元素的创建时,由被添加目标对象的分配器提供;
2234-其它情形的分配器对象由 ContextNode 的存储资源提供。
2235-
2236-@5.7.1.3 取分配器的方式:
2237-提供分配器的对象可能若干种隐式(如转移构造)或显式(如 TermNode::get_allocator 调用)方式决定被使用的分配器。
2238-满足上述预期的分配器相等性能决定同一来源的不同方式取得的分配器相等时,取分配器使用的方式未指定,如:
2239-一个表示对象语言的值的 TermNode 对象提供分配器时,可直接取其分配器或子节点(若存在)的分配器。
2240-否则,一般不能通用,如:
2241-引起 TermNode 对象创建时,被初始化的值数据成员不能通过某个未知分配器来源也不具有相等性保证的被 ValueObject 值持有的对象取得。
2242-
2243-@5.7.2 值所有权:
2244-TermNode 对构成值的表示的子节点和值数据成员(@5.4.2) 具有所有权。和 @5.4.2 不同,非直接的所有权可能不是独占的。
2245-TermNode 表示对象语言中一等对象的所有权规则符合求值和对象所有权(@5.2.4.2) 规则,且作为被归约项时,符合被规约项的所有权(@5.7.5) 或派生实现指定的其它规则的所有权约束。
2246-值对宿主对象(@5.5) 的所有权的机制由值数据成员(具有 ValueObject 类型)相关的 API 提供。
2247-默认使用的 ValueObject 使用值的持有者 YSLib::ValueHolder ,直接作为对象的表示,同时具有对象的所有权。
2248-引用一个项可能有多种方式,不具有所有权。关于引用相关的值的所有权,参见以下各个小节。
2249-
2250-@5.7.3 间接值分类和所有权:
2251-使用 ValueObject 的特定持有者或约定特定的中间值(@5.6) 实现间接值允许和所有权分离的其它形式的表示,提供和非间接值不同的对象所有权和附加操作。
2252-间接值的实例总是具有关联对象。间接值可间接访问关联对象。
2253-对作为对象语言中的一等对象的间接值,允许复制或转移关联的对象以恢复对应的非间接值作为一等对象直接访问。
2254-间接值可用于代替非间接值,避免求值时改变环境(@5.4.3) 所有的非临时对象的所有权(@5.2.4.2) 。
2255-间接值可实现和 C++ 引用类型表达式类似的行为。
2256-NPLA 提供引入间接值的 API ,参见 @6.6.2 。
2257-实现通过值数据成员以外的机制也可隐含固定(不通过用户代码修改定制)的所有权关系,如上下文(@5.4.4) 和环境(@5.4.3) 。
2258-所有权机制和中间值(@5.6) 正交:具有间接值的对象可能作为中间值,也可能不作为中间值。
2259-本节以下约定要求被 NPLA 实现支持的间接值。派生实现可以定义 NPLA 扩展间接值。
2260-
2261-@5.7.3.1 环境引用(@5.6.2) :
2262-环境引用间接访问环境对象。
2263-环境强引用(@5.6.2) 可能共享环境对象的所有权,对环境对象的名称绑定映射(@5.4.3) 持有的项具有间接的所有权。
2264-
2265-@5.7.3.2 引用值:
2266-使用项引用(@5.6.3) 作为间接值引用一个项,访问被引用对象(@4.2.3) 。
2267-这也包括使用子对象引用(@5.6.3.5) 的情形。
2268-
2269-@5.7.3.3 引用持有者:
2270-使用持有者 YSLib::RefHolder 的实例可实现间接值,访问其它 ValueObject 对象。
2271-被间接引用的值的类型和 TermNode 的值数据成员(@5.4.2) 取得的类型一致,可直接替换非间接引用的对象。
2272-引用持有者一般并不能替代引用值(@5.7.3.2) ,因为持有者仅影响存储来源和所有权,不区分类型,也无法要求用户代码使用明确的引用操作。
2273-一般引用的持有者的项仅作为上述明确的引用操作的结果,作为中间结果继续以和非间接值一致的方式被规约(使用 NPL::LiftTermRef(@6.6.2) )。
2274-
2275-@5.7.4 间接值的有效性:
2276-间接值有效当且仅当存在关联的对象且访问对象不引起未定义行为。
2277-其它间接值是无效(invalid) 的。
2278-类似实现中项的无效化(@5.4.2) ,有效的引用值可能被无效化(invalidate) 不再有效。
2279-因关联的对象存储期(@5.2.4) 结束而被无效化的是悬空(dangling) 间接值。
2280-引用值(@5.7.3.2) 作为一种间接值,其规则是上述规则的实例。
2281-以下约定要求被 NPLA 实现支持的有效的引用值总是无条件地允许访问对象。
2282-
2283-@5.7.5 被规约项(@5.4.2) 所有权:
2284-具有值类别的被规约项若指称对象,对应的对象是这个项的项对象(term object) 。项对象是求值结果(@4.1) 或求值的中间结果(@5.5) 对应的对象。
2285-项对象可能不直接由项自身表示,即可以通过项引用其它途径引入的对象。
2286-基于 @5.7.3 ,为保证内存安全,避免临时对象被引用,仅在泛左值(@5.5.1) 中允许引入被可能引用的间接值。
2287-推论:泛左值的项对象不是临时对象,被环境所有。
2288-通常纯右值作为其它项的子项而被独占所有权,求值时可能通过临时对象实质化转换(@5.5.5) 标识创建的临时对象(@5.5.6) 。
2289-为实现临时对象求值和对象所有权(@5.2.4.2) ,临时对象以项自身作为表示(@5.5) ,被纯右值所有,也间接被其它项所有。
2290-特定情况下纯右值可能被环境所有,但应只通过复制等方式访问其值而不依赖所有权关系(如仅复制记号值(@5.7.6.2) )。
2291-
2292-@5.7.6 间接值使用规则:
2293-在特定的适当情形(详见以下小节)下复制或转移间接值引用的对象以保证满足生存期要求(@5.7.5) ,维护内存安全。
2294-不能满足上述适当情形条件的若不明确引起错误,则行为未定义。
2295-间接值生存期规则:被规约对象中间接值的生存期总是不超过被引用的环境中的对象,以保证内存安全。
2296-不满足间接值生存期规则的情形,除非提供派生实现定义的其它保证,不保证内存安全。
2297-以含间接值的项替代不含间接值的项称为引入间接值。
2298-包含间接值的项可被不含引用值的项替代,称为消除。
2299-如需直接替换项表示的值,需消除间接值。否则,没有必要提前对项进行操作以提前移除间接值。
2300-另见被求值的被规约项中的对象的所有权(@5.7.5) 。
2301-派生实现可基于本节约定其它规则。
2302-
2303-@5.7.6.1 引用值(@5.5.4) 作为间接值:
2304-由于左值(@5.5.1) 的项对象被环境所有(@5.7.5) ,在项上的求值需要其它项对象作为中间值。
2305-这种中间值通过间接引用左值以确保左值标识的对象可作为一等对象(@4.1) 使用,也是一种间接值,即引用值(@5.5.4) ,是引用(@4.2.3) 的实例。
2306-NPL::TermReference(@5.6.3) 类型是可能需要区分引用与被引用对象的值的内部表示。
2307-这类似宿主语言的对象类型的左值引用和右值引用的差异(@5.5.4) 。
2308-消亡值(@5.5.1) 以作为其表示的引用项(@5.5.4) 内部的 TermTags::Unique 标签(@5.4.2.2) 标记,即唯一引用。
2309-基于引用的左值到右值转换(@5.5.5) 可通过 NPL::ReferenceTerm(@6.4.4) 实现。
2310-和宿主语言的涉及成员访问的表达式类似,直接使用标识符进行名称查找(@4.3.3) 得到的表达式是左值或消亡值。在此环境总是被视为左值,所以结果由环境中的对象类型确定:当且仅当对象是左值时,结果是左值引用;否则是右值引用。
2311-和宿主语言类似,右值引用类型的表达式可作为左值使用。
2312-一般地,并非所有对象需要引用,详见 @4.2.3 。
2313-引用值在必要时被消除(@5.6.3.3) ,以被进一步求值。
2314-
2315-@5.7.6.2 非引用值间接值:
2316-规约实现在特定情形的求值中使用 NPL::LiftTermRef 等(@6.6.2) 引入基于引用持有者(@5.7.3.3) 的间接值避免求值时改变泛左值(@5.5.1) 标识的非临时对象的所有权。
2317-否则,引入的间接值引用环境所有的对象。
2318-应注意存储环境以外的间接值时,不超出持有对象的存储期,避免未定义行为(@5.2.4) 。当前实现不对此附加检查。
2319-注意不求值而仅替换项时,使用 NPL::LiftOther 或 NPL::LiftTerm(@6.6.1) ,这不引入间接值。
2320-除构造间接值的例程(@6.6) ,当前不直接引入引用持有者。
2321-如需要引入环境引用(@5.7.3.1) 外的间接值,一般使用引用值(@5.5.4) 而不是非引用值间接值,以统一处理无法直接持有的列表项和非列表项的引用。
2322-
2323-@5.7.6.3 间接值的消除:
2324-访问间接值涉及维护内存安全保证(@5.2.4.3) 时,可能需要提升项(@6.6) 以移除允许非内存安全(@5.2.4.3.1) 访问的间接值。
2325-引用值作为间接值可被消除(@5.6.3.3) 。
2326-用于按值传递参数时,一般使用 NPL::LiftTerm(@6.6.2) 和确保创建值副本的 NPL::SetContentWith(@6.2.1) 实现;前者取非引用类型的右值(@5.5.1) ,后者提升间接值确保结果不是中间值。
2327-用于按值传递返回值时(@4.5.3.1) ,除显式分别对是否为引用值的情形进行处理,可使用 NPL::LiftToReturn(@6.6.3)(其中也使用以上方式实现),实现临时对象实质化转换(@5.5.5) ,详见 @5.7.6.4 。
2328-
2329-@5.7.6.4 返回值转换(@5.5.5.2) :
2330-作为提升项的使用用例(@5.7.6.3) ,在返回值转换上下文(@5.5.6.2) 中确定函数返回值的实质化转换上下文(@5.5.6.1) 的部分操作消除引用值(@5.6.3.3) ,即返回值转换。
2331-这可约束作为间接值的引用值不逃逸(escape)(即使用被引用对象的值时不超出指向对象的生存期),而保证只考虑项的值数据成员(@5.4.2) 可能是引用值时的内存安全。
2332-返回值转换不保证未折叠的引用值在消除引用值后的结果不逃逸。为确保内存安全,程序仍需要保证被引用的对象的间接引用的对象生存期结束后,不能访问间接引用的对象。
2333-除非证明不需要临时对象(当前未实现),返回值转换中初始化临时对象作为返回值的项对象(@5.7.5) 。
2334-是否需要返回值转换由实质化转换上下文中的被调用的函数而非上下文是否需要使用右值决定,无关被转换的表达式是否是左值,因此返回值转换不是左值到右值转换(@5.5.5) 。
2335-不论是否存在返回值转换,返回值的项对象来自返回的右值关联的临时对象实质化转换(@5.5.6) 。这可能在 NPL::LiftToReturn 或之前的求值规约的调用中蕴含。
2336-
2337-@5.7.7 被归约项稳定性:
2338-基于 TermNode 的性质,未被规约修改的项的子项的迭代器、指针和引用保持稳定(@5.4.2) 。
2339-被规约的项在取得求值结果(@5.8.1) 前不被删除,以避免项的迭代器、指针和引用失效。
2340-结合 @4.2.4.1 得到推论:被规约的项在取得求值结果起可被非内部对象(@4.2.4.1) 引用。
2341-由 @5.2.4.1 ,项中的值数据成员(@5.4.2) 表示的宿主对象(@5.5) 保持固定。
2342-子项的稳定性保证只要被规约的整个项不被作为对象被引用,直接转移被规约的项仍保持子项中的宿主对象保持固定。
2343-子项不直接保证其它稳定性。
2344-在取得求值结果前,若项不改变可观察行为(@4.1.3)(例如,项不被作为对象语言中的表达式或求值结果的表示(@5.5) 或表示的值在对象语言中不可见),可能被转移。
2345-这允许规约的本机实现内部在取得求值结果前保存表示不属于求值结果的中间结果的子项。
2346-
2347-@5.7.7.1 规约操作资源:
2348-规约时保持决定当前规约操作的状态的资源的独占所有权([Documentation::CommonRules @@2.3.4.6]) 的项应具有足够长的生存期,以避免调用时引起未定义行为。
2349-实现 WHNF(@4.4.3.1) 时,第一个子项对此资源具有独占所有权。
2350-在无法预知子项是否需要被进一步使用而不会在被调用前另行存储包含这些资源的项时,实现使用的操作应避免删除这里的子项(对实现 WHNF 而言即第一个子项),以免无法使用其中的状态。
2351-
2352-@5.7.7.2 临时对象资源:
2353-表示临时对象的项的子项的迭代器、指针和引用应保持稳定,以支持子项被绑定到对象语言中的引用值(@5.5.4) 。
2354-表示临时对象的项自身不需被绑定到引用值,不保证稳定,可被直接存储为 TermNode(而不需要保存为指针等间接值)。
2355-对临时对象分配资源可能转移表示临时对象的项,但不影响其子项的稳定性。
2356-
2357-@5.7.8 项修改限制:
2358-若规约需调整项的结构以便使用特定 API ,可使用 TermNode& 作为参数的规约函数(@5.8.5)(另见规约函数形式约定(@6.5.3) );
2359-如不可行,一般需转移到新创建的项上进行调用,以免改变现有的项导致无法使用项独占所有权的资源(如 @5.7.7.1 的状态)。
2360-单独转移可能具有宿主类型对象的子项会违反固定对象的约定(@5.7.7) ,因此除非确保子项不被引用,不单独转移或销毁可能带有宿主对象(@5.5) 的子项(包括引起子项对象被销毁的删除操作)。
2361-若规约内部确定符合内存安全(@5.2.4.3) 或派生实现指定的假定满足的条件(至少应满足 @5.7 中的资源管理要求),子项可被转移或销毁。这些操作包括:
2362-被规约项取得自求值结果起,在非内部对象引用此项前被整体转移的操作;
2363-NPLA 指定删除或其它可能转移或销毁子项的操作;
2364-派生实现指定的其它要求假定或可证明安全的操作。
2365-其它情形应避免这类操作。特别地,一般应避免在被处理的 TermNode 上直接调用 RemoveHead(@5.4.2) 。
2366-
2367-@5.8 规约约定:
2368-NPLA 规约 API(@6.5) 约定默认使用 TermNode 和 ContextNode 类型的引用作为参数类型,分别表示被规约项(@5.4.2) 和使用的上下文(@4.6.1) 。
2369-更具体的其它约定参见规约函数形式约定(@6.5.3) 。
2370-一次规约局限在当前被规约项(一个 TermNode 对象)进行修改(@5.7.8) ,这个被规约项称为此次规约的当前项(current term) 。
2371-表达式求值起始时,当前项是当前被求值项(@5.5) 。
2372-规约 API 的 TermNode 参数指定当前项。必要时,当前项可被其它方式指定。
2373-NPLA 约定列表表达式(@3.4.2.3) 子项作为被规约项需进行递归的树规约(@5.4.2) 。
2374-
2375-@5.8.1 规约结果:
2376-规约过程返回描述一次规约调用操作结束后的状态的结果,包括以下几种情形:
2377-部分规约(partial reduction) :需要继续进行规约。表示不是完整的求值规约,也可以仅是管理规约(@4.4) 。一般仅用于异步规约(@5.10) 。
2378-中立规约(neutral reduction) :规约成功终止(@4.4.3) ,且未指定是否需要保留子项。
2379-纯值规约:规约成功终止,且不需要保留子项。
2380-非纯值规约:规约成功终止,且需要保留子项。
2381-重规约:当前项需重新进行规约迭代(@5.8.3) ,而首先需要跳过当前项上当前一轮迭代的剩余的规约动作(@5.4.4) 。
2382-取得重规约外的规约结果时,被规约项表示求值结果(@4.1) 。另见关于使用规约实现表达式求值(@5.5) 。
2383-纯值规约的求值结果总是由值数据成员(@5.4.2) 决定。
2384-非纯值规约为纯列表规约,当且仅当求值结果仅由子项决定而不需要访问值数据成员。
2385-除不指定要求保留子项,中立规约和非纯值规约的作用相同,仅影响 @5.8.3 中的处理。
2386-若被规约的项已符合保留子项的要求,中立规约可以通过判断当前项是否已保留子项而分别指定为非纯值规约或纯值规约代替,但这带来不必要的项访问,不利于性能;因此本设计直接在规约结果中提供。
2387-除非实现逻辑需要或另行指定,规约结果默认为纯值规约。因为通常求值结果的子项来自被规约项的子项,和表示规约后的项经常并没有关联而应被清除,默认规约结果设计为纯值规约便于处理这种常见情形。
2388-规约结果作为规约实现的状态,设计原理如下:
2389-基于实现的可扩展性、可复用性、复杂性和性能,判断规约终止的谓词(范式判断(@5.8.4.1) 谓词)不一定适合在单独的规约过程中指定。
2390-引入可指定规约不终止的显式的重规约状态代替范式判断谓词应对这些问题,允许项在最终取得范式前附加可变状态,并优化范式判断(@5.8.4.1) 。
2391-区分规约终止的不同情形能在一定程度上复用已有的列表节点(@5.4.2.1) ,避免规约时节点或其它数据结构的一些冗余创建。
2392-
2393-@5.8.2 子项规约:
2394-由树规约的性质(@5.4.2) ,可假定规约不改变参数外部的项的有效性,不需要外部项的存在性及结构进行额外的检查以确保继续满足相关前置条件(如子项被规约后所在的项总是继续为枝节点(@5.4.2.1) )。
2395-
2396-@5.8.2.1 子项的清理(cleanup) :
2397-纯值规约(@5.8.1) 要求清理操作,即移除不被需要(@5.8.1) 的子项。
2398-清理时子项所有的对象被销毁,可具有副作用(@2.3.4) 。
2399-子项的清理属于删除子项的操作(@5.7.8) 。
2400-
2401-@5.8.3 规约迭代:
2402-对项的一次规约可分解为若干个对这个项的分步规约的有序的迭代过程,每个过程称为一个遍(pass) ;另见 @6.8 。
2403-一次规约中有且仅有最后一遍规约迭代终止(@5.8.1) ;重规约(@5.8.1) 或通过抛出异常退出的迭代是一遍非终止的迭代。
2404-一次不异常退出的规约按规约结果总是对应部分规约以外的规约结果(@5.8.1) 之一。
2405-
2406-@5.8.3.1 规约迭代结果:
2407-一次规约取得一个规约结果。
2408-规约结果被用于在每次规约后维护被规约项的表示(@5.9) 。
2409-若无法利用规约结果,在不需要保留项时,可能需要其它的显式的清理(@5.8.2.1) 操作。
2410-
2411-@5.8.3.2 规约结果合并:
2412-为避免保存规约结果的不必要开销,每一遍的规约结果不被提供给其它遍共享地访问。
2413-不同遍的规约结果可能不同。项的一次规约的结果由被调用的一个或多个遍的结果顺序地迭代决定:
2414-未经遍调用迭代的规约结果为中立规约(参见 @5.8.3.3 );
2415-之后每一遍返回后,逐一合并(combined) 之前遍决定的规约结果和新的遍的结果,作为新一轮迭代后的规约结果。
2416-(注意此处的合并操作不同于函数合并(@4.5.3) 。)
2417-项的一次规约的结果为规约迭代的最后一遍合并后得到的结果。
2418-迭代处理不同遍的规约结果仍有开销,但即便作为纯软件实现,通常仍远小于直接保存每个遍规约结果的开销。这也允许减少一次规约中不同遍之间的重复的维护操作(@5.9.2) 。
2419-
2420-@5.8.3.3 规约结果覆盖:
2421-一次规约中,某一轮被迭代处理的遍的规约结果可能和处理结果相同,即覆盖当前处理的规约结果。
2422-规约成功终止的规约结果不被不表示规约成功终止的规约结果覆盖,以满足范式的语义(@5.8.4) 。
2423-规约成功终止的不同规约结果可能支持互相覆盖。
2424-由于规约结果作用不同,不同规约覆盖不一定被同等地支持。(这同时避免需要过多的不同的规约结果表示不同的覆盖操作。)
2425-覆盖行为不应影响单一遍的规约结果的语义正确性。对子项的影响明确不同的规约不能互相替换。
2426-例如,中立规约实现处理为纯值规约(@5.8.1) 或非纯值规约是未指定的。但中立规约明确不清理子项,一般不能用纯值规约代替,不论纯值规约是否按以下规则覆盖其它规约结果。而为避免实现细节,中立规约也不能用非纯值规约替换。
2427-当前规约结果覆盖使用以下对称覆盖设计:
2428-未被规约的初始状态假定为中立规约,不影响子项并允许被之后的规约结果覆盖。
2429-规约成功终止时,明确指定纯值规约或非纯值规约的规约结果在合并过程中可覆盖其它规约结果。
2430-一般地,一个项上的规约成功终止时已决定是否需要保留子项,通常不需要覆盖非默认规约结果。
2431-所以相对地,被确定为非纯值规约的情形不严格需要支持被下一轮规约结果覆盖为纯值规约。若仍要求不保留项,则需其它的显式的清理操作(@5.8.2.1) 。
2432-和纯值规约类似而和非纯值规约不同,中立规约不覆盖其它规约成功终止的规约结果,不影响子项是否需要被清理。
2433-
2434-@5.8.3.4 迭代规约结果的合并算法:
2435-根据以上讨论中的要求,可确定规约结果的合并算法。
2436-成功终止(@5.8.1) 的遍规约迭代结果的合并符合以下规则:
2437-若被迭代的新一轮的遍的指定可覆盖其它结果的规约结果(@5.8.3.3) ,则指定合并的规约结果为此规约结果;
2438-否则,新一轮合并的规约结果为之前的遍合并的规约结果。
2439-一般的序列规约结果合并符合以下规则:
2440-若之前的遍合并的规约结果指定规约成功终止,则使用以上关于成功终止的规约结果的合并规则决定新一轮合并后的规约结果;
2441-否则,新一轮合并的规约结果同之前的遍合并的结果。
2442-注意和非纯值规约(@5.8.1) 不同,中立规约(@5.8.1) 不会影响保留的规约结果。
2443-
2444-@5.8.4 规约范式:
2445-被规约项可能通过规约得到范式(@4.4.3) 。
2446-
2447-@5.8.4.1 范式判断:
2448-基于范式的定义,终止的(@5.8.1) 规约迭代应对 TermNode 参数进行规范化(@4.4.3) 以得到范式。
2449-NPLA 提供以下等价的方式判断规约迭代后是否在 TermNode 得到范式:
2450-除非另行指定,可直接使用 NPL::CheckReducible(@6.5.2) 以规约结果(@5.8.1) 决定是否为可继续规约的非范式;
2451-通过检查节点结构的谓词确定是否为枝节点(@5.4.2.1) 等作为范式判断谓词进行判断;
2452-其它派生实现指定的范式的判断方式。
2453-以上等价性由特定的 NPLA 规则(如 @5.9.1 )和规约实现机制保证。
2454-因为只检查规约结果的值而不访问项,使用 NPL::CheckReducible 代替范式判断谓词(@5.8.1) 一般能优化性能。
2455-基于上述等价性保证,上述等价方式中的范式判断谓词可蕴含 NPL::CheckReducible 的结果,必要时也可用范式判断谓词代替规约结果,详见 @5.9.2 。
2456-
2457-@5.8.4.2 规范化规约(@4.4.3) 约定:
2458-除非另行指定(@5.9.2) ,一次终止的规约迭代中若存在规范化规约,其发生的次数和时机未指定;一般在最后一遍(@5.8.3) 或之前存在一次即可。
2459-注意规范化规约可能有副作用(@2.3.4) ,实现应保证规约行为可被预期。
2460-
2461-@5.8.5 规约函数:
2462-接受 TermNode 和其它参数进行规约的 API 主要以宿主语言的函数(子例程)的形式提供,称为规约函数。
2463-规约函数以被规约的项作为第一形式参数。
2464-规约函数可能返回规约结果(@5.8.1) 。
2465-规约函数可以调用其它规约函数实现。
2466-注意因为可直接忽略被调用的规约函数的返回值并指定其它值,规约函数的实现的分类不一定具有组合性。
2467-
2468-@5.9 规约表示(@5.5) 的正规性(regularity) :
2469-规约结果(@5.8.1) 和求值结果(@4.1) 或求值的中间结果(@5.5) 的表示有如下关系:
2470-纯值规约(@5.8.1) 后,仅由被规约的项的值数据成员(@5.4.2) 决定规约得到的值;
2471-纯列表规约(@5.8.1) 后,仅由被规约的项的子项决定规约得到的值,即得到真列表(@5.4.2.1) 。
2472-满足上述约定的求值结果的表示(@5.5) 是正规(regular) 表示;否则,求值结果的表示是非正规(irregular) 表示。
2473-表达式具有正规表示。
2474-空求值(@4.4.2) 不改变表示的正规性。
2475-平凡(trivial) 非正规表示保证可被(此次规约中剩余的操作)安全忽略而不改变规约的语义。
2476-对被求值项(@5.5) 的任一遍(@5.8.3) 规约都可引入非正规表示,但应满足取得范式的要求:
2477-被求值项取得平凡非正规以外的表示是取得范式(@5.8.4) 的必要非充分条件。
2478-为取得范式,可能需要继续进行规范化规约(@4.4.3) 。
2479-求值规约(@4.4) 可包含规范化规约。
2480-作为正规表示的表达式项的值数据成员(@5.4.2) 称为正规值(regular value) 。
2481-不论是否正规表示,项应可复制和转移,且不影响其中值的有效性。
2482-推论:非正规表示中不能依赖特定对象的同一性(如值数据成员直接引用子项)。
2483-
2484-@5.9.1 规约表示基本性质:
2485-除非另行指定(参见非平凡非正规表示(@5.9.4) ),NPLA 实现应保证规约表示满足以下基本性质:
2486-任一遍规约中,非正规表示应是平凡的。
2487-规约表示基本性质允许在已知 TermNode 得到范式时通过直接判断项的节点结构(@5.4.2.1) 是否存在子项代替推断此次规约中之前的规约结果。
2488-在对象语言输入可保证不存在非正规表示的前提下,这样的设计满足原则 @1.4.1.1 和 @1.4.2.2 。
2489-若不能保证不引入非正规表示,实现可通过正规化操作(@5.9.2) 约束规约后的项,以确保接口行为满足上述规约语义的保证。
2490-
2491-@5.9.2 正规化(regularization) 操作:
2492-一次规约后,被规约的项中的值数据成员或子项仍然可保留其它状态而非范式;对表示求值的情况,也不是正规表示(@5.9) 。
2493-因为规范化规约可能存在副作用(@5.8.4.2),NPLA 约定求值得到正规表示的规范化规约在抽象机(@2.6) 的意义上总是被进行,称为正规化操作。
2494-纯值规约(@5.8.1) 的正规化操作对子项进行清理(@5.8.2.1) 。由纯值规约的语义,被清理的子项不应影响项作为求值结果的表示。若应清理的子项存在,则清理前为平凡(trivial) 的非正规表示。
2495-若需避免子项的生存期扩展到所在的项,需确保对平凡非正规表示总是存在可预期的清理操作。
2496-纯值规约中存在的清理和修改值数据成员的作用仍非决定性有序(@5.4.2) ,因此一个项中的清理可能先序(@4.4.1) 最终确定作为值的表示的对值数据成员的修改操作。
2497-因为子项的删除时机未指定(@5.4.2) ,不假定 NPLA 实现的规约清理求值后的节点,即清理由派生的具体规约实现指定。
2498-一般在一个表达式取得求值结果(@5.8.1) 前,清理纯值规约要求排除的子项。
2499-纯列表规约的规范化标记值数据成员为特定的值,可以是默认构造的空值或派生实现定义的表达式项(@5.5) 内的值数据成员外的记号值(@5.6.1) 。
2500-除非平凡非正规表示(@5.9.4)(通过先行判断值数据成员而排除)外,当前未从非纯值规约结果中区分纯列表规约,因此不对纯列表规约进行操作。
2501-注意正规化操作和之前的清理操作可能会影响项的生存期;另见规约操作资源(@5.7.7.1) 。
2502-对同一个规约结果的正规化操作是幂等(@4.1) 的,超过一次的连续正规化操作无其它作用。
2503-
2504-@5.9.3 正规表示分类:
2505-基于规约表示基本性质(@5.9.1) ,除非另行指定(参见非平凡非正规表示(@5.9.4) ),不需要单独判断正规表示。
2506-基于正规化操作规则(@5.9.2) ,通过以下逻辑对作为表示(@5.5) 的 TermNode 分类:
2507-具有非空子项的 TermNode 表示非空列表(@5.4.2.1) ;
2508-空节点表示空列表(@5.4.2.1) ;
2509-其它项当值数据成员不是特定的值时,表示非列表表达式的值。
2510-值数据成员为特定的值(如没有约定在对象语言中可表示的中间值(@5.6) )的没有子项的 TermNode 不是任何表达式的表示。
2511-注意值数据成员为 TermNode 类型的节点不表示列表节点。
2512-
2513-@5.9.4 非平凡(non-trivial) 非正规表示:
2514-基于非纯列表规约的非纯值规约,允许存在子项和值数据成员都有意义的非平凡非正规表示。
2515-这样的表示要求先通过值数据成员进行判断,以排除为列表项。
2516-NPLA 使用的子对象引用的非正规表示应符合以下约定:
2517-只存在以下和派生实现另行指定的特定有限种类的非平凡非正规表示受支持;否则,行为未定义。
2518-当前实现只支持以下有限情形的非平凡非正规表示。
2519-
2520-@5.9.4.1 子对象引用(@5.6.3.5) :
2521-值数据成员持有 NPL::TermReference 类型的值,保留某个子项的引用。
2522-子对象引用的项的子项数应为 1 ,该子项持有 shared_ptr 的实例的非空值且其指向的对象和值数据成员持有的 NPL::TermReference 值的 get() 结果应引用同一个项对象。
2523-
2524-@5.9.4.2 带有记号值的列表:
2525-表达式在求值过程中的中间表示可在列表的表示的基础上,用值数据成员附加记号值的表示作为辅助信息。
2526-
2527-@5.10 异步规约(asynchronized reduction) :
2528-直接式(direct style) 的语言实现利用宿主语言的函数调用实现图规约(@5.4.2) ,满足规约函数(@5.8.5) 的调用包含子项的规约,即同步规约(synchronized reduction) 。
2529-非同步规约称为异步规约。异步规约允许在宿主语言中分离部分规约(@5.8.1) 的不同部分的实现,使之公开为一等对象表示控制状态(@4.1) 及被调度。
2530-NPLA 支持同步规约和异步规约中立(neutral) 的接口设计(@5.10.5) 。具体的异步规约在派生实现使用。
2531-NPLA 实现支持异步规约,以避免 C++ 本机(@2.4.1) 资源限制下不限定数量(@5.2.6.2) 调用深度时可能引起的宿主语言的未定义行为(@5.2.2) 。
2532-若资源分配失败,应满足常规宿主资源分配要求(@5.2.2.1) 。
2533-
2534-@5.10.1 异步规约动作(@5.4.4) :
2535-NPLA 中,这通过以规约动作(@5.4.4) 作为 CPS(@5.4.4) 的被跳板(trampoline) 执行的中间值(@4.4.4.2) 例程的机制实现。此时,当前动作(@5.4.4) 是尾动作(@5.4.4) 。
2536-跳板例程设置的规约操作将被异步调用(@5.4.4) ,即当前动作(@5.4.4) 设置为异步动作,在跳板中被循环调用。上下文的重写循环(@5.4.4) 提供一个跳板调用当前动作(@5.4.4) 。
2537-这种方式支持嵌套调用安全(@5.2.7) 。其它方式实现可能具有更好的性能,但存在限制而不被使用,如:
2849+当前动作可作为尾上下文(@4.4.7) 的实现,称为尾动作(tail action) 。特定的尾动作可实现符合 PTC 的尾调用(@5.10.2) 。
2850+配合重写循环提供基于尾动作的循环 API 可实现 CPS(contiuation passing style) 风格的异步调用。相关 API 参见 @6.8.4 。
2851+
2852+@6.8.3.1 上下文类:
2853+类 ContextNode 提供若干 API 。
2854+数据成员 Resolve :名称解析算法。
2855+数据成员 LastStatus 保存最后一次规约状态,配合当前动作序列(@6.8.3) 决定之后的续延中的动作调用。
2856+成员函数 GetBindingsRef 取环境记录(@6.8.3) 中的名称绑定映射(@6.8.1) 引用。
2857+成员函数 GetCurrent :访问上下文内部的保存当前动作序列,是类型为 NPL::ReducerSequence 的一遍规约序列的 const 引用。
2858+当前动作序列可被作为尾动作(@6.8.3) 。
2859+成员函数 GetMemoryResourceRef 取存储资源(@6.4.1) 引用。
2860+成员函数 RewriteLoop 以 IsAlive 的结果为条件的循环调用 ApplyTail ,作为跳板(@6.9.1) 实现一般规约重写。
2861+成员函数 SaveExceptionHandler
2862+成员函数模板 SetupCurrent 和成员函数模板 SetupFront 设置当前动作序列。前者具有前置条件 !IsAlive() 以避免重复设置覆盖当前动作的原始状态。
2863+成员函数 SwitchEnvironment 和 SwitchEnvironmentUnchecked 切换环境,即设置参数指定的环境并返回之前的环境。两者接受 shared_ptr<Environment> 的参数,区别为对空参数抛出异常或引起未定义行为。环境切换便于实现续延或过程调用(@4.5.3.1) 。
2864+切换环境时,旧的环境若没有被引用,则被释放。由于环境具有对象的所有权(@5.6.2) ,内部绑定的对象作为自动变量也被一并释放(@5.6.5) 。
2865+成员函数 ShareRecord 和 WeakenRecord 取环境的引用。
2866+部分 API 使用的参数类似规约函数(@6.7.6) 。
2867+这些函数的参数可能在内部实现被预先绑定,调用时对应的实际参数被忽略:如 TermNode& 类型的形式参数被绑定,内部使用绑定时确定的项而不是实际参数指定的任意的项。
2868+因为绑定参数引入附加的函数调用,一般仅在有必要时使用。
2869+
2870+@6.8.4 其它环境和上下文相关的处理 API :
2871+
2872+@6.8.4.1 环境、绑定访问和解析操作:
2873+基于环境的名称解析(@4.3.3) 操作可选地处理保留名称(@6.11.1.1) 并查找名称(@4.3.3) 。
2874+解析环境只支持要求作为一等对象(@4.1) 的环境引用(@6.8.1) 。
2875+NPLA 提供绑定访问相关的操作,以 Doxygen 命令标识为 \ingroup BindingAccess 。
2876+
2877+@6.8.4.2 上下文切换:
2878+NPL::EnvironmentSwitcher 类型用于切换上下文(@6.8.3.1) 中的当前环境(@6.8.3) 。
2879+
2880+@6.9 异步规约(asynchronized reduction) :
2881+直接式(direct style) 的语言实现利用宿主语言的函数调用实现图规约(@6.2) ,满足规约函数(@6.7.5) 的调用包含子项的规约,即同步规约(synchronized reduction) 。
2882+非同步规约称为异步规约。异步规约允许在宿主语言中分离部分规约(@6.7.1) 的不同部分的实现,使之公开为一等对象表示控制状态(@4.1) 及被调度。
2883+NPLA 支持同步规约和异步规约中立(neutral) 的接口设计(@6.9.5) 。具体的异步规约在派生实现使用。
2884+NPLA 实现支持异步规约,以避免 C++ 本机(@2.4.1) 资源限制下不限定数量(@5.10.2) 调用深度时可能引起的宿主语言的未定义行为(@5.4) 。
2885+若资源分配失败,应满足常规宿主资源分配要求(@5.4.1) 。
2886+
2887+@6.9.1 异步规约动作(@6.8.3) :
2888+NPLA 中,这通过以规约动作(@6.8.3) 作为 CPS(@6.8.3) 的被跳板(trampoline) 执行的中间值(@4.4.4.2) 例程的机制实现。此时,当前动作(@6.8.3) 是尾动作(@6.8.3) 。
2889+跳板例程设置的规约操作将被异步调用(@6.8.3) ,即当前动作(@6.8.3) 设置为异步动作,在跳板中被循环调用。上下文的重写循环(@6.8.3) 提供一个跳板调用当前动作(@6.8.3) 。
2890+这种方式支持嵌套调用安全(@5.11) 。其它方式实现可能具有更好的性能,但存在限制而不被使用,如:
25382891 依赖不可移植的宿主语言扩展及实现假定,
2539-对对象语言源代码中使用的语言构造(特别是影响控制流的合并子(@4.5.3.2) )进行限制,使用单独的阶段进行抽象求值(@4.1) 。(这允许使用静态 TCO (@5.2.6.4.2) 所需的变换。)
2540-异步规约动作不需要接受以宿主语言 API 的参数传递的当前项(@5.8) 。
2892+对对象语言源代码中使用的语言构造(特别是影响控制流的合并子(@4.5.3.2) )进行限制,使用单独的阶段进行抽象求值(@4.1) 。(这允许使用静态 TCO (@5.10.4.2) 所需的变换。)
2893+异步规约动作不需要接受以宿主语言 API 的参数传递的当前项(@6.7) 。
25412894 必要时,由上下文或异步规约操作内部捕获保存的值确定当前项。
25422895 起始求值规约(@4.4) 的规约动作是异步求值规约动作。
2543-异步求值规约动作应及时以当前被求值项(@5.5) 更新保存在上下文中保存的项,使之后新的表达式求值的动作可确定正确的表达式。
2544-
2545-@5.10.2 异步规约操作流程:
2546-调用当前动作(@5.4.4) 引导若干操作(可包括后继的动作对当前动作的更新)组成一个异步操作序列实现对一个项的规约,这和对应的同步操作的树规约(@5.4.2) 保证 CPS 等价:
2896+异步求值规约动作应及时以当前被求值项(@6.3) 更新保存在上下文中保存的项,使之后新的表达式求值的动作可确定正确的表达式。
2897+
2898+@6.9.2 异步规约操作流程:
2899+调用当前动作(@6.8.3) 引导若干操作(可包括后继的动作对当前动作的更新)组成一个异步操作序列实现对一个项的规约,这和对应的同步操作的树规约(@6.2) 保证 CPS 等价:
25472900 除以下约定不支持的宿主行为关联的副作用,规约对应求值的副作用顺序满足可观察行为(@4.1.3) 等价(一般因为无法预测内容,需完全一致);
25482901 组成异步序列的动作(即作用在单一项上的操作)的作用的顺序仍然满足顺序依赖规则(@4.4.4.1) ,但不指定其它的顺序;保持可观察行为等价时,子项不需要使用严格满足树规约的实现。
25492902 使用异步序列进行规约即异步规约,实现一个支持 TCO 的规约的一般流程如下:
2550-明确下一步会在规约函数返回规约结果(@6.5.1) 后被异步调用的操作;
2551-通过直接访问或使用 ContextNode::SetupTail 等 API(@6.9.4) 设置上下文中的当前动作(@5.4.4) 为此操作。
2903+明确下一步会在规约函数返回规约结果(@6.7.1) 后被异步调用的操作;
2904+通过直接访问或使用 ContextNode::SetupTail 等 API(@6.8.3.1) 设置上下文中的当前动作(@6.8.3) 为此操作。
25522905 下一步设置的当前动作为下一动作(next action) 。
25532906
2554-@5.10.3 和同步规约的对应关系:
2555-因为不再通过宿主的活动记录保留同步操作对应的项,当明确异步动作及对应的项时,规约函数返回的规约结果(@6.5.1) 和对应同步操作不再具有一一对应关系;上述外层循环的循环条件仅考虑当前动作的有效性,具体同 ContextNode::Rewrite(@6.9.4) 的约定。
2556-当前动作对应单一项的到当前被求值项(@5.5) 的有界续延(delimited continuation) 。后继动作(@5.4.4) 是这个边界外的被定界的续延。
2907+@6.9.3 和同步规约的对应关系:
2908+因为不再通过宿主的活动记录保留同步操作对应的项,当明确异步动作及对应的项时,规约函数返回的规约结果(@6.7.1) 和对应同步操作不再具有一一对应关系;上述外层循环的循环条件仅考虑当前动作的有效性,具体同 ContextNode::Rewrite(@6.8.3.1) 的约定。
2909+当前动作对应单一项的到当前被求值项(@6.3) 的有界续延(delimited continuation) 。后继动作(@6.8.3) 是这个边界外的被定界的续延。
25572910 实现过程调用(@4.5.3.1) 时,设置子项的当前动作前保存当前动作,和同步操作时保存活动记录帧(@4.5.3.4) 等价。保存当前动作的方式(具体调用的 API )由调用方的实现指定。
2558-异步规约所在的重写循环(@5.10.1) 中,每个动作返回的规约状态的含义不变;除当前项(@5.8)(若保持副作用顺序,也包括每个子项)上最后一步外,使用当前动作实现一个项内非最终步骤的异步操作,都为部分规约(@5.8.1) ,规约函数返回值应为 ReductionStatus::Partial 。
2559-因为不记录规约的具体项,求值遍(@7.4.1.2) 中的动作序列在需要重规约时无法判断是否保留剩余的规约动作(@5.8.1) 。
2911+异步规约所在的重写循环(@6.9.1) 中,每个动作返回的规约状态的含义不变;除当前项(@6.7)(若保持副作用顺序,也包括每个子项)上最后一步外,使用当前动作实现一个项内非最终步骤的异步操作,都为部分规约(@6.7.1) ,规约函数返回值应为 ReductionStatus::Partial 。
2912+因为不记录规约的具体项,求值遍(@7.4.1.2) 中的动作序列在需要重规约时无法判断是否保留剩余的规约动作(@6.7.1) 。
25602913 是否继续需要剩余规约动作,由规约函数自行判断:
25612914 异步规约的实现通过重规约指定跳过剩余的规约动作;
25622915 上下文把规约动作的返回值保存在上下文的最后一次规约状态中;
2563-由剩余的规约动作中访问上下文检查状态,根据最后一次规约状态判断是否继续异步添加或忽略(跳过)当前被规约项上的规约迭代(@5.8.3) 中剩余的后续的规约动作。
2916+由剩余的规约动作中访问上下文检查状态,根据最后一次规约状态判断是否继续异步添加或忽略(跳过)当前被规约项上的规约迭代(@6.7.4) 中剩余的后续的规约动作。
25642917 使用当前动作实现等效的规约时,应注意保存和恢复当前动作,以保持已有规约操作的连续性。
2565-例如,调用 ContextNode::Switch(@6.9.4) 替换当前动作,返回被替换的当前动作可在当前动作后再次设置。
2918+例如,调用 ContextNode::Switch(@6.8.3.1) 替换当前动作,返回被替换的当前动作可在当前动作后再次设置。
25662919 不指定当前动作的 ContextNode::Switch 也可用于清空当前动作以满足扩展当前动作操作的前置条件。
25672920 实现可使用扩展当前动作操作(@6.9.7) 配合这些调用组合当前动作。保存当前动作可在被保存的当前动作前添加其它操作。因为当前动作表示的续延有界,也可在当前被求值项之后添加操作。
25682921
2569-@5.10.4 续延捕获(@4.5.3.3) 支持:
2922+@6.9.4 续延捕获(@4.5.3.3) 支持:
25702923 由于宿主语言不支持作为一等对象的续延,捕获续延需在异步动作的边界保存当前续延对应的状态。
25712924 一般地,当前异步动作可以是一般的可调用对象,设置组合的动作后不保证可以拆分,也没有限制异步动作的组合必须显式地保留外部可见的边界(保存到活动记录),因此边界需在当前动作外部。
25722925 考虑到性能开销和对象语言的基于规约实现表达式求值,约定仅在表达式边界允许保存续延状态。
25732926
2574-@5.10.5 异步规约中立(neutrality) :
2927+@6.9.5 异步规约中立(neutrality) :
25752928 一些实现对是否存在异步规约中立,即实现中被调用的操作可以是同步规约或异步规约。
25762929 这具有传递性:当底层实现依赖的被调用的操作都支持异步规约或对异步规约中立,此 API 实现支持异步规约。
25772930 所有间接依赖的 API 也都不涉及可观察行为(@4.1.3) 改变的上下文操作的情形称为严格中立。
2578-注意因为可能实现为同步或异步操作,非严格中立上下文的行为仍然可能改变(然而不应被用户代码依赖)。公开的规约不能是返回 ReductionStatus::Partial 的部分规约(@5.10.3) 。
2931+注意因为可能实现为同步或异步操作,非严格中立上下文的行为仍然可能改变(然而不应被用户代码依赖)。公开的规约不能是返回 ReductionStatus::Partial 的部分规约(@6.9.3) 。
25792932 NPLA 及其派生实现的 API 中,依赖可能包括通过同步的回调等方式引入。仅在完全确定不涉及引入这些依赖时,操作被视为异步规约中立的。
25802933
2581-@5.10.6 动作帧:
2582-分离的当前动作序列中 NPL::Reducer 类型作为规约器(reducer) ,保存抽象一遍规约的动作(@5.4.4) 。
2934+@6.9.6 动作帧:
2935+分离的当前动作序列中 NPL::Reducer 类型作为规约器(reducer) ,保存抽象一遍规约的动作(@6.8.3) 。
25832936 被保存的规约动作表示续延的帧(frame)(和活动记录帧(@4.5.3.4) 不一定一一对应)。类似方案参见 SRFI-157 。
2584-这也可用于实现捕获(@5.10.4) 有界续延(如控制操作符(@4.4.3.1) shift )。被捕获的续延在适当的条件下可重新组合到动作中以实现控制流的切换(如 reset 控制操作符)。
2585-
2586-@5.11 派生实现和应用实例:
2937+这也可用于实现捕获(@6.9.4) 有界续延(如控制操作符(@4.4.3.1) shift )。被捕获的续延在适当的条件下可重新组合到动作中以实现控制流的切换(如 reset 控制操作符)。
2938+
2939+@6.9.7 组合动作和扩展当前动作(@6.8.3) 的操作:
2940+以下 API 集中实现基于当前动作的复合操作作为中继(relay) ,可用于兼容异步规约(@6.9.1) :
2941+函数模板 NPL::RelaySwitched 规约当前动作作为后继动作(@6.8.3) ,并在后继动作起始设置当前动作。
2942+NPL::RelaySwitched 以当前动作或特定派生实现定义的其它动作调用时候,可支持 TCO(@5.10.4) 。
2943+
2944+@6.10 其它辅助 API :
2945+NPLA 提供 NPL 语言实现诊断相关的操作,以 Doxygen 命令标识为 \ingroup NPLDiagnostics 。
2946+函数 NPL::TraceException
2947+
2948+@6.11 派生实现和应用实例:
25872949 除 NPLA 公共实现和派生实现 NPLA1 外,NPLA API 还具有一些其它应用实例。
25882950
2589-@5.11.1 NPLA1 约定:
2951+@6.11.2 NPLA1 应用实例:
2952+在对象语言(@7) 外,NPLA1 的实现也被用于其它应用。
2953+NPLA1 当前加入特定的序列化和反序列化作为配置文件,参见 NPL::Configuration 。
2954+NPLA1 的上述配置文件加入特定的匹配和初始化机制作为 YSLib::UI::Loader([Documenatation::YSLib @@6.11.9]) 在运行时读取用户界面布局和配置的脚本。
2955+NPLA1 用于 MIME 类型和文件名映射([Documentation::YSLib @@4.5.3]) 的实现,上述配置文件对应的外部配置格式。
2956+注意这些应用不直接使用 NPLA1 的语义,其中使用的 TermNode 类型中名称直接表示上下文(@4.6.1) 中的实体名称。
2957+计划使用完整的实现(@7) 取代这些应用的底层,使用 NPLA1 作为对象语言或作为附加的代码生成遍重新实现这些应用,但具体路线图未定。
2958+关于当前提供的应用,另见依赖管理模块(@8.5) 和 @10 。
2959+
2960+@6.11.3 NPLA 其它实现应用实例:
2961+NPL::SXML 命名空间提供的 API 部分支持以 NPLA 分析 SXML 及构造 NPLA 表示的节点并转换为 XML 输出。
2962+NPL 自定义分析器被用于 NPL::Dependency 模块中的函数 NPL::DecomposeMakefileDepList 实现解析 GCC 输出的兼容 GNU make 包含依赖字符串。
2963+
2964+@6.11.3.1 非规约节点操作:
2965+部分 API 用于不经过规约过程的变换,为纯语法操作。
2966+SXML(@6.11.3) 依赖这些 API 。
2967+其它操作包括向输出流打印节点的文本表示等。
2968+
2969+@6.11.4 实现兼容性:
2970+除非在此另行指定,YSLib 中的 NPL 实现保持兼容。
2971+影响向前兼容的变更:
2972+b449 增加对多个未命名节点(叶节点或第一个子节点未能解析为名称的分支节点)作为非名称子节点时的反序列化支持。多个值会被以 $ 前缀接序号(从 0 起始)命名。之前的版本中读取的节点名称为空串,值被覆盖为第一个节点值。
2973+
2974+@7 NPLA1 解释实现:
2975+NPLA1 解释实现是 NPLA 给定的 AST(@6.1.6) 作为输入的解释器(@5.1) 。
2976+和 NPLA 类似,NPLA1 的实现也注重可扩展性。在限制修改语言规则的前提下,部分实现可被语义等价地使用更高性能的简化实现替代。
2977+命名空间 NPL::A1 提供了特定于 NPLA1 的 API 。以下命名空间 A1 指 NPL::A1 。
2978+命名空间 A1::Forms 提供了 NPLA1 的语法形式(@3.4.3) 对应的功能的实现(@8.4) 。
2979+以下限定名称中,无歧义的嵌套限定命名空间使用非嵌套类型名称(即 Forms 指 A1::Forms )。
2980+除此以外,本章以 @6.1.4 约定的限定名称的方式使用 API 中出现的 C++ 名称和项目模块命名空间。
2981+
2982+@7.1 NPLA1 一般约定:
2983+在 NPLA 的一般约定(@5.2) 外,本节概述不通过具体 API 指定的一般特性。
2984+参见存储和对象模型(@5.6)(包括临时对象生存期(@5.6.2) 和内存安全(@5.6.3) )的基本规则。
2985+另见作为具体实现的 NPLA 间接值(@6.4) 使用规则的保证机制(@7.1.4) 及 NPLA1 间接值的有关规则(@7.1.3) 和求值对应的 API(@7.6.4) 。
2986+类似 NPLA(@6.1.2) ,NPLA1 实现可在实现中提供构建时确定的实现选项,以宿主语言的宏定义等方式启用。
2987+和 NPLA 不同,这些选项主要用于提供参考实现,不应被直接作为公开接口;通常情形不需要更改。
2988+特定的 API 可间接依赖这些特性,以便派生实现提供不同的优化实现,包括:
2989+A1::REPLContext::IsAsynchronous(@7.8.1)
2990+部分 NPLA 安全保证(@6.1.3) 依赖特定的实现选项支持(如 NPL_Impl_NPLA1_Enable_ThunkedSeparatorPass(@7.9.2) ),当没有启用时可能不满足。
2991+
2992+@7.1.1 对象语言约定:
25902993 NPLA1 仅使用宿主语言的类型和值作为状态。
2591-在 NPLA 的基础上,NPLA1 要求对象语言支持以一等对象作为表达式并被求值(@5.5) 。
2592-类型等价性基于类型映射(@5.2.3) 及其实现(@5.4.1) ,由 C++ 的语义规则定义。
2994+在 NPLA 的基础上,NPLA1 要求对象语言支持以一等对象作为表达式并被求值(@6.3) 。
2995+类型等价性基于类型映射(@5.5) 及其实现(@6.1.7) ,由 C++ 的语义规则定义。
25932996 值等价性由宿主实现的 == 表达式的结果定义。
25942997 除非另行指定,所有类型的外部表示(@4.1.1) 都是允许从作为内部表示的项节点结构确定的同宿主类型的空字符结尾的字符串(即 ISO C++ 的 NTCTS )。
2595-
2596-@5.11.1.1 附加规则:
2998+扩展字面量同 NPLA(@5.2) ,其中至少包括 #t 、#f 和数值的处理。前两者的对象语言和宿主值(@6.3) 类型为 bool ,后者的类型由派生实现指定。
2999+引入过程(@4.5.2.1) 的具体形式参见 @8.4.5 ,也可约定通过特定的本机函数(@5.3) 等其它方式提供。
3000+
3001+@7.1.1.1 附加规则:
25973002 当前仅支持标识符(@3.3.1) 作为名称。
25983003 部分名称是保留名称(reserved name) :含有“$$”的名称保留给宿主交互使用;含有“__”的名称保留给 NPLA1 实现。
25993004 若程序的源代码中的保留名称被作为变量名解析,程序的行为未定义。
26003005 在 NPLA 规则(@5.2) 的基础上,具有以下情形的程序引起未定义行为:
26013006 显式使用保留给实现的标识符。
26023007
2603-@5.11.2 NPLA1 应用实例:
2604-在对象语言(@7) 外,NPLA1 的实现也被用于其它应用。
2605-NPLA1 当前加入特定的序列化和反序列化作为配置文件,参见 NPL::Configuration 。
2606-NPLA1 的上述配置文件加入特定的匹配和初始化机制作为 YSLib::UI::Loader([Documenatation::YSLib @@5.11.9]) 在运行时读取用户界面布局和配置的脚本。
2607-NPLA1 用于 MIME 类型和文件名映射([Documentation::YSLib @@4.5.3]) 的实现,上述配置文件对应的外部配置格式。
2608-注意这些应用不直接使用 NPLA1 的语义,其中使用的 TermNode 类型中名称直接表示上下文(@4.6.1) 中的实体名称。
2609-计划使用完整的实现(@7) 取代这些应用的底层,使用 NPLA1 作为对象语言或作为附加的代码生成遍重新实现这些应用,但具体路线图未定。
2610-关于当前提供的应用,另见依赖管理模块(@8.5) 和 @10 。
2611-
2612-@5.11.3 NPLA 其它实现应用实例:
2613-NPL::SXML 命名空间提供的 API 部分支持以 NPLA 分析 SXML 及构造 NPLA 表示的节点并转换为 XML 输出。
2614-NPL 自定义分析器被用于 NPL::Dependency 模块中的函数 NPL::DecomposeMakefileDepList 实现解析 GCC 输出的兼容 GNU make 包含依赖字符串。
2615-
2616-@5.11.4 实现兼容性:
2617-除非在此另行指定,YSLib 中的 NPL 实现保持兼容。
2618-影响向前兼容的变更:
2619-b449 增加对多个未命名节点(叶节点或第一个子节点未能解析为名称的分支节点)作为非名称子节点时的反序列化支持。多个值会被以 $ 前缀接序号(从 0 起始)命名。之前的版本中读取的节点名称为空串,值被覆盖为第一个节点值。
2620-
2621-@6 NPLA 公共语言实现接口:
2622-部分实现的功能由公共 API 的形式提供,以便派生实现复用。
2623-用于NPLA1 应用实例(@5.11.2) 和 NPLA 其它实现应用实例(@5.11.3) 等部分不直接用于 NPLA 解释实现的 API 可能在本节中被略过。
2624-一些 API 仅列出名称,其具体描述详见接口文档(通过源代码注释生成)。
2625-
2626-@6.1 安全保证:
2627-除以下例外,NPLA 实现及对应可能由用户提供的替换的实现应支持嵌套调用安全(@5.2.7) :
2628-直接递归遍历节点的操作如 NPL::SetContentWith(@6.2.1) 和 NPL::TokenizeTerm(@6.4.3) ;
2629-因为直接或间接调用上述遍历节点实现的操作,详见不支持嵌套调用安全的提升操作(@6.6.6) ;
2630-派生实现定义的情形。
2631-
2632-@6.2 节点(node) 操作:
2633-NPLA 提供类似 YSLib::ValueNode 的 TermNode 类(@5.4.2) 和 ContextNode 类(@5.4.4) 的作为规约的公共实现基础。
2634-ContextNode 作为上下文的实现,其中包含的部分数据成员是环境(@4.6.1.1) 的实现。
2635-当前实现中,环境中的绑定(@5.4.3) 是平坦的映射,未依赖节点的递归性质;但是其中的被绑定对象(@5.4.3) 使用 TermNode ,也适用以下操作。
2636-
2637-@6.2.1 项节点访问:
2638-模块 SContext 提供访问项节点的 API 。
2639-其中,判断项节点基本分类(@5.4.2.1) 的谓词包括:
2640-NPL::IsBranch
2641-NPL::IsBranchedList
2642-NPL::IsEmpty
2643-NPL::IsExtendedList
2644-NPL::IsLeaf
2645-NPL::IsList
2646-这些谓词分别判断枝节点、分支列表节点、空节点、扩展列表节点、叶节点和列表节点。
2647-TermNode 在类定义外提供关于节点内容的辅助 API 简化操作:
2648-函数模板 NPL::Access 和 NPL::AccessPtr 访问值数据成员(@5.4.2) 中持有对象的引用或指针。重载兼容 TermNode 和 ValueNode 。
2649-通常仅在不涉及语义(仅涉及语法,或更基本的底层操作实现)时使用其访问项中非列表类型(@5.4.2.1) 的值。
2650-涉及语义时,使用 @6.4.3 、@6.4.4 或 @6.4.5 的 NPLA API 代替。这些接口提供了确切的项结构检查以符合正规化表示的相关假设(@5.9.2) 。
2651-函数 NPL::AssertBranch 断言项是枝节点。
2652-函数模板 NPL::AsTermNode
2653-函数模板 NPL::SetContentWith
2654-函数模板 NPL::GetValueOf
2655-函数 NPL::AccessFirstSubterm
2656-函数 NPL::ShareMoveTerm
2657-函数 NPL::RemoveHead
2658-函数模板 NPL::SetContentWith
2659-其余 API 包括:
2660-函数模板 NPL::HasValue
2661-函数模板 NPL::TraverseSubnodes
2662-
2663-@6.2.2 非规约节点操作:
2664-部分 API 用于不经过规约过程的变换,为纯语法操作。
2665-SXML(@5.11.3) 依赖这些 API 。
2666-其它操作包括向输出流打印节点的文本表示等。
2667-
2668-@6.3 异常处理:
2669-NPL::NPLException 是 NPL 实现的异常基类。
2670-其它 NPL 异常都从此类直接或间接派生,包括:
2671-NPL::TypeError
2672-NPL::ValueCategoryMismatch
2673-NPL::ListReductionFailure
2674-NPL::ListTypeError 列表类型(@5.4.2.1) 错误。
2675-NPL::ParameterMismatch
2676-NPL::ArityMismatch
2677-NPL::InvalidSyntax
2678-NPL::BadIdentifier
2679-NPL::InvalidReference
2680-除 @6.2.2 外,NPLA 实现可能抛出这些异常或派生实现定义的派生这些类的其它异常。
2681-NPLA 实现可能抛出标准库异常。
2682-一般地,不通过对象语言构造(而仅通过互操作(@5.2.1) 或实现缺陷)引起的异常,不使用 NPL 异常基类。
2683-
2684-@6.4 上下文无关非节点处理 API :
2685-NPLA 实现提供不依赖一般项规约逻辑(@5.8) 的公共 TermNode 操作 API 。
2686-以下 API 处理和 TermNode 或其中的成员数据类型相关但和 ContextNode 无关的数据。
2687-
2688-@6.4.1 记号类别支持:
2689-NPL::LexemeCategory 表示 NPLA 支持的词素(@5.6.1) 类别。
2690-NPL::CategorizeBasicLexeme 和 NPL::CategorizeLexeme 对词素分类。两者的区别在于是否把扩展字面量视为符号(@5.6.1) 。
2691-NPL::IsNPLAExtendedLiteralPrefix 判断字符是否为 @5.2 约定的扩展字面量前缀。
2692-NPL::IsNPLASymbol
2693-
2694-@6.4.2 规约相关类型别名:
2695-
2696-@6.4.2.1 记号值:
2697-基本概念参见 NPLA 记号值(@5.6.1) 。
2698-调用 NPL::TermToNamePtr(@6.4.3) 访问具有记号值的名称节点对应的字符串。
2699-
2700-@6.4.2.2 锚对象(@5.6.2) 指针:
2701-NPL::AnchorPtr 是锚对象的指针的类型,是不确定类型的 shared_ptr 实例。
2702-相关接口详见上下文 API(@6.9.4) 。
2703-
2704-@6.4.3 项访问操作:
2705-NPLA 提供访问若干访问 TermNode 的便利接口。
2706-NPL::TermToNamePtr
2707-NPL::TermToString
2708-NPL::TermToStringWithReferenceMark
2709-NPL::TermToTags
2710-NPL::ThrowInsufficientTermsError
2711-NPL::ThrowListTypeErrorForInvalidType
2712-NPL::ThrowListTypeErrorForNonlist
2713-NPL::TokenizeTerm
2714-NPL::TryAccessLeaf
2715-NPL::TryAccessTerm
2716-
2717-@6.4.4 项引用操作:
2718-NPL::TermReference(@5.6.3) 包括若干成员,其中主要有以下 API :
2719-IsMovable
2720-GetEnvironmentReference
2721-get
2722-对 NPL::TermReference 的相关操作包括以下 API :
2723-NPL::Collapse
2724-NPL::PrepareCollapse
2725-NPL::ReferenceTerm
2726-
2727-@6.4.5 项引用访问操作:
2728-类型 NPL::ResolvedNPL::TermReferencePtr 表示解析项后作为访问参数的项引用指针。
2729-函数 NPL::ResolveToNPL::TermReferencePtr 转换项引用指针为项引用的访问参数指针。
2730-函数和函数模板 NPL::IsMovable 判断解析后的项是否指示可转移项(@5.6.3.4) 。
2731-若被判断的参数是 NPL::TermReference(@5.6.3) 值,则同 NPL::TermReference::IsMovable(@6.4.4) 。
2732-否则,被判断的参数是指向指向项的指针(支持包括项引用指针和项引用的访问参数指针)。
2733-考虑到项可能是 NPL::TermReference 的访问项的相关操作包括以下 API :
2734-NPL::TryAccessReferencedLeaf 是 NPL::TryAccessTerm 和 NPL::ReferenceTerm 的复合。
2735-NPL::IsReferenceTerm
2736-NPL::IsBoundLValueTerm
2737-NPL::IsUncollapsedTerm
2738-NPL::IsModifiableTerm
2739-NPL::IsUniqueTerm
2740-NPL::IsTemporaryTerm
2741-NPL::ResolveTerm 解析并间接引用处理可能是引用值(@5.5.4) 的项,其中尝试解析使用 NPL::TryAccessLeaf 。
2742-NPL::CheckRegular 指定函数访问检查项后的指定类型正规值(@5.9) ,即按模板参数指定的类型访问解析后的正规值。
2743-NPL::AccessRegular
2744-NPL::ResolveRegular
2745-
2746-@6.4.6 项引用函数对象:
2747-除以上 API ,NPLA 还提供以下关于 @6.4.4 的便利接口:
2748-ç±» NPL::ReferenceTermOp
2749-ç±» NPL::ComposeReferencedTermOp
2750-
2751-@6.5 规约 API :
2752-基本概念和约定参见 @5.8 。
2753-部分规约 API 以函数(如 @6.5.3 )的形式提供,其中:
2754-ContextNode 可被递归地作为子项规约的参数,因此可以不使用其它参数。
2755-其它兼容实现可能使用其它参数。
2756-
2757-@6.5.1 规约结果类型:
2758-规约函数(@5.8.5) 的返回类型可以是规约结果(@5.8.1) ,以枚举 NPL::ReductionStatus 表示,其枚举项的值具有含义:
2759-ReductionStatus::Partial 指定部分规约;
2760-ReductionStatus::Neutral 指定中立规约;
2761-ReductionStatus::Clean 指定纯值规约;
2762-ReductionStatus::Retained 指定非纯值规约;
2763-ReductionStatus::Regular 指定已取得正规表示(@5.9) 的规约(当前实现的枚举值的数值同指定非纯值规约);
2764-ReductionStatus::Retrying 指定重规约。
2765-已取得正规表示的规约未指定公开的规约结果覆盖行为(@5.8.3.3) ,可以实现为中立规约、纯值规约或非纯值规约;当前实现同非纯值规约会覆盖默认的纯值规约,避免不必要的清理(@5.8.2.1) 而有利于性能。
2766-
2767-@6.5.2 规约迭代:
2768-基本概念参见 @5.8.3 和 @5.8.4 。
2769-规约检查 API :
2770-函数 NPL::CheckReducible 检查参数指定的规约结果是否可继续规约,详见 @5.8.4.1 。
2771-函数模板 NPL::CheckedReduceWith 循环规约直至不满足 NPL::CheckReducible 的判断结果。
2772-正规表示(@5.9) API :
2773-函数 NPL::RegularizeTerm 按规约结果正规化(@5.9.2) 项。
2774-
2775-@6.5.3 规约函数(@5.8.5) 形式约定:
2776-规约函数(@5.8.5) 包括以下形式:
2777-第一参数为被规约的项,类型为 TermNode& ;
2778-或者,第一参数是和 TermNode& 对应的容器的以下至少之一:
2779- 有效的 TermNode::Container& 值或 TermNode::Container 对象上的连续序列的迭代器范围之一;
2780- 可作为值数据成员(@5.4.2) 的有效的 ValueObject& 值。
2781-替代 TermNode& 的参数的形式允许不构造完整的 TermNode 而允许更好的性能,但仅在保证不要求直接使用 TermNode (如取得 O(1) 的 size() 或调用遍(@7.4.1) )时适用。
2782-规约函数的返回类型是 ReductionStatus(@6.5.1) 或 void 。
2783-返回 void 的规约函数在规约结果(@5.8.1) 的语义上同总是返回 ReductionStatus::Clean 。
2784-
2785-@6.5.3.1 直接和间接规约函数:
2786-一些规约函数被设计可直接用于作为 EvaluationPass(@7.4.1.2) 遍的处理器(@6.8.1) ,称为直接规约函数。
2787-其余规约函数是间接规约函数。
2788-间接规约函数可能直接或间接调用直接规约函数。
2789-规约函数对表示被规约的项以及上下文(若存在)或其上述对应形式的形式参数的使用应符合 NPLA 实现的规约迭代默认的约定(@5.8) 。
2790-构成直接规约函数类型的一个必要非充分条件是:
2791-第一参数的类型是 TermNode& ,且当第二参数存在时,其类型可隐式转换为 ContextNode& 。
2792-
2793-@6.5.3.2 名称约定:
2794-以下规约函数的名称以 Reduce 起始:
2795-直接规约函数(@6.5.3.1) ;
2796-起始两个形式参数符合直接规约函数要求,且可能直接或间接调用其它直接规约函数的间接规约函数。
2797-不调用其它直接规约函数的间接规约函数的名称以 Evaluate 起始。
2798-其余规约函数的名称不以 Reduce 或 Evaluate 起始。
2799-注意不同实现中的命名空间可能不同,在此不作限制。
2800-
2801-@6.5.3.3 主规约函数:
2802-NPLA 的派生实现可提供对一般的规约(@4.1) 的实现以蕴含对象语言表达式的求值规则。
2803-NPL 的表达式的语法是递归表示的,因此包括对列表表达式子项的递归规约。使用单一规约函数实现的这种规约一般即树规约(@5.4.2) 。
2804-规约实现作用在对象语言表达式上通用的求值算法(evaluaton algorithm) ,符合规约性质(@5.5) 。
2805-主规约函数是提供这种单一规约函数实现的直接规约函数(@6.5.3.1) 。
2806-间接值(@5.7.3) 对主规约函数透明,不被直接处理。
2807-推论:主规约函数实现的求值算法中,被求值的表达式不发生值类别转换(@5.5.5) 。这简化实现的复杂性,并允许派生实现单独处理需要间接值(如引用值)的情形,并在其它情形避免不需要的开销。
2808-
2809-@6.6 提升项:
2810-对项的提升(lifting) 指一类对项的替换变换,使用项进行一定的变换后取代其它项或其一部分,并满足下述提升条件。
2811-决定替换变换是提升的条件为:被提升的项是提升后的项的一个直接或间接子项,以树表示项则为叶节点取代枝节点。
2812-这可以视为作为语法变换的消去 λ 抽象的 lambda 提升(详见 https://en.wikipedia.org/wiki/Lambda_lifting )的一般化,但此处和 λ 抽象没有直接关联。
2813-被提升的项往往被转移,因此一般地,需要可修改。
2814-提升时对抽象的值表示进行操作实现基本的语义功能,可能进行检查,包括为满足接口行为的语义检查和实现为预防宿主语言的未定义行为的附加检查(@5.2.2) 。
2815-提升项对象通过变换操作取作为项的值数据成员(@5.4.2) 。在此基础上有递归版本。
2816-提升项可引入或消除(@5.7.6) 间接值(@5.7.3) 。
2817-提升项通过被引用的对象替换作为项的值数据成员的引用值而消除引用值(@5.6.3.3) 。提升项的求值结果(@5.8.1) 是消除引用值的结果。
2818-提升操作辅助对项的操作,可用于实现规约函数,包括以下各节中描述的 API 。其中:
2819-名称前缀为 Lift 的函数是一元提升操作,具有一个 TermNode& 参数和其它可选的参数,提升第一参数指定的项,提升后的项保存在第一参数指定的项;
2820-名称前缀为 Move 的函数是二元提升操作,具有两个 TermNode& 参数和其它可选的参数,要求参数指定的两个项不相同,提升第二参数指定的项,提升后的项保存在第一参数指定的项。
2821-
2822-@6.6.1 基本提升操作:
2823-基本提升操作包括直接转移赋值及以下 API :
2824-成员函数 ValueObject::MakeIndirect 取记号值或持有其它类型的值的引用的间接值(@5.7.3.3) 。
2825-成员函数 ValueObject::MakeMoveCopy 转移或复制对象以消除间接值。
2826-函数 NPL::LiftOther
2827-函数 NPL::LiftTerm
2828-函数 NPL::LiftOtherOrCopy
2829-函数 NPL::LiftTermOrCopy
2830-函数 NPL::LiftTermValueOrCopy
2831-
2832-@6.6.2 可能引入间接值(@5.7.5) 的提升操作:
2833-以下值操作可能引入间接值:
2834-函数 NPL::LiftCollapsed
2835-函数 NPL::MoveCollapsed
2836-函数 NPL::LiftTermRef 提升项引用:提升项的内容为参数指定的项或值的引用值(@5.5.4) 。
2837-引用值通过参数指定的值对象(@5.4.2) 上创建得到。
2838-函数 NPL::LiftToReference 提升项对象为引用。
2839-若项对象表示引用值则提升项,否则对 ValueObject 进行基于 ValueObject 所有权的检查(间接进行生存期检查)并取引用这个项的引用值。
2840-运行时进行的检查类似于强制 C++ 的一元 & 对表达式值类别(另见 @4.2.3 )的要求但更严格(尽管仍然不能保证避免未定义行为),避免临时对象(@5.5.6) 被保存为引用值。
2841-
2842-@6.6.3 消除中间值的提升操作:
2843-函数 NPL::LiftMoved
2844-函数 NPL::LiftMovedOther
2845-函数 NPL::LiftToReturn
2846-函数 NPL::MoveRValueToReturn
2847-函数 NPL::MoveRValueToForward
2848-函数 NPL::LiftSubtermsToReturn
2849-本节的函数作用在被规约项时,对引用值的操作实现临时对象实质化转换(@5.5.5) 所在的求值规约的最后部分的操作,一般在尾上下文(@4.4.7) 中进行处理。
2850-提升消除中间值只作用在被提升项,不作用在子项。需要递归复制的操作不在这里提供,以避免抽象泄漏。
2851-为消除中间值需要进行复制消除(@5.5.6.3) ,其中使用不同的可转移条件(@5.6.3.4) 决定使用转移而非复制。
2852-当前实现在转移项时使用的宿主转移操作总是使用宿主对象的转移(基于 NPL::SetContentWith(@6.2.1) )而不是 TermNode 的转移。
2853-另见 @5.7.6.3 。
2854-
2855-@6.6.4 辅助提升操作:
2856-函数 NPL::LiftFirst 和 NPL::LiftLast 提升第一个和最后一个子项。
2857-
2858-@6.6.5 非内存安全(@5.2.4.3.1) 的提升操作:
2859-以上操作中,不保证引入的对象内存安全(@5.2.4.3) 且不提供检查(@5.2.2) 的操作有:
2860-NPL::LiftTermRef :通过 ValueObject::MakeIndirect 引入间接值。
2861-NPL::LiftToReference :非内存安全的项引用操作(@5.2.4.3.1) 。
2862-
2863-@6.6.6 不支持嵌套调用安全(@5.2.7) 的提升操作:
2864-以上操作中,作为例外,当前(因为直接或间接调用 NPL::SetContentWith(@6.2.1) 遍历节点)不支持嵌套调用安全的函数包括:
2865-NPL::LiftTermValueOrCopy(@6.6.1) ;
2866-NPL::LiftTermRef(@6.6.2) ;
2867-@6.6.3 的操作。
2868-
2869-@6.7 辅助函数:
2870-可作为规约函数(@6.5.3) 的不依赖上下文的 API 的辅助函数称为辅助规约函数,能处理一般情形的项,不存在作为断言的要求项非空的前置条件。
2871-
2872-@6.7.1 简单规约操作:
2873-NPLA 提供只依赖项既有结构的项简单规约操作。这些操作是直接规约函数(@6.5.3.1) 。
2874-函数 NPL::ReduceBranchToList 要求参数是枝节点(@5.4.2.1) ;移除第一个子项,剩余项作为列表的元素,并返回 ReductionStatus::Retained(@6.5.1) 。
2875-函数 NPL::ReduceBranchToListValue 要求参数是枝节点;移除第一个子项,剩余项作为列表的元素,调用 NPL::LiftSubtermsToReturn(@6.6.2) 提升子项的值,并返回 ReductionStatus::Retained(@6.5.1) 。
2876-这保证最外的第一级引用被提升,不影响被引用项自身包含的引用。
2877-函数 NPL::ReduceForLiftedResult
2878-函数 NPL::ReduceHeadEmptyList
2879-函数 NPL::ReduceToList
2880-函数 NPL::ReduceToListValue
2881-
2882-@6.7.2 间接辅助规约操作:
2883-以下辅助规约函数转移参数实现规约:
2884-函数 NPL::ReduceToReference
2885-函数 NPL::ReduceToReferenceAt
2886-函数 NPL::ReduceToValue
2887-
2888-@6.8 遍迭代 API :
2889-NPLA 实现提供支持可调用的遍(@5.8.3) 的集合作为一次规约迭代的基础可配置的边界。
2890-部分遍以 ContextNode(@6.9) 的引用作为参数。声明时,ContextNode 不需要是完整类型。
2891-
2892-@6.8.1 遍处理器(handler) :
2893-每一遍规约迭代(@5.8.3) 实现为一个事件处理器(handler) 。这允许运行时修改求值执行的不同逻辑以取得较大的灵活性和可扩展性。
2894-类似 std::function ,遍处理器持有目标对象(target object) 并具有其所有权。
2895-
2896-@6.8.2 遍合并:
2897-函数 NPL::IsOverridableReductionResult 判断参数指定的规约结果在合并中是否可被覆盖(@5.8.3.3) 。
2898-函数 NPL::CombineReductionResult 和 NPL::CombineSequenceReductionResult 提供 @5.8.3.1 的成功终止和一般规约结果的合并操作。
2899-
2900-@6.9 环境和上下文处理:
2901-类 ContextNode 表示上下文(@5.4.4) 。
2902-类 Environment 表示环境(@5.4.3) 。
2903-只使用其中包含的环境时,可使用 ContextNode(@6.9.4) 和 Environment(@6.9.1) 的 API 访问环境的数据。
2904-需要上下文时,一般使用现有上下文,可能替换和重置状态;或通过现有上下文和环境创建新的上下文。
2905-
2906-@6.9.1 环境类:
2907-类 Environment 中,指定变量绑定的名称绑定映射(@5.4.3) 类型为 Environment::BindingMap 。
2908-类 Environment 提供父环境以及基本的名称解析(@4.3.3) 等操作的接口。其中的操作可被针对特定的环境对象重新设置。
2909-类 Environment 包含以下相关的数据成员:
2910-Bindings :类型为 Environment::BindingMap 的变量绑定容器,实现变量名称到表示被绑定对象的映射(@5.4.3) 。
2911-Parent :作为可在运行时确定类型的父环境(@5.4.3) 引用,用于重定向(@4.3.3) 算法的实现。
2912-类 Environment 还包含其它一些维护内部状态和绑定(@4.1) 的 API 。
2913-成员函数 IsOrphan 判断是否为孤立的环境,即锚对象(@5.6.2) 未被外部引用。
2914-成员函数 GetAnchorPtr 取锚对象指针(@6.4.2.2) 。
2915-成员函数 GetAnchorCount
2916-成员函数 GetMapRef 取名称绑定映射的引用。
2917-成员函数模板 AddValue
2918-成员函数模板 Bind
2919-静态成员函数 CheckParent
2920-静态成员函数 Deduplicate
2921-静态成员函数 DefaultResolve
2922-的成员函数 Define 、Redefine 和 Remove 修改上下文中的值。
2923-成员函数 Define 和 DefineChecked 添加定义。后者在定义已存在时抛出异常。
2924-成员函数 LookupName 在绑定集合中查找(一般作为局部变量的)标识符指定的名称,实现名称查找(@4.3.3) 。输入字符串指定(假定名称验证已通过的)名称。
2925-成员函数 Remove 和 RemoveChecked 以字符串为标识符在指定上下文移除定义。后者在定义不存在时抛出异常。
2926-成员函数 Replace 和 ReplaceChecked 以字符串为标识符在指定上下文的名称查找结果中替换定义。后者在定义不存在时抛出异常。
2927-成员函数 ThrowForInvalidType
2928-成员函数 ThrowForInvalidValue
2929-友元函数 swap 交换环境对象的绑定、算法和锚对象指针。
2930-
2931-@6.9.2 环境和变量绑定相关类型:
2932-NPL::EnvironmentList 是 NPL::vector<ValueObject> 的别名,表示一个环境列表,作为父环境(@5.4.3) 时可用于递归重定向(@4.3.3) 。
2933-Environment::DefaultResolve(@6.9.1) 中的重定向(@4.3.3) 依次检查如下之一的重定向目标的宿主值(@5.5) 类型:
2934-NPL::EnvironmentList ;
2935-shared_ptr<Environment> ;
2936-NPL::EnvironmentReference 。
2937-实现可提供其它宿主类型,如 observer_ptr<Environment> ,用于内部实现而不要求所有使用环境的操作支持。
2938-派生实现可提供其它公开的宿主类型。
2939-
2940-@6.9.3 环境引用相关类型 :
2941-类型 shared_ptr<Environment> 和类 NPL::EnvironmentReference(作为中间值另见 @5.6.2 )是环境强引用和环境强引用(@5.4.3) 的宿主类型(@5.2.3) 。
2942-访问环境的 API 可使用非空的环境引用值引用对象,或空的引用值指定不存在环境对象。
2943-使用可能具有这些值的代码需要对空值进行检查。因为这些空值不出现在对象语言,一般通过 Environment::ThrowForInvalidValue(@6.9.1) 抛出 std::invalid_argument 而非 NPL 异常(@6.3) 。
2944-通过类 Environment 对象可取得引用它的 shared_ptr<Environment> 类型的环境强引用值。
2945-因为支持直接从对象中创建 shared_ptr<Environment> 类型的值,环境类应使用和强引用兼容的分配方式创建,以支持相关引用类型(@5.4.3) 。
2946-使用环境弱引用(@5.4.3) 可避免非预期的所有权(@4.2.2.3) 关系构成循环引用(@5.2) 。
2947-当前支持循环引用检查的上下文有:
2948-Environment::DefaultResolve(@6.9.1) 重定向父环境(@5.4.3) ;
2949-以引用的环境作为动态环境创建 vau 抽象(@8.4.5) 且创建的对象不对环境有所有权。
2950-因为循环引用是对象语言的未定义行为,检查循环引用失败抛出 std::bad_weak_ptr 而非 NPL 异常(@6.3) 。
2951-对象语言中支持环境引用的宿主值类型是 Environment::DefaultResolve(@6.9.1) 中的重定向目标的宿主值类型(@6.9.2) 中公开的类型,即:
2952-shared_ptr<Environment> ;
2953-NPL::EnvironmentReference ;
2954-派生实现定义的提供明确支持的其它类型。
2955-除 shared_ptr<Environment> 的 use_count 的结果外,环境的引用计数具体值是实现细节,不保证具有明确含义的稳定的结果。
2956-
2957-@6.9.4 上下文 API :
2958-类型 ReducerFunctionType
2959-类型 NPL::Reducer 是以上下文引用作为参数的规约器(@5.10.6) ,保存动作(@5.4.4) 。
2960-NPL::Reduer 能以兼容 ReducerFuntionType 的方式调用,可选提供一个 ContextNode& 参数。
2961-类 ContextNode 提供其它一些上下文 API 。
2962-成员类型 ExceptionHandler
2963-成员类 ReducerSequence
2964-成员类 ReductionGuard
2965-数据成员 Resolve :名称解析算法。
2966-数据成员 TailAction
2967-数据成员 HandleException
2968-数据成员 LastStatus 保存最后一次规约状态,配合当前动作序列(@5.4.4) 决定之后的续延中的动作调用。
2969-成员函数 IsAlive
2970-成员函数 GetBindingsRef 取环境记录(@5.4.4) 中的名称绑定映射(@5.4.3) 引用。
2971-成员函数 GetCurrent :访问上下文内部的保存当前动作序列,是类型为 NPL::ReducerSequence 的一遍规约序列的 const 引用。
2972-当前动作序列可被作为尾动作(@5.4.4) 。
2973-成员函数 GetRecordRef
2974-成员函数 GetMemoryResourceRef 取存储资源(@5.7.1) 引用。
2975-成员函数 ApplyTail
2976-静态成员函数 DefaultHandleException
2977-成员函数 Rewrite
2978-成员函数 RewriteLoop 以 IsAlive 的结果为条件的循环调用 ApplyTail ,作为跳板(@5.10.1) 实现一般规约重写。
2979-成员函数 SaveExceptionHandler
2980-成员函数模板 SetupCurrent 和成员函数模板 SetupFront 设置当前动作序列。前者具有前置条件 !IsAlive() 以避免重复设置覆盖当前动作的原始状态。
2981-成员函数 Shift
2982-成员函数 Switch
2983-成员函数 SwitchEnvironment 和 SwitchEnvironmentUnchecked 切换环境,即设置参数指定的环境并返回之前的环境。两者接受 shared_ptr<Environment> 的参数,区别为对空参数抛出异常或引起未定义行为。环境切换便于实现续延或过程调用(@4.5.3.1) 。
2984-切换环境时,旧的环境若没有被引用,则被释放。由于环境具有对象的所有权(@5.2.4.2) ,内部绑定的对象作为自动变量也被一并释放(@5.2.4.5) 。
2985-成员函数 ShareRecord 和 WeakenRecord 取环境的引用。
2986-部分 API 使用的参数类似规约函数(@6.5.3) 。
2987-这些函数的参数可能在内部实现被预先绑定,调用时对应的实际参数被忽略:如 TermNode& 类型的形式参数被绑定,内部使用绑定时确定的项而不是实际参数指定的任意的项。
2988-因为绑定参数引入附加的函数调用,一般仅在有必要时使用。
2989-
2990-@6.9.5 环境、绑定访问和解析操作:
2991-基于环境的名称解析(@4.3.3) 操作可选地处理保留名称(@5.11.1.1) 并查找名称(@4.3.3) 。
2992-解析环境只支持要求作为一等对象(@4.1) 的环境引用(@5.4.3) 。
2993-以下 API 提供解析名称相关的操作:
2994-函数模板 NPL::EmplaceLeaf
2995-函数 NPL::ResolveName
2996-函数 NPL::MoveResolved
2997-函数 NPL::ResolveIdentifier
2998